OLD | NEW |
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; | 7 import android.app.Activity; |
8 import android.graphics.Bitmap; | 8 import android.graphics.Bitmap; |
9 import android.os.Handler; | 9 import android.os.Handler; |
10 import android.text.TextUtils; | 10 import android.text.TextUtils; |
(...skipping 13 matching lines...) Expand all Loading... |
24 import org.chromium.chrome.browser.preferences.autofill.AutofillCreditCardEditor
; | 24 import org.chromium.chrome.browser.preferences.autofill.AutofillCreditCardEditor
; |
25 import org.chromium.chrome.browser.preferences.autofill.AutofillProfileEditor; | 25 import org.chromium.chrome.browser.preferences.autofill.AutofillProfileEditor; |
26 import org.chromium.chrome.browser.profiles.Profile; | 26 import org.chromium.chrome.browser.profiles.Profile; |
27 import org.chromium.chrome.browser.util.UrlUtilities; | 27 import org.chromium.chrome.browser.util.UrlUtilities; |
28 import org.chromium.components.safejson.JsonSanitizer; | 28 import org.chromium.components.safejson.JsonSanitizer; |
29 import org.chromium.content.browser.ContentViewCore; | 29 import org.chromium.content.browser.ContentViewCore; |
30 import org.chromium.content_public.browser.WebContents; | 30 import org.chromium.content_public.browser.WebContents; |
31 import org.chromium.mojo.system.MojoException; | 31 import org.chromium.mojo.system.MojoException; |
32 import org.chromium.mojom.payments.PaymentDetails; | 32 import org.chromium.mojom.payments.PaymentDetails; |
33 import org.chromium.mojom.payments.PaymentItem; | 33 import org.chromium.mojom.payments.PaymentItem; |
| 34 import org.chromium.mojom.payments.PaymentMethodData; |
34 import org.chromium.mojom.payments.PaymentOptions; | 35 import org.chromium.mojom.payments.PaymentOptions; |
35 import org.chromium.mojom.payments.PaymentRequest; | 36 import org.chromium.mojom.payments.PaymentRequest; |
36 import org.chromium.mojom.payments.PaymentRequestClient; | 37 import org.chromium.mojom.payments.PaymentRequestClient; |
37 import org.chromium.mojom.payments.PaymentResponse; | 38 import org.chromium.mojom.payments.PaymentResponse; |
38 import org.chromium.mojom.payments.ShippingOption; | 39 import org.chromium.mojom.payments.ShippingOption; |
39 import org.chromium.ui.base.WindowAndroid; | 40 import org.chromium.ui.base.WindowAndroid; |
40 import org.json.JSONException; | 41 import org.json.JSONException; |
41 import org.json.JSONObject; | 42 import org.json.JSONObject; |
42 | 43 |
43 import java.io.IOException; | 44 import java.io.IOException; |
44 import java.util.ArrayList; | 45 import java.util.ArrayList; |
45 import java.util.Arrays; | 46 import java.util.Arrays; |
46 import java.util.HashSet; | 47 import java.util.HashMap; |
47 import java.util.Iterator; | |
48 import java.util.List; | 48 import java.util.List; |
49 import java.util.Locale; | 49 import java.util.Locale; |
50 import java.util.Set; | 50 import java.util.Set; |
51 import java.util.regex.Pattern; | 51 import java.util.regex.Pattern; |
52 | 52 |
53 /** | 53 /** |
54 * Android implementation of the PaymentRequest service defined in | 54 * Android implementation of the PaymentRequest service defined in |
55 * third_party/WebKit/public/platform/modules/payments/payment_request.mojom. | 55 * third_party/WebKit/public/platform/modules/payments/payment_request.mojom. |
56 */ | 56 */ |
57 public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie
nt, | 57 public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie
nt, |
58 PaymentApp.InstrumentsCallback, PaymentInstrument.DetailsCallback { | 58 PaymentApp.InstrumentsCallback, PaymentInstrument.DetailsCallback { |
59 | 59 |
60 /** | 60 /** |
61 * The size for the favicon in density-independent pixels. | 61 * The size for the favicon in density-independent pixels. |
62 */ | 62 */ |
63 private static final int FAVICON_SIZE_DP = 24; | 63 private static final int FAVICON_SIZE_DP = 24; |
64 | 64 |
65 private static final String TAG = "cr_PaymentRequest"; | 65 private static final String TAG = "cr_PaymentRequest"; |
66 | 66 |
67 private final Handler mHandler = new Handler(); | 67 private final Handler mHandler = new Handler(); |
68 | 68 |
69 private Activity mContext; | 69 private Activity mContext; |
70 private String mMerchantName; | 70 private String mMerchantName; |
71 private String mOrigin; | 71 private String mOrigin; |
72 private Bitmap mFavicon; | 72 private Bitmap mFavicon; |
73 private List<PaymentApp> mApps; | 73 private List<PaymentApp> mApps; |
74 private PaymentRequestClient mClient; | 74 private PaymentRequestClient mClient; |
75 private Set<String> mSupportedMethods; | |
76 | 75 |
77 /** | 76 /** |
78 * The raw total amount being charged, as it was received from the website.
This data is passed | 77 * The raw total amount being charged, as it was received from the website.
This data is passed |
79 * to the payment app. | 78 * to the payment app. |
80 */ | 79 */ |
81 private PaymentItem mRawTotal; | 80 private PaymentItem mRawTotal; |
82 | 81 |
83 /** | 82 /** |
84 * The raw items in the shopping cart, as they were received from the websit
e. This data is | 83 * The raw items in the shopping cart, as they were received from the websit
e. This data is |
85 * passed to the payment app. | 84 * passed to the payment app. |
(...skipping 12 matching lines...) Expand all Loading... |
98 * due to user selecting a shipping address. | 97 * due to user selecting a shipping address. |
99 */ | 98 */ |
100 private List<ShippingOption> mRawShippingOptions; | 99 private List<ShippingOption> mRawShippingOptions; |
101 | 100 |
102 /** | 101 /** |
103 * The UI model for the shipping options. Includes the label and sublabel fo
r each shipping | 102 * The UI model for the shipping options. Includes the label and sublabel fo
r each shipping |
104 * option. Also keeps track of the selected shipping option. This data is pa
ssed to the UI. | 103 * option. Also keeps track of the selected shipping option. This data is pa
ssed to the UI. |
105 */ | 104 */ |
106 private SectionInformation mUiShippingOptions; | 105 private SectionInformation mUiShippingOptions; |
107 | 106 |
108 private JSONObject mData; | 107 private HashMap<String, JSONObject> mMethodData; |
109 private SectionInformation mShippingAddressesSection; | 108 private SectionInformation mShippingAddressesSection; |
110 private List<PaymentApp> mPendingApps; | 109 private List<PaymentApp> mPendingApps; |
111 private List<PaymentInstrument> mPendingInstruments; | 110 private List<PaymentInstrument> mPendingInstruments; |
112 private SectionInformation mPaymentMethodsSection; | 111 private SectionInformation mPaymentMethodsSection; |
113 private PaymentRequestUI mUI; | 112 private PaymentRequestUI mUI; |
114 private Callback<PaymentInformation> mPaymentInformationCallback; | 113 private Callback<PaymentInformation> mPaymentInformationCallback; |
115 private Pattern mRegionCodePattern; | 114 private Pattern mRegionCodePattern; |
116 private boolean mMerchantNeedsShippingAddress; | 115 private boolean mMerchantNeedsShippingAddress; |
117 | 116 |
118 /** | 117 /** |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
170 | 169 |
171 if (mContext == null) { | 170 if (mContext == null) { |
172 disconnectFromClientWithDebugMessage("Web contents don't have associ
ated activity"); | 171 disconnectFromClientWithDebugMessage("Web contents don't have associ
ated activity"); |
173 } | 172 } |
174 } | 173 } |
175 | 174 |
176 /** | 175 /** |
177 * Called by the merchant website to show the payment request to the user. | 176 * Called by the merchant website to show the payment request to the user. |
178 */ | 177 */ |
179 @Override | 178 @Override |
180 public void show(String[] supportedMethods, PaymentDetails details, PaymentO
ptions options, | 179 public void show(PaymentMethodData[] methodData, PaymentDetails details, |
181 String stringifiedData) { | 180 PaymentOptions options) { |
182 if (mClient == null) return; | 181 if (mClient == null) return; |
183 | 182 |
184 if (mSupportedMethods != null) { | 183 if (mMethodData != null) { |
185 disconnectFromClientWithDebugMessage("PaymentRequest.show() called m
ore than once."); | 184 disconnectFromClientWithDebugMessage("PaymentRequest.show() called m
ore than once."); |
186 return; | 185 return; |
187 } | 186 } |
188 | 187 |
189 mSupportedMethods = getValidatedSupportedMethods(supportedMethods); | 188 mMethodData = getValidatedMethodData(methodData); |
190 if (mSupportedMethods == null) { | 189 if (mMethodData == null) { |
191 disconnectFromClientWithDebugMessage("Invalid payment methods"); | 190 disconnectFromClientWithDebugMessage("Invalid payment methods or dat
a"); |
192 return; | 191 return; |
193 } | 192 } |
194 | 193 |
195 if (!parseAndValidateDetailsOrDisconnectFromClient(details)) return; | 194 if (!parseAndValidateDetailsOrDisconnectFromClient(details)) return; |
196 | 195 |
197 // If the merchant requests shipping and does not provide a selected shi
pping option, then | 196 // If the merchant requests shipping and does not provide a selected shi
pping option, then |
198 // the merchant needs the shipping address to calculate the shipping pri
ce and availability. | 197 // the merchant needs the shipping address to calculate the shipping pri
ce and availability. |
199 boolean requestShipping = options != null && options.requestShipping; | 198 boolean requestShipping = options != null && options.requestShipping; |
200 mMerchantNeedsShippingAddress = | 199 mMerchantNeedsShippingAddress = |
201 requestShipping && mUiShippingOptions.getSelectedItem() == null; | 200 requestShipping && mUiShippingOptions.getSelectedItem() == null; |
202 | 201 |
203 mData = getValidatedData(mSupportedMethods, stringifiedData); | |
204 if (mData == null) { | |
205 disconnectFromClientWithDebugMessage("Invalid payment method specifi
c data"); | |
206 return; | |
207 } | |
208 | |
209 List<AutofillAddress> addresses = new ArrayList<>(); | 202 List<AutofillAddress> addresses = new ArrayList<>(); |
210 List<AutofillProfile> profiles = PersonalDataManager.getInstance().getPr
ofilesToSuggest(); | 203 List<AutofillProfile> profiles = PersonalDataManager.getInstance().getPr
ofilesToSuggest(); |
211 for (int i = 0; i < profiles.size(); i++) { | 204 for (int i = 0; i < profiles.size(); i++) { |
212 AutofillProfile profile = profiles.get(i); | 205 AutofillProfile profile = profiles.get(i); |
213 if (profile.getCountryCode() != null | 206 if (profile.getCountryCode() != null |
214 && mRegionCodePattern.matcher(profile.getCountryCode()).matc
hes() | 207 && mRegionCodePattern.matcher(profile.getCountryCode()).matc
hes() |
215 && profile.getStreetAddress() != null && profile.getRegion()
!= null | 208 && profile.getStreetAddress() != null && profile.getRegion()
!= null |
216 && profile.getLocality() != null && profile.getDependentLoca
lity() != null | 209 && profile.getLocality() != null && profile.getDependentLoca
lity() != null |
217 && profile.getPostalCode() != null && profile.getSortingCode
() != null | 210 && profile.getPostalCode() != null && profile.getSortingCode
() != null |
218 && profile.getCompanyName() != null && profile.getFullName()
!= null | 211 && profile.getCompanyName() != null && profile.getFullName()
!= null |
219 && profile.getPhoneNumber() != null) { | 212 && profile.getPhoneNumber() != null) { |
220 addresses.add(new AutofillAddress(profile)); | 213 addresses.add(new AutofillAddress(profile)); |
221 } | 214 } |
222 } | 215 } |
223 | 216 |
224 int selectedIndex = SectionInformation.NO_SELECTION; | 217 int selectedIndex = SectionInformation.NO_SELECTION; |
225 if (!addresses.isEmpty() && mUiShippingOptions.getSelectedItem() != null
) { | 218 if (!addresses.isEmpty() && mUiShippingOptions.getSelectedItem() != null
) { |
226 selectedIndex = 0; | 219 selectedIndex = 0; |
227 } | 220 } |
228 mShippingAddressesSection = new SectionInformation( | 221 mShippingAddressesSection = new SectionInformation( |
229 PaymentRequestUI.TYPE_SHIPPING_ADDRESSES, selectedIndex, address
es); | 222 PaymentRequestUI.TYPE_SHIPPING_ADDRESSES, selectedIndex, address
es); |
230 | 223 |
231 mPendingApps = new ArrayList<>(mApps); | 224 mPendingApps = new ArrayList<>(mApps); |
232 mPendingInstruments = new ArrayList<>(); | 225 mPendingInstruments = new ArrayList<>(); |
233 boolean isGettingInstruments = false; | 226 boolean isGettingInstruments = false; |
234 | 227 |
235 for (int i = 0; i < mApps.size(); i++) { | 228 for (int i = 0; i < mApps.size(); i++) { |
236 PaymentApp app = mApps.get(i); | 229 PaymentApp app = mApps.get(i); |
237 Set<String> appMethods = app.getSupportedMethodNames(); | 230 Set<String> appMethods = app.getSupportedMethodNames(); |
238 appMethods.retainAll(mSupportedMethods); | 231 appMethods.retainAll(mMethodData.keySet()); |
239 if (appMethods.isEmpty()) { | 232 if (appMethods.isEmpty()) { |
240 mPendingApps.remove(app); | 233 mPendingApps.remove(app); |
241 } else { | 234 } else { |
242 isGettingInstruments = true; | 235 isGettingInstruments = true; |
243 app.getInstruments(mRawTotal, mRawLineItems, this); | 236 app.getInstruments(mRawTotal, mRawLineItems, this); |
244 } | 237 } |
245 } | 238 } |
246 | 239 |
247 if (!isGettingInstruments) { | 240 if (!isGettingInstruments) { |
248 mPaymentMethodsSection = new SectionInformation(PaymentRequestUI.TYP
E_PAYMENT_METHODS); | 241 mPaymentMethodsSection = new SectionInformation(PaymentRequestUI.TYP
E_PAYMENT_METHODS); |
249 } | 242 } |
250 | 243 |
251 mUI = PaymentRequestUI.show(mContext, this, requestShipping, mMerchantNa
me, mOrigin); | 244 mUI = PaymentRequestUI.show(mContext, this, requestShipping, mMerchantNa
me, mOrigin); |
252 if (mFavicon != null) mUI.setTitleBitmap(mFavicon); | 245 if (mFavicon != null) mUI.setTitleBitmap(mFavicon); |
253 mFavicon = null; | 246 mFavicon = null; |
254 } | 247 } |
255 | 248 |
256 private HashSet<String> getValidatedSupportedMethods(String[] methods) { | 249 private HashMap<String, JSONObject> getValidatedMethodData(PaymentMethodData
[] methodData) { |
257 // Payment methods are required. | 250 // Payment methodData are required. |
258 if (methods == null || methods.length == 0) return null; | 251 if (methodData == null || methodData.length == 0) return null; |
| 252 HashMap<String, JSONObject> result = new HashMap<>(); |
| 253 for (int i = 0; i < methodData.length; i++) { |
| 254 JSONObject data = null; |
| 255 if (!TextUtils.isEmpty(methodData[i].stringifiedData)) { |
| 256 try { |
| 257 data = new JSONObject(JsonSanitizer.sanitize(methodData[i].s
tringifiedData)); |
| 258 } catch (JSONException | IOException | IllegalStateException e)
{ |
| 259 // Payment method specific data should be a JSON object. |
| 260 // According to the payment request spec[1], for each method
data, |
| 261 // if the data field is supplied but is not a JSON-serializa
ble object, |
| 262 // then should throw a TypeError. So, we should return null
here even if |
| 263 // only one is bad. |
| 264 // [1] https://w3c.github.io/browser-payment-api/specs/payme
ntrequest.html |
| 265 return null; |
| 266 } |
| 267 } |
259 | 268 |
260 HashSet<String> result = new HashSet<>(); | 269 String[] methods = methodData[i].supportedMethods; |
261 for (int i = 0; i < methods.length; i++) { | 270 |
262 // Payment methods should be non-empty. | 271 // Payment methods are required. |
263 if (TextUtils.isEmpty(methods[i])) return null; | 272 if (methods == null || methods.length == 0) return null; |
264 result.add(methods[i]); | 273 |
| 274 for (int j = 0; j < methods.length; j++) { |
| 275 // Payment methods should be non-empty. |
| 276 if (TextUtils.isEmpty(methods[j])) return null; |
| 277 result.put(methods[j], data); |
| 278 } |
265 } | 279 } |
266 | |
267 return result; | 280 return result; |
268 } | 281 } |
269 | 282 |
270 /** | 283 /** |
271 * Called by merchant to update the shipping options and line items after th
e user has selected | 284 * Called by merchant to update the shipping options and line items after th
e user has selected |
272 * their shipping address or shipping option. | 285 * their shipping address or shipping option. |
273 */ | 286 */ |
274 @Override | 287 @Override |
275 public void updateWith(PaymentDetails details) { | 288 public void updateWith(PaymentDetails details) { |
276 if (mClient == null) return; | 289 if (mClient == null) return; |
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
437 ShippingOption option = options[i]; | 450 ShippingOption option = options[i]; |
438 result.add(new PaymentOption(option.id, option.label, | 451 result.add(new PaymentOption(option.id, option.label, |
439 formatter.format(option.amount.value), PaymentOption.NO_ICON
)); | 452 formatter.format(option.amount.value), PaymentOption.NO_ICON
)); |
440 if (option.selected) selectedItemIndex = i; | 453 if (option.selected) selectedItemIndex = i; |
441 } | 454 } |
442 | 455 |
443 return new SectionInformation(PaymentRequestUI.TYPE_SHIPPING_OPTIONS, se
lectedItemIndex, | 456 return new SectionInformation(PaymentRequestUI.TYPE_SHIPPING_OPTIONS, se
lectedItemIndex, |
444 result); | 457 result); |
445 } | 458 } |
446 | 459 |
447 private JSONObject getValidatedData(Set<String> supportedMethods, String str
ingifiedData) { | |
448 if (TextUtils.isEmpty(stringifiedData)) return new JSONObject(); | |
449 | |
450 JSONObject result; | |
451 try { | |
452 result = new JSONObject(JsonSanitizer.sanitize(stringifiedData)); | |
453 } catch (JSONException | IOException | IllegalStateException e) { | |
454 // Payment method specific data should be a valid JSON object. | |
455 return null; | |
456 } | |
457 | |
458 Iterator<String> it = result.keys(); | |
459 while (it.hasNext()) { | |
460 String name = it.next(); | |
461 // Each key should be one of the supported payment methods. | |
462 if (!supportedMethods.contains(name)) return null; | |
463 // Each value should be a JSON object. | |
464 if (result.optJSONObject(name) == null) return null; | |
465 } | |
466 | |
467 return result; | |
468 } | |
469 | |
470 /** | 460 /** |
471 * Called to retrieve the data to show in the initial PaymentRequest UI. | 461 * Called to retrieve the data to show in the initial PaymentRequest UI. |
472 */ | 462 */ |
473 @Override | 463 @Override |
474 public void getDefaultPaymentInformation(Callback<PaymentInformation> callba
ck) { | 464 public void getDefaultPaymentInformation(Callback<PaymentInformation> callba
ck) { |
475 mPaymentInformationCallback = callback; | 465 mPaymentInformationCallback = callback; |
476 | 466 |
477 if (mPaymentMethodsSection == null) return; | 467 if (mPaymentMethodsSection == null) return; |
478 | 468 |
479 mHandler.post(new Runnable() { | 469 mHandler.post(new Runnable() { |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
550 mContext, AutofillCreditCardEditor.class.getName()); | 540 mContext, AutofillCreditCardEditor.class.getName()); |
551 } | 541 } |
552 } | 542 } |
553 | 543 |
554 @Override | 544 @Override |
555 public void onPayClicked(PaymentOption selectedShippingAddress, | 545 public void onPayClicked(PaymentOption selectedShippingAddress, |
556 PaymentOption selectedShippingOption, PaymentOption selectedPaymentM
ethod) { | 546 PaymentOption selectedShippingOption, PaymentOption selectedPaymentM
ethod) { |
557 assert selectedPaymentMethod instanceof PaymentInstrument; | 547 assert selectedPaymentMethod instanceof PaymentInstrument; |
558 PaymentInstrument instrument = (PaymentInstrument) selectedPaymentMethod
; | 548 PaymentInstrument instrument = (PaymentInstrument) selectedPaymentMethod
; |
559 instrument.getDetails(mMerchantName, mOrigin, mRawTotal, mRawLineItems, | 549 instrument.getDetails(mMerchantName, mOrigin, mRawTotal, mRawLineItems, |
560 mData.optJSONObject(instrument.getMethodName()), this); | 550 mMethodData.get(instrument.getMethodName()), this); |
561 } | 551 } |
562 | 552 |
563 @Override | 553 @Override |
564 public void onDismiss() { | 554 public void onDismiss() { |
565 disconnectFromClientWithDebugMessage("Dialog dismissed"); | 555 disconnectFromClientWithDebugMessage("Dialog dismissed"); |
566 closeUI(false); | 556 closeUI(false); |
567 } | 557 } |
568 | 558 |
569 @Override | 559 @Override |
570 public boolean merchantNeedsShippingAddress() { | 560 public boolean merchantNeedsShippingAddress() { |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
609 /** | 599 /** |
610 * Called after retrieving the list of payment instruments in an app. | 600 * Called after retrieving the list of payment instruments in an app. |
611 */ | 601 */ |
612 @Override | 602 @Override |
613 public void onInstrumentsReady(PaymentApp app, List<PaymentInstrument> instr
uments) { | 603 public void onInstrumentsReady(PaymentApp app, List<PaymentInstrument> instr
uments) { |
614 mPendingApps.remove(app); | 604 mPendingApps.remove(app); |
615 | 605 |
616 if (instruments != null) { | 606 if (instruments != null) { |
617 for (int i = 0; i < instruments.size(); i++) { | 607 for (int i = 0; i < instruments.size(); i++) { |
618 PaymentInstrument instrument = instruments.get(i); | 608 PaymentInstrument instrument = instruments.get(i); |
619 if (mSupportedMethods.contains(instrument.getMethodName())) { | 609 if (mMethodData.containsKey(instrument.getMethodName())) { |
620 mPendingInstruments.add(instrument); | 610 mPendingInstruments.add(instrument); |
621 } else { | 611 } else { |
622 instrument.dismiss(); | 612 instrument.dismiss(); |
623 } | 613 } |
624 } | 614 } |
625 } | 615 } |
626 | 616 |
627 if (mPendingApps.isEmpty()) { | 617 if (mPendingApps.isEmpty()) { |
628 mPaymentMethodsSection = new SectionInformation( | 618 mPaymentMethodsSection = new SectionInformation( |
629 PaymentRequestUI.TYPE_PAYMENT_METHODS, 0, mPendingInstrument
s); | 619 PaymentRequestUI.TYPE_PAYMENT_METHODS, 0, mPendingInstrument
s); |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
697 } | 687 } |
698 mPaymentMethodsSection = null; | 688 mPaymentMethodsSection = null; |
699 } | 689 } |
700 } | 690 } |
701 | 691 |
702 private void closeClient() { | 692 private void closeClient() { |
703 if (mClient != null) mClient.close(); | 693 if (mClient != null) mClient.close(); |
704 mClient = null; | 694 mClient = null; |
705 } | 695 } |
706 } | 696 } |
OLD | NEW |