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

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

Issue 2030193002: Add 'total' field to 'PaymentDetails'. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase, address comments, and add more tests Created 4 years, 6 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 side-by-side diff with in-line comments
Download patch
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();
}

Powered by Google App Engine
This is Rietveld 408576698