Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(73)

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java

Issue 1904553003: Java implementation of PaymentRequest mojo service (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Disable Pay button before user provided required information. Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.chrome.browser.payments; 5 package org.chromium.chrome.browser.payments;
6 6
7 import android.app.Activity;
8 import android.graphics.Bitmap;
9 import android.text.TextUtils;
10
11 import org.chromium.base.Callback;
12 import org.chromium.chrome.browser.autofill.PersonalDataManager;
13 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
14 import org.chromium.chrome.browser.favicon.FaviconHelper;
15 import org.chromium.chrome.browser.payments.ui.LineItem;
16 import org.chromium.chrome.browser.payments.ui.PaymentInformation;
17 import org.chromium.chrome.browser.payments.ui.PaymentOption;
18 import org.chromium.chrome.browser.payments.ui.PaymentRequestUI;
19 import org.chromium.chrome.browser.payments.ui.SectionInformation;
20 import org.chromium.chrome.browser.profiles.Profile;
21 import org.chromium.content.browser.ContentViewCore;
7 import org.chromium.content_public.browser.WebContents; 22 import org.chromium.content_public.browser.WebContents;
8 import org.chromium.mojo.system.MojoException; 23 import org.chromium.mojo.system.MojoException;
9 import org.chromium.mojom.payments.PaymentDetails; 24 import org.chromium.mojom.payments.PaymentDetails;
25 import org.chromium.mojom.payments.PaymentItem;
10 import org.chromium.mojom.payments.PaymentOptions; 26 import org.chromium.mojom.payments.PaymentOptions;
11 import org.chromium.mojom.payments.PaymentRequest; 27 import org.chromium.mojom.payments.PaymentRequest;
12 import org.chromium.mojom.payments.PaymentRequestClient; 28 import org.chromium.mojom.payments.PaymentRequestClient;
29 import org.chromium.mojom.payments.PaymentResponse;
30 import org.chromium.mojom.payments.ShippingOption;
31 import org.chromium.ui.base.WindowAndroid;
32
33 import org.json.JSONException;
34 import org.json.JSONObject;
35
36 import java.util.ArrayList;
37 import java.util.HashSet;
38 import java.util.Iterator;
39 import java.util.LinkedList;
40 import java.util.List;
13 41
14 /** 42 /**
15 * Android implementation of the PaymentRequest service defined in 43 * Android implementation of the PaymentRequest service defined in
16 * third_party/WebKit/public/platform/modules/payments/payment_request.mojom. 44 * third_party/WebKit/public/platform/modules/payments/payment_request.mojom.
17 */ 45 */
18 public class PaymentRequestImpl implements PaymentRequest { 46 public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie nt,
47 PaymentApp.InstrumentsCallback,
Ted C 2016/04/22 03:59:37 indent 8 from the start of the previous line
please use gerrit instead 2016/04/25 19:22:31 Done.
48 PaymentInstrument.DetailsCallback {
49 /**
50 * The size for the favicon in density-independent pixels.
51 */
52 private static final int FAVICON_SIZE_DP = 24;
53
54 private Activity mContext;
55 private String mMerchantName;
56 private String mOrigin;
57 private Bitmap mFavicon;
58 private List<PaymentApp> mApps;
59 private PaymentRequestClient mClient;
60 private HashSet<String> mSupportedMethods;
Ted C 2016/04/22 03:59:38 Set/List generic versions plz
please use gerrit instead 2016/04/25 19:22:31 Done.
61 private ArrayList<LineItem> mLineItems;
62 private SectionInformation mShippingOptions;
63 private JSONObject mData;
64 private SectionInformation mShippingAddresses;
65 private List<PaymentApp> mPendingApps;
66 private List<PaymentInstrument> mPendingInstruments;
67 private SectionInformation mPaymentMethods;
68 private PaymentRequestUI mUI;
69 private Callback<PaymentInformation> mPaymentInformationCallback;
70 private Callback<SectionInformation> mPaymentMethodsCallback;
71 private boolean mClientCompletion;
72
19 /** 73 /**
20 * Builds the dialog. 74 * Builds the dialog.
21 * 75 *
22 * @param webContents The web contents that have invoked the PaymentRequest API. 76 * @param webContents The web contents that have invoked the PaymentRequest API.
23 */ 77 */
24 public PaymentRequestImpl(WebContents webContents) {} 78 public PaymentRequestImpl(WebContents webContents) {
79 if (webContents == null) return;
80
81 ContentViewCore contentViewCore = ContentViewCore.fromWebContents(webCon tents);
82 if (contentViewCore == null) return;
83
84 WindowAndroid window = contentViewCore.getWindowAndroid();
85 if (window == null) return;
86
87 mContext = window.getActivity().get();
88 if (mContext == null) return;
Ted C 2016/04/22 03:59:38 personal aside...we should really write a helper f
please use gerrit instead 2016/04/25 19:22:30 Acknowledged.
89
90 mMerchantName = webContents.getTitle();
91 mOrigin = webContents.getVisibleUrl();
92
93 final FaviconHelper faviconHelper = new FaviconHelper();
94 float scale = mContext.getResources().getDisplayMetrics().density;
95 faviconHelper.getLocalFaviconImageForURL(Profile.getLastUsedProfile(),
96 webContents.getVisibleUrl(), (int) (FAVICON_SIZE_DP * scale + 0. 5f),
97 new FaviconHelper.FaviconImageCallback() {
98 @Override
99 public void onFaviconAvailable(Bitmap bitmap, String iconUrl ) {
100 faviconHelper.destroy();
101 if (bitmap == null) return;
102 if (mUI == null) {
103 mFavicon = bitmap;
104 return;
105 }
106 mUI.setTitleBitmap(bitmap);
107 }
108 });
109
110 mApps = PaymentAppFactory.create(webContents);
111 }
25 112
26 /** 113 /**
27 * Called by the renderer to provide an endpoint for callbacks. 114 * Called by the renderer to provide an endpoint for callbacks.
28 */ 115 */
29 @Override 116 @Override
30 public void setClient(PaymentRequestClient client) { 117 public void setClient(PaymentRequestClient client) {
31 assert client != null; 118 mClient = client;
Ted C 2016/04/22 03:59:37 can this be called more than once? should we asse
please use gerrit instead 2016/04/25 19:22:31 Should not be called more than once. Added the ass
32 client.onError(); 119 if (mClient == null) return;
120 if (mContext == null) mClient.onError();
33 } 121 }
34 122
35 /** 123 /**
36 * Called by the merchant website to show the payment request to the user. 124 * Called by the merchant website to show the payment request to the user.
37 */ 125 */
38 @Override 126 @Override
39 public void show(String[] supportedMethods, PaymentDetails details, PaymentO ptions options, 127 public void show(String[] supportedMethods, PaymentDetails details, PaymentO ptions options,
40 String stringifiedData) {} 128 String stringifiedData) {
129 if (mClient == null) return;
130
131 if (mSupportedMethods != null) {
132 mClient.onError();
Ted C 2016/04/22 03:59:37 for debugging, we might want to log each of these
please use gerrit instead 2016/04/25 19:22:31 Done.
133 return;
134 }
135
136 mSupportedMethods = getValidatedSupportedMethods(supportedMethods);
137 if (mSupportedMethods == null) {
138 mClient.onError();
139 return;
140 }
141
142 mLineItems = getValidatedLineItems(details);
143 if (mLineItems == null) {
144 mClient.onError();
145 return;
146 }
147
148 mShippingOptions = getValidatedShippingOptions(details);
149 if (mShippingOptions == null) {
150 mClient.onError();
151 return;
152 }
153
154 mData = getValidatedData(mSupportedMethods, stringifiedData);
155 if (mData == null) {
156 mClient.onError();
157 return;
158 }
159
160 List<AutofillAddress> addresses = new LinkedList<AutofillAddress>();
161 for (AutofillProfile profile : PersonalDataManager.getInstance().getProf iles()) {
Ted C 2016/04/22 03:59:37 same comment about iterator vs for int i = 0... a
please use gerrit instead 2016/04/25 19:22:32 Done.
162 addresses.add(new AutofillAddress(profile));
163 }
164
165 if (addresses.isEmpty()) {
166 mShippingAddresses = new SectionInformation();
167 } else {
168 mShippingAddresses = new SectionInformation(0, addresses);
169 }
170
171 mPendingApps = new LinkedList<PaymentApp>(mApps);
172 mPendingInstruments = new LinkedList<PaymentInstrument>();
173 boolean isGettingInstruments = false;
174
175 for (PaymentApp app : mApps) {
176 HashSet<String> appMethods = app.getSupportedMethodNames();
177 appMethods.retainAll(mSupportedMethods);
178 if (!appMethods.isEmpty()) {
179 isGettingInstruments = true;
180 app.getInstruments(mLineItems, this);
181 }
182 }
183
184 if (!isGettingInstruments) mPaymentMethods = new SectionInformation();
185
186 boolean requestShipping = options != null && options.requestShipping;
187 mUI = new PaymentRequestUI(mContext, this, requestShipping, mMerchantNam e, mOrigin);
188 if (mFavicon != null) mUI.setTitleBitmap(mFavicon);
Ted C 2016/04/22 03:59:37 should we set mFavicon = null after this?
please use gerrit instead 2016/04/25 19:22:32 Done.
189 }
190
191 private HashSet<String> getValidatedSupportedMethods(String[] methods) {
Ted C 2016/04/22 03:59:37 s/HashSet/Set
please use gerrit instead 2016/04/25 19:22:31 Done.
192 if (methods == null || methods.length == 0) return null;
193
194 HashSet<String> result = new HashSet<String>();
Ted C 2016/04/22 03:59:37 when creating like this, you can do: Set<String>
please use gerrit instead 2016/04/25 19:22:31 Done.
195 for (String method : methods) {
196 if (TextUtils.isEmpty(method)) return null;
Ted C 2016/04/22 03:59:37 continue or return?
please use gerrit instead 2016/04/25 19:22:31 "return null" to indicate invalid method name.
Ted C 2016/04/25 22:49:02 I'm still somewhat surprised we don't just skip ov
please use gerrit instead 2016/04/25 23:19:24 Acknowledged.
197 result.add(method);
198 }
199
200 return result;
201 }
202
203 private ArrayList<LineItem> getValidatedLineItems(PaymentDetails details) {
Ted C 2016/04/22 03:59:37 List
please use gerrit instead 2016/04/25 19:22:31 Done.
204 if (null == details || details.items == null || details.items.length == 0) return null;
Ted C 2016/04/22 03:59:37 details == null is the more consistent thing to do
please use gerrit instead 2016/04/25 19:22:31 Done.
205
206 for (PaymentItem item : details.items) {
207 if (item == null || TextUtils.isEmpty(item.id) || TextUtils.isEmpty( item.label)
208 || item.amount == null || TextUtils.isEmpty(item.amount.curr encyCode)
209 || TextUtils.isEmpty(item.amount.value)) {
210 return null;
211 }
212 }
213
214 PaymentItem total = details.items[details.items.length - 1];
215 String totalCurrencyCode = total.amount.currencyCode;
216 CurrencyStringFormatter totalFormatter = new CurrencyStringFormatter(tot alCurrencyCode);
217 ArrayList<LineItem> result = new ArrayList<LineItem>(details.items.lengt h);
218
219 for (PaymentItem item : details.items) {
Ted C 2016/04/22 03:59:37 i would do i < length - 1 and skip the == total ch
please use gerrit instead 2016/04/25 19:22:32 Done.
220 if (item == total) break;
221 String currencyCode = item.amount.currencyCode;
222 if (currencyCode.equals(totalCurrencyCode)) {
223 result.add(new LineItem(item.label, "", totalFormatter.format(it em.amount.value)));
224 } else {
225 result.add(new LineItem(item.label, currencyCode,
Ted C 2016/04/22 03:59:37 yikes...we support multiple currencies in a single
please use gerrit instead 2016/04/25 19:22:31 Although the spec does not explicitly prohibit thi
226 new CurrencyStringFormatter(currencyCode).format(item.am ount.value)));
227 }
228 }
229
230 result.add(new LineItem(
231 total.label, totalCurrencyCode, totalFormatter.format(total.amou nt.value)));
232
233 return result;
234 }
235
236 private SectionInformation getValidatedShippingOptions(PaymentDetails detail s) {
237 if (details.shippingOptions == null || details.shippingOptions.length == 0) {
238 return new SectionInformation();
239 }
240
241 ArrayList<PaymentOption> result = new ArrayList<PaymentOption>();
242 for (ShippingOption option : details.shippingOptions) {
243 if (option == null || TextUtils.isEmpty(option.id) || TextUtils.isEm pty(option.label)
244 || option.amount == null || TextUtils.isEmpty(option.amount. currencyCode)
245 || TextUtils.isEmpty(option.amount.value)) {
246 return null;
Ted C 2016/04/22 03:59:37 return or continue?
please use gerrit instead 2016/04/25 19:22:31 "return null" to indicate invalid shipping options
247 }
248 result.add(new PaymentOption(option.id, option.label, "", PaymentOpt ion.NO_ICON));
249 }
250
251 return new SectionInformation(
252 result.size() == 1 ? 0 : SectionInformation.NO_SELECTION, result );
253 }
254
255 private JSONObject getValidatedData(HashSet<String> supportedMethods, String stringifiedData) {
256 if (TextUtils.isEmpty(stringifiedData)) return new JSONObject();
257
258 JSONObject result;
259 try {
260 result = new JSONObject(stringifiedData);
261 } catch (JSONException e) {
262 return null;
263 }
264
265 Iterator<String> it = result.keys();
266 while (it.hasNext()) {
267 String name = it.next();
268 if (!supportedMethods.contains(name)) return null;
Ted C 2016/04/22 03:59:37 same...return or continue?
please use gerrit instead 2016/04/25 19:22:31 "return null" to indicate invalid data. I'll add a
269 if (result.optJSONObject(name) == null) return null;
270 }
271
272 return result;
273 }
274
275 /**
276 * Called to retrieve the data to show in the initial PaymentRequest UI.
277 */
278 @Override
279 public void getDefaultPaymentInformation(Callback<PaymentInformation> callba ck) {
280 mPaymentInformationCallback = callback;
281 if (mPaymentMethods == null) return;
282 provideDefaultPaymentInformation();
Ted C 2016/04/22 03:59:37 in general I would avoid this indirection for now.
please use gerrit instead 2016/04/25 19:22:31 I call provideDefault() also when I've finished lo
283 }
284
285 private void provideDefaultPaymentInformation() {
286 mPaymentInformationCallback.onResult(new PaymentInformation(
287 mLineItems.get(mLineItems.size() - 1), mShippingAddresses.getSel ectedItem(),
288 mShippingOptions.getSelectedItem(), mPaymentMethods.getSelectedI tem()));
289 mPaymentInformationCallback = null;
290 }
291
292 @Override
293 public void getLineItems(Callback<ArrayList<LineItem>> callback) {
294 callback.onResult(mLineItems);
Ted C 2016/04/22 03:59:37 same async-ness for all the callbacks
please use gerrit instead 2016/04/25 19:22:31 Done.
295 }
296
297 @Override
298 public void getShippingAddresses(Callback<SectionInformation> callback) {
299 callback.onResult(mShippingAddresses);
300 }
301
302 @Override
303 public void getShippingOptions(Callback<SectionInformation> callback) {
304 callback.onResult(mShippingOptions);
305 }
306
307 @Override
308 public void getPaymentMethods(Callback<SectionInformation> callback) {
309 mPaymentMethodsCallback = callback;
310 if (mPaymentMethods == null) return;
311 providePaymentMethods();
312 }
313
314 private void providePaymentMethods() {
315 mPaymentMethodsCallback.onResult(mPaymentMethods);
316 mPaymentMethodsCallback = null;
317 }
318
319 @Override
320 public void onShippingAddressChanged(PaymentOption selectedShippingAddress) {
321 assert selectedShippingAddress instanceof AutofillAddress;
322 mShippingAddresses.setSelectedItem(selectedShippingAddress);
323 mClient.onShippingAddressChange(
324 ((AutofillAddress) selectedShippingAddress).toShippingAddress()) ;
325 }
326
327 @Override
328 public void onShippingOptionChanged(PaymentOption selectedShippingOption) {
329 mShippingOptions.setSelectedItem(selectedShippingOption);
330 mClient.onShippingOptionChange(selectedShippingOption.getIdentifier());
331 }
332
333 @Override
334 public void onPaymentMethodChanged(PaymentOption selectedPaymentMethod) {
335 assert selectedPaymentMethod instanceof PaymentInstrument;
336 mPaymentMethods.setSelectedItem(selectedPaymentMethod);
337 }
338
339 @Override
340 public void onPayClicked(PaymentOption selectedShippingAddress,
341 PaymentOption selectedShippingOption, PaymentOption selectedPaymentM ethod) {
342 assert selectedPaymentMethod instanceof PaymentInstrument;
343 PaymentInstrument instrument = (PaymentInstrument) selectedPaymentMethod ;
344 instrument.getDetails(mMerchantName, mOrigin, mLineItems,
345 mData.optJSONObject(instrument.getMethodName()), this);
346 }
347
348 @Override
349 public void onDismiss() {
350 mClient.onError();
351 closeUI(false);
352 }
41 353
42 /** 354 /**
43 * Called by the merchant website to abort the payment. 355 * Called by the merchant website to abort the payment.
44 */ 356 */
45 @Override 357 @Override
46 public void abort() {} 358 public void abort() {
359 closeUI(false);
360 }
47 361
48 /** 362 /**
49 * Called when the merchant website has processed the payment. 363 * Called when the merchant website has processed the payment.
50 */ 364 */
51 @Override 365 @Override
52 public void complete(boolean success) {} 366 public void complete(boolean success) {
367 mClientCompletion = true;
368 closeUI(success);
369 }
53 370
54 /** 371 /**
55 * Called when the renderer closes the Mojo connection. 372 * Called when the renderer closes the Mojo connection.
56 */ 373 */
57 @Override 374 @Override
58 public void close() {} 375 public void close() {
376 closeUI(false);
377 }
59 378
60 /** 379 /**
61 * Called when the Mojo connection encounters an error. 380 * Called when the Mojo connection encounters an error.
62 */ 381 */
63 @Override 382 @Override
64 public void onConnectionError(MojoException e) {} 383 public void onConnectionError(MojoException e) {
384 closeUI(false);
385 }
386
387 /**
388 * Called after retrieving the list of payment instruments in an app.
389 */
390 @Override
391 public void onInstrumentsReady(PaymentApp app, List<PaymentInstrument> instr uments) {
392 mPendingApps.remove(app);
393
394 if (instruments != null) {
395 for (PaymentInstrument instrument : instruments) {
396 if (mSupportedMethods.contains(instrument.getMethodName())) {
397 mPendingInstruments.add(instrument);
398 } else {
399 instrument.dismiss();
400 }
401 }
402 }
403
404 if (mPendingApps.isEmpty()) {
405 if (mPendingInstruments.isEmpty()) {
406 mPaymentMethods = new SectionInformation();
407 } else {
408 mPaymentMethods = new SectionInformation(0, mPendingInstruments) ;
409 mPendingInstruments.clear();
410 }
411
412 if (mPaymentInformationCallback != null) provideDefaultPaymentInform ation();
413 if (mPaymentMethodsCallback != null) providePaymentMethods();
414 }
415 }
416
417 /**
418 * Called after retrieving instrument details.
419 */
420 @Override
421 public void onInstrumentDetailsReady(String methodName, String stringifiedDe tails) {
422 PaymentResponse response = new PaymentResponse();
423 response.methodName = methodName;
424 response.stringifiedDetails = stringifiedDetails;
425 mClient.onPaymentResponse(response);
426 }
427
428 /**
429 * Called if unable to retrieve instrument details.
430 */
431 @Override
432 public void onInstrumentDetailsError() {
433 mClient.onError();
434 closeUI(false);
435 }
436
437 /**
438 * Stop communications with the client and closes the UI.
439 */
440 private void closeUI(boolean paymentSuccess) {
441 if (mUI != null) {
442 mUI.close(paymentSuccess, new Runnable() {
443 @Override
444 public void run() {
445 if (!mClientCompletion) return;
446 if (mClient == null) return;
447 mClient.onComplete();
448 mClient = null;
449 }
450 });
451 mUI = null;
452 } else {
453 mClient = null;
454 }
455
456 if (mPaymentMethods != null) {
457 for (int i = 0; i < mPaymentMethods.getSize(); i++) {
458 PaymentOption option = mPaymentMethods.getItem(i);
459 assert option instanceof PaymentInstrument;
460 ((PaymentInstrument) option).dismiss();
461 }
462 mPaymentMethods = null;
463 }
464 }
65 } 465 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698