Index: chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java |
index 2e3d9ba46745dc09377bdbf3d9d7d39dd3f56df4..8a9001c90bbec5414d8630c176fb8413502fbd32 100644 |
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java |
@@ -19,6 +19,7 @@ import org.chromium.chrome.browser.payments.ui.PaymentInformation; |
import org.chromium.chrome.browser.payments.ui.PaymentOption; |
import org.chromium.chrome.browser.payments.ui.PaymentRequestUI; |
import org.chromium.chrome.browser.payments.ui.SectionInformation; |
+import org.chromium.chrome.browser.payments.ui.ShoppingCart; |
import org.chromium.chrome.browser.preferences.PreferencesLauncher; |
import org.chromium.chrome.browser.preferences.autofill.AutofillCreditCardEditor; |
import org.chromium.chrome.browser.preferences.autofill.AutofillProfileEditor; |
@@ -53,6 +54,7 @@ import java.util.regex.Pattern; |
*/ |
public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Client, |
PaymentApp.InstrumentsCallback, PaymentInstrument.DetailsCallback { |
+ |
/** |
* The size for the favicon in density-independent pixels. |
*/ |
@@ -69,10 +71,38 @@ public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie |
private List<PaymentApp> mApps; |
private PaymentRequestClient mClient; |
private Set<String> mSupportedMethods; |
- private List<LineItem> mLineItems; |
- private List<PaymentItem> mDisplayItems; |
- private List<ShippingOption> mShippingOptions; |
- private SectionInformation mShippingOptionsSection; |
+ |
+ /** |
+ * The raw total amount being charged, as it was received from the website. This data is passed |
+ * to the payment app. |
+ */ |
+ private PaymentItem mRawTotal; |
+ |
+ /** |
+ * The raw items in the shopping cart, as they were received from the website. This data is |
+ * passed to the payment app. |
+ */ |
+ private List<PaymentItem> mRawLineItems; |
+ |
+ /** |
+ * The UI model of the shopping cart, including the total. Each item includes a label and a |
+ * price string. This data is passed to the UI. |
+ */ |
+ private ShoppingCart mUiShoppingCart; |
+ |
+ /** |
+ * The raw shipping options, as they were received from the website. This data is compared to |
+ * updated payment options from the website to determine whether shipping options have changed |
+ * due to user selecting a shipping address. |
+ */ |
+ private List<ShippingOption> mRawShippingOptions; |
+ |
+ /** |
+ * The UI model for the shipping options. Includes the label and sublabel for each shipping |
+ * option. Also keeps track of the selected shipping option. This data is passed to the UI. |
+ */ |
+ private SectionInformation mUiShippingOptions; |
+ |
private JSONObject mData; |
private SectionInformation mShippingAddressesSection; |
private List<PaymentApp> mPendingApps; |
@@ -160,12 +190,12 @@ public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie |
return; |
} |
- if (!setLineItemsAndShippingOptionsOrDisconnectFromClient(details)) return; |
+ if (!parseAndValidateDetailsOrDisconnectFromClient(details)) return; |
// If the merchant requests shipping and does not provide shipping options here, then the |
// merchant needs the shipping address to calculate shipping price and availability. |
boolean requestShipping = options != null && options.requestShipping; |
- mMerchantNeedsShippingAddress = requestShipping && mShippingOptionsSection.isEmpty(); |
+ mMerchantNeedsShippingAddress = requestShipping && mUiShippingOptions.isEmpty(); |
mData = getValidatedData(mSupportedMethods, stringifiedData); |
if (mData == null) { |
@@ -188,7 +218,7 @@ public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie |
} |
int selectedIndex = SectionInformation.NO_SELECTION; |
- if (!addresses.isEmpty() && mShippingOptionsSection.getSelectedItem() != null) { |
+ if (!addresses.isEmpty() && mUiShippingOptions.getSelectedItem() != null) { |
selectedIndex = 0; |
} |
mShippingAddressesSection = new SectionInformation( |
@@ -206,7 +236,7 @@ public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie |
mPendingApps.remove(app); |
} else { |
isGettingInstruments = true; |
- app.getInstruments(mDisplayItems, this); |
+ app.getInstruments(mRawTotal, mRawLineItems, this); |
} |
} |
@@ -219,6 +249,20 @@ public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie |
mFavicon = null; |
} |
+ private HashSet<String> getValidatedSupportedMethods(String[] methods) { |
+ // Payment methods are required. |
+ if (methods == null || methods.length == 0) return null; |
+ |
+ HashSet<String> result = new HashSet<>(); |
+ for (int i = 0; i < methods.length; i++) { |
+ // Payment methods should be non-empty. |
+ if (TextUtils.isEmpty(methods[i])) return null; |
+ result.add(methods[i]); |
+ } |
+ |
+ return result; |
+ } |
+ |
/** |
* Called by merchant to update the shipping options and line items after the user has selected |
* their shipping address or shipping option. |
@@ -233,142 +277,182 @@ public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie |
return; |
} |
- if (!setLineItemsAndShippingOptionsOrDisconnectFromClient(details)) return; |
+ if (!parseAndValidateDetailsOrDisconnectFromClient(details)) return; |
// Empty shipping options means the merchant cannot ship to the user's selected shipping |
// address. |
- if (mShippingOptionsSection.isEmpty() && !mMerchantNeedsShippingAddress) { |
- disconnectFromClientWithDebugMessage("Merchant indicates inablity to ship although " |
+ if (mUiShippingOptions.isEmpty() && !mMerchantNeedsShippingAddress) { |
+ disconnectFromClientWithDebugMessage("Merchant indicates inability to ship although " |
+ "originally indicated that can ship anywhere"); |
+ return; |
} |
- mUI.updateOrderSummarySection(mLineItems); |
- mUI.updateSection(PaymentRequestUI.TYPE_SHIPPING_OPTIONS, mShippingOptionsSection); |
+ mUI.updateOrderSummarySection(mUiShoppingCart); |
+ mUI.updateSection(PaymentRequestUI.TYPE_SHIPPING_OPTIONS, mUiShippingOptions); |
} |
- private boolean setLineItemsAndShippingOptionsOrDisconnectFromClient(PaymentDetails details) { |
- mLineItems = getValidatedLineItems(details); |
- if (mLineItems == null) { |
- disconnectFromClientWithDebugMessage("Invalid line items"); |
+ /** |
+ * Sets the total, display line items, and shipping options based on input and returns the |
+ * status boolean. That status is true for valid data, false for invalid data. If the input is |
+ * invalid, disconnects from the client. Both raw and UI versions of data are updated. |
+ * |
+ * @param details The total, line items, and shipping options to parse, validate, and save in |
+ * member variables. |
+ * @return True if the data is valid. False if the data is invalid. |
+ */ |
+ private boolean parseAndValidateDetailsOrDisconnectFromClient(PaymentDetails details) { |
+ if (details == null) { |
+ disconnectFromClientWithDebugMessage("Payment details required"); |
return false; |
} |
- mDisplayItems = Arrays.asList(details.displayItems); |
- mShippingOptionsSection = |
- getValidatedShippingOptions(details.displayItems[0].amount.currencyCode, details); |
- if (mShippingOptionsSection == null) { |
- disconnectFromClientWithDebugMessage("Invalid shipping options"); |
+ if (!hasAllPaymentItemFields(details.total)) { |
+ disconnectFromClientWithDebugMessage("Invalid total"); |
return false; |
} |
- mShippingOptions = Arrays.asList(details.shippingOptions); |
- return true; |
- } |
+ String totalCurrency = details.total.amount.currencyCode; |
+ CurrencyStringFormatter formatter = |
+ new CurrencyStringFormatter(totalCurrency, Locale.getDefault()); |
- private HashSet<String> getValidatedSupportedMethods(String[] methods) { |
- // Payment methods are required. |
- if (methods == null || methods.length == 0) return null; |
+ if (!formatter.isValidAmountCurrencyCode(details.total.amount.currencyCode)) { |
+ disconnectFromClientWithDebugMessage("Invalid total amount currency"); |
+ return false; |
+ } |
- HashSet<String> result = new HashSet<>(); |
- for (int i = 0; i < methods.length; i++) { |
- // Payment methods should be non-empty. |
- if (TextUtils.isEmpty(methods[i])) return null; |
- result.add(methods[i]); |
+ if (!formatter.isValidAmountValue(details.total.amount.value) |
+ || details.total.amount.value.startsWith("-")) { |
+ disconnectFromClientWithDebugMessage("Invalid total amount value"); |
+ return false; |
} |
- return result; |
- } |
+ LineItem uiTotal = new LineItem( |
+ details.total.label, totalCurrency, formatter.format(details.total.amount.value)); |
- private List<LineItem> getValidatedLineItems(PaymentDetails details) { |
- // Line items are required. |
- if (details == null || details.displayItems == null || details.displayItems.length == 0) { |
- return null; |
+ List<LineItem> uiLineItems = getValidatedLineItems(details.displayItems, totalCurrency, |
+ formatter); |
+ if (uiLineItems == null) { |
+ disconnectFromClientWithDebugMessage("Invalid line items"); |
+ return false; |
} |
- for (int i = 0; i < details.displayItems.length; i++) { |
- PaymentItem item = details.displayItems[i]; |
- // "id", "label", "currencyCode", and "value" should be non-empty. |
- if (item == null || TextUtils.isEmpty(item.label) || item.amount == null |
- || TextUtils.isEmpty(item.amount.currencyCode) |
- || TextUtils.isEmpty(item.amount.value)) { |
- return null; |
- } |
+ mUiShoppingCart = new ShoppingCart(uiTotal, uiLineItems); |
+ mRawTotal = details.total; |
+ mRawLineItems = Arrays.asList(details.displayItems); |
+ |
+ mUiShippingOptions = getValidatedShippingOptions(details.shippingOptions, totalCurrency, |
+ formatter, mRawShippingOptions, mUiShippingOptions); |
+ if (mUiShippingOptions == null) { |
+ disconnectFromClientWithDebugMessage("Invalid shipping options"); |
+ return false; |
} |
- CurrencyStringFormatter formatter = new CurrencyStringFormatter( |
- details.displayItems[0].amount.currencyCode, Locale.getDefault()); |
+ mRawShippingOptions = Arrays.asList(details.shippingOptions); |
- // Currency codes should be in correct format. |
- if (!formatter.isValidAmountCurrencyCode(details.displayItems[0].amount.currencyCode)) { |
- return null; |
- } |
+ return true; |
+ } |
+ |
+ /** |
+ * Returns true if all fields in the payment item are non-null and non-empty. |
+ * |
+ * @param item The payment item to examine. |
+ * @return True if all fields are present and non-empty. |
+ */ |
+ private static boolean hasAllPaymentItemFields(PaymentItem item) { |
+ // "label", "currencyCode", and "value" should be non-empty. |
+ return item != null && !TextUtils.isEmpty(item.label) && item.amount != null |
+ && !TextUtils.isEmpty(item.amount.currencyCode) |
+ && !TextUtils.isEmpty(item.amount.value); |
+ } |
+ |
+ /** |
+ * Validates a list of payment items and returns their parsed representation or null if invalid. |
+ * |
+ * @param items The payment items to parse and validate. |
+ * @param totalCurrency The currency code for the total amount of payment. |
+ * @param formatter A formatter and validator for the currency amount value. |
+ * @return A list of valid line items or null if invalid. |
+ */ |
+ private static List<LineItem> getValidatedLineItems( |
+ PaymentItem[] items, String totalCurrency, CurrencyStringFormatter formatter) { |
+ // Line items are optional. |
+ if (items == null) return new ArrayList<LineItem>(); |
+ |
+ List<LineItem> result = new ArrayList<>(items.length); |
+ for (int i = 0; i < items.length; i++) { |
+ PaymentItem item = items[i]; |
- List<LineItem> result = new ArrayList<>(details.displayItems.length); |
- for (int i = 0; i < details.displayItems.length; i++) { |
- PaymentItem item = details.displayItems[i]; |
+ if (!hasAllPaymentItemFields(item)) return null; |
// All currency codes must match. |
- if (!item.amount.currencyCode.equals(details.displayItems[0].amount.currencyCode)) { |
- return null; |
- } |
+ if (!item.amount.currencyCode.equals(totalCurrency)) return null; |
// Value should be in correct format. |
if (!formatter.isValidAmountValue(item.amount.value)) return null; |
- result.add(new LineItem(item.label, |
- i == details.displayItems.length - 1 ? item.amount.currencyCode : "", |
- formatter.format(item.amount.value))); |
+ result.add(new LineItem(item.label, "", formatter.format(item.amount.value))); |
} |
return result; |
} |
- private SectionInformation getValidatedShippingOptions( |
- String itemsCurrencyCode, PaymentDetails details) { |
+ /** |
+ * Validates a list of shipping options and returns their parsed representation or null if |
+ * invalid. Preserves the selected shipping option by comparing the raw options to the previous |
+ * raw options and returning the previous UI options if the raw versions are the same. |
+ * |
+ * @param options The raw shipping options to parse and validate. |
+ * @param totalCurrency The currency code for the total amount of payment. |
+ * @param formatter A formatter and validator for the currency amount value. |
+ * @param previousRawOptions The raw previous shipping options that have been parsed and |
+ * validated. Can be null. |
+ * @param previousUiOptions The UI representation of the previous shipping options. |
+ * @return The UI representation of the shipping options or null if invalid. |
+ */ |
+ private static SectionInformation getValidatedShippingOptions(ShippingOption[] options, |
+ String totalCurrency, CurrencyStringFormatter formatter, |
+ List<ShippingOption> previousRawOptions, SectionInformation previousUiOptions) { |
// Shipping options are optional. |
- if (details.shippingOptions == null || details.shippingOptions.length == 0) { |
+ if (options == null || options.length == 0) { |
return new SectionInformation(PaymentRequestUI.TYPE_SHIPPING_OPTIONS); |
} |
- CurrencyStringFormatter formatter = |
- new CurrencyStringFormatter(itemsCurrencyCode, Locale.getDefault()); |
- |
- for (int i = 0; i < details.shippingOptions.length; i++) { |
- ShippingOption option = details.shippingOptions[i]; |
+ for (int i = 0; i < options.length; i++) { |
+ ShippingOption option = options[i]; |
// Each "id", "label", "currencyCode", and "value" should be non-empty. |
// Each "value" should be a valid amount value. |
- // Each "currencyCode" should match the line items' currency codes. |
+ // Each "currencyCode" should match the total currency code. |
if (option == null || TextUtils.isEmpty(option.id) || TextUtils.isEmpty(option.label) |
|| option.amount == null || TextUtils.isEmpty(option.amount.currencyCode) |
|| TextUtils.isEmpty(option.amount.value) |
- || !itemsCurrencyCode.equals(option.amount.currencyCode) |
+ || !totalCurrency.equals(option.amount.currencyCode) |
|| !formatter.isValidAmountValue(option.amount.value)) { |
return null; |
} |
} |
- boolean isSameAsCurrentOptions = true; |
- if (mShippingOptions == null || mShippingOptions.size() != details.shippingOptions.length) { |
- isSameAsCurrentOptions = false; |
+ boolean isSameAsPreviousOptions = true; |
+ if (previousRawOptions == null || previousRawOptions.size() != options.length) { |
+ isSameAsPreviousOptions = false; |
} else { |
- for (int i = 0; i < details.shippingOptions.length; i++) { |
- ShippingOption newOption = details.shippingOptions[i]; |
- ShippingOption currentOption = mShippingOptions.get(i); |
- if (!newOption.id.equals(currentOption.id) |
- || !newOption.label.equals(currentOption.label) |
- || !newOption.amount.currencyCode.equals(currentOption.amount.currencyCode) |
- || !newOption.amount.value.equals(currentOption.amount.value)) { |
- isSameAsCurrentOptions = false; |
+ for (int i = 0; i < options.length; i++) { |
+ ShippingOption newOption = options[i]; |
+ ShippingOption previousOption = previousRawOptions.get(i); |
+ if (!newOption.id.equals(previousOption.id) |
+ || !newOption.label.equals(previousOption.label) |
+ || !newOption.amount.currencyCode.equals(previousOption.amount.currencyCode) |
+ || !newOption.amount.value.equals(previousOption.amount.value)) { |
+ isSameAsPreviousOptions = false; |
break; |
} |
} |
} |
- if (isSameAsCurrentOptions) return mShippingOptionsSection; |
+ if (isSameAsPreviousOptions) return previousUiOptions; |
List<PaymentOption> result = new ArrayList<>(); |
- for (int i = 0; i < details.shippingOptions.length; i++) { |
- ShippingOption option = details.shippingOptions[i]; |
+ for (int i = 0; i < options.length; i++) { |
+ ShippingOption option = options[i]; |
result.add(new PaymentOption(option.id, option.label, |
formatter.format(option.amount.value), PaymentOption.NO_ICON)); |
} |
@@ -419,18 +503,17 @@ public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie |
private void provideDefaultPaymentInformation() { |
mPaymentInformationCallback.onResult(new PaymentInformation( |
- mLineItems.get(mLineItems.size() - 1), mShippingAddressesSection.getSelectedItem(), |
- mShippingOptionsSection.getSelectedItem(), |
- mPaymentMethodsSection.getSelectedItem())); |
+ mUiShoppingCart.getTotal(), mShippingAddressesSection.getSelectedItem(), |
+ mUiShippingOptions.getSelectedItem(), mPaymentMethodsSection.getSelectedItem())); |
mPaymentInformationCallback = null; |
} |
@Override |
- public void getLineItems(final Callback<List<LineItem>> callback) { |
+ public void getShoppingCart(final Callback<ShoppingCart> callback) { |
mHandler.post(new Runnable() { |
@Override |
public void run() { |
- callback.onResult(mLineItems); |
+ callback.onResult(mUiShoppingCart); |
} |
}); |
} |
@@ -444,7 +527,7 @@ public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie |
if (optionType == PaymentRequestUI.TYPE_SHIPPING_ADDRESSES) { |
callback.onResult(mShippingAddressesSection); |
} else if (optionType == PaymentRequestUI.TYPE_SHIPPING_OPTIONS) { |
- callback.onResult(mShippingOptionsSection); |
+ callback.onResult(mUiShippingOptions); |
} else if (optionType == PaymentRequestUI.TYPE_PAYMENT_METHODS) { |
assert mPaymentMethodsSection != null; |
callback.onResult(mPaymentMethodsSection); |
@@ -465,7 +548,7 @@ public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie |
} |
} else if (optionType == PaymentRequestUI.TYPE_SHIPPING_OPTIONS) { |
// This may update the line items. |
- mShippingOptionsSection.setSelectedItem(option); |
+ mUiShippingOptions.setSelectedItem(option); |
mClient.onShippingOptionChange(option.getIdentifier()); |
} else if (optionType == PaymentRequestUI.TYPE_PAYMENT_METHODS) { |
assert option instanceof PaymentInstrument; |
@@ -490,7 +573,7 @@ public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie |
PaymentOption selectedShippingOption, PaymentOption selectedPaymentMethod) { |
assert selectedPaymentMethod instanceof PaymentInstrument; |
PaymentInstrument instrument = (PaymentInstrument) selectedPaymentMethod; |
- instrument.getDetails(mMerchantName, mOrigin, mDisplayItems, |
+ instrument.getDetails(mMerchantName, mOrigin, mRawTotal, mRawLineItems, |
mData.optJSONObject(instrument.getMethodName()), this); |
} |
@@ -580,7 +663,7 @@ public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie |
((AutofillAddress) selectedShippingAddress).toPaymentAddress(); |
} |
- PaymentOption selectedShippingOption = mShippingOptionsSection.getSelectedItem(); |
+ PaymentOption selectedShippingOption = mUiShippingOptions.getSelectedItem(); |
if (selectedShippingOption != null && selectedShippingOption.getIdentifier() != null) { |
response.shippingOptionId = selectedShippingOption.getIdentifier(); |
} |