Skip to content

Commit

Permalink
Show additional display items when instruments change.
Browse files Browse the repository at this point in the history
If the user selects a payment instrument that matches a modifier, then
the contents of the shopping cart should show the additional display
items from the modifier. For example, if the merchant provides
discounts for "visa", then selecting a "visa" type card should show the
"VISA discount $5.00" line item in the shopping cart.

This is accomplished by keeping track of the full modifier, instead of
just the total. This will also be useful in upcoming patch where the
modifier contents will be sent to the payment app.

This feature is behind chrome://flags#web-payments-modifiers flag.

This patch additionally removes the no longer used variable
"totalCurrency".

BUG=660926

Review-Url: https://codereview.chromium.org/2549313002
Cr-Commit-Position: refs/heads/master@{#437347}
  • Loading branch information
rsolomakhin authored and Commit bot committed Dec 8, 2016
1 parent 27f0507 commit a01e730
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.chromium.payments.mojom.CanMakePaymentQueryResult;
import org.chromium.payments.mojom.PaymentComplete;
import org.chromium.payments.mojom.PaymentDetails;
import org.chromium.payments.mojom.PaymentDetailsModifier;
import org.chromium.payments.mojom.PaymentErrorReason;
import org.chromium.payments.mojom.PaymentItem;
import org.chromium.payments.mojom.PaymentMethodData;
Expand Down Expand Up @@ -222,10 +223,11 @@ public void didSelectTab(Tab tab, TabSelectionType type, int lastId) {
private List<PaymentItem> mRawLineItems;

/**
* A mapping from method names to modified totals. Used to display modified totals for each
* payment instrument.
* A mapping from method names to modifiers, which include modified totals and additional line
* items. Used to display modified totals for each payment instrument, modified total in order
* summary, and additional line items in order summary.
*/
private Map<String, PaymentItem> mModifiedTotals;
private Map<String, PaymentDetailsModifier> mModifiers;

/**
* The UI model of the shopping cart, including the total. Each item includes a label and a
Expand Down Expand Up @@ -390,9 +392,9 @@ public void init(PaymentRequestClient client, PaymentMethodData[] methodData,
addresses.get(firstCompleteAddressIndex).setShippingAddressLabelWithoutCountry();
}

mShippingAddressesSection =
new SectionInformation(PaymentRequestUI.TYPE_SHIPPING_ADDRESSES,
firstCompleteAddressIndex, addresses);
mShippingAddressesSection = new SectionInformation(
PaymentRequestUI.TYPE_SHIPPING_ADDRESSES,
firstCompleteAddressIndex, addresses);
}

if (requestPayerName || requestPayerPhone || requestPayerEmail) {
Expand Down Expand Up @@ -420,8 +422,8 @@ public void init(PaymentRequestClient client, PaymentMethodData[] methodData,
if (!uniqueContactInfos.contains(uniqueContactInfo)) {
uniqueContactInfos.add(uniqueContactInfo);

boolean isComplete =
mContactEditor.isContactInformationComplete(name, phone, email);
boolean isComplete = mContactEditor.isContactInformationComplete(name,
phone, email);
contacts.add(new AutofillContact(profile, name, phone, email, isComplete));
}
}
Expand Down Expand Up @@ -484,7 +486,7 @@ public void show() {
setIsShowing(true);
if (disconnectIfNoPaymentMethodsSupported()) return;

// Catch any time the user switches tabs. Because the dialog is modal, a user shouldn't be
// Catch any time the user switches tabs. Because the dialog is modal, a user shouldn't be
// allowed to switch tabs, which can happen if the user receives an external Intent.
mContext.getTabModelSelector().addObserver(mSelectorObserver);
mContext.getCurrentTabModel().addObserver(mTabModelObserver);
Expand Down Expand Up @@ -524,8 +526,8 @@ private void getMatchingPaymentInstruments() {
Map<PaymentApp, Map<String, PaymentMethodData>> queryApps = new ArrayMap<>();
for (int i = 0; i < mApps.size(); i++) {
PaymentApp app = mApps.get(i);
Map<String, PaymentMethodData> appMethods =
filterMerchantMethodData(mMethodData, app.getAppMethodNames());
Map<String, PaymentMethodData> appMethods = filterMerchantMethodData(mMethodData,
app.getAppMethodNames());
if (appMethods == null || !app.supportsMethodsAndData(appMethods)) {
mPendingApps.remove(app);
} else {
Expand Down Expand Up @@ -605,32 +607,30 @@ private boolean parseAndValidateDetailsOrDisconnectFromClient(PaymentDetails det
return false;
}

String totalCurrency = details.total.amount.currency;
if (mFormatter == null) {
mFormatter = new CurrencyStringFormatter(totalCurrency, Locale.getDefault());
mFormatter = new CurrencyStringFormatter(details.total.amount.currency,
Locale.getDefault());
}

// Total is never pending.
LineItem uiTotal = new LineItem(
details.total.label, mFormatter.getFormattedCurrencyCode(),
mFormatter.format(details.total.amount.value), /* isPending */ false);

List<LineItem> uiLineItems = getLineItems(details.displayItems, totalCurrency, mFormatter);
List<LineItem> uiLineItems = getLineItems(details.displayItems, mFormatter);

mUiShoppingCart = new ShoppingCart(uiTotal, uiLineItems);
mRawTotal = details.total;
mRawLineItems = Collections.unmodifiableList(Arrays.asList(details.displayItems));

mUiShippingOptions = getShippingOptions(details.shippingOptions, totalCurrency, mFormatter);
mUiShippingOptions = getShippingOptions(details.shippingOptions, mFormatter);

for (int i = 0; i < details.modifiers.length; i++) {
PaymentItem total = details.modifiers[i].total;
String[] methods = details.modifiers[i].methodData.supportedMethods;
if (total != null) {
for (int j = 0; j < methods.length; j++) {
if (mModifiedTotals == null) mModifiedTotals = new ArrayMap<>();
mModifiedTotals.put(methods[j], total);
}
PaymentDetailsModifier modifier = details.modifiers[i];
String[] methods = modifier.methodData.supportedMethods;
for (int j = 0; j < methods.length; j++) {
if (mModifiers == null) mModifiers = new ArrayMap<>();
mModifiers.put(methods[j], modifier);
}
}

Expand All @@ -639,63 +639,54 @@ private boolean parseAndValidateDetailsOrDisconnectFromClient(PaymentDetails det
return true;
}

/** Updates the modified totals for payment instruments. */
/** Updates the modifiers for payment instruments and order summary. */
private void updateInstrumentModifiedTotals() {
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.WEB_PAYMENTS_MODIFIERS)) return;
if (mModifiedTotals == null) return;
if (mModifiers == null) return;
if (mPaymentMethodsSection == null) return;

for (int i = 0; i < mPaymentMethodsSection.getSize(); i++) {
PaymentInstrument instrument = (PaymentInstrument) mPaymentMethodsSection.getItem(i);
PaymentItem modifiedTotal = getModifiedTotal(instrument);
instrument.setModifiedTotal(
modifiedTotal == null ? null : mFormatter.format(modifiedTotal.amount.value));
PaymentDetailsModifier modifier = getModifier(instrument);
instrument.setModifiedTotal(modifier == null || modifier.total == null ? null
: mFormatter.format(modifier.total.amount.value));
}

updateOrderSummaryTotal((PaymentInstrument) mPaymentMethodsSection.getSelectedItem());
updateOrderSummary((PaymentInstrument) mPaymentMethodsSection.getSelectedItem());
}

private void updateOrderSummaryTotal(@Nullable PaymentInstrument instrument) {
/** Sets the modifier for the order summary based on the given instrument, if any. */
private void updateOrderSummary(@Nullable PaymentInstrument instrument) {
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.WEB_PAYMENTS_MODIFIERS)) return;

PaymentItem total = getModifiedTotal(instrument);
PaymentDetailsModifier modifier = getModifier(instrument);
PaymentItem total = modifier == null ? null : modifier.total;
if (total == null) total = mRawTotal;

mUiShoppingCart.setTotal(new LineItem(total.label, mFormatter.getFormattedCurrencyCode(),
mFormatter.format(total.amount.value), false /* isPending */));
mUiShoppingCart.setAdditionalContents(modifier == null ? null
: getLineItems(modifier.additionalDisplayItems, mFormatter));
mUI.updateOrderSummarySection(mUiShoppingCart);
}

@Nullable private PaymentItem getModifiedTotal(@Nullable PaymentInstrument instrument) {
/** @return The first modifier that matches the given instrument, or null. */
@Nullable private PaymentDetailsModifier getModifier(@Nullable PaymentInstrument instrument) {
if (instrument == null) return null;
Set<String> methodNames = instrument.getInstrumentMethodNames();
methodNames.retainAll(mModifiedTotals.keySet());
return methodNames.isEmpty() ? null : mModifiedTotals.get(methodNames.iterator().next());
}

/**
* 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", "currency", and "value" should be non-empty.
return item != null && !TextUtils.isEmpty(item.label) && item.amount != null
&& !TextUtils.isEmpty(item.amount.currency)
&& !TextUtils.isEmpty(item.amount.value);
methodNames.retainAll(mModifiers.keySet());
return methodNames.isEmpty() ? null : mModifiers.get(methodNames.iterator().next());
}

/**
* Converts a list of payment items and returns their parsed representation.
*
* @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.
* @param items The payment items to parse. Can be null.
* @param formatter A formatter for the currency amount value.
* @return A list of valid line items.
*/
private static List<LineItem> getLineItems(
PaymentItem[] items, String totalCurrency, CurrencyStringFormatter formatter) {
@Nullable PaymentItem[] items, CurrencyStringFormatter formatter) {
// Line items are optional.
if (items == null) return new ArrayList<>();

Expand All @@ -713,13 +704,12 @@ private static List<LineItem> getLineItems(
/**
* Converts a list of shipping options and returns their parsed representation.
*
* @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 options The raw shipping options to parse. Can be null.
* @param formatter A formatter for the currency amount value.
* @return The UI representation of the shipping options.
*/
private static SectionInformation getShippingOptions(PaymentShippingOption[] options,
String totalCurrency, CurrencyStringFormatter formatter) {
private static SectionInformation getShippingOptions(
@Nullable PaymentShippingOption[] options, CurrencyStringFormatter formatter) {
// Shipping options are optional.
if (options == null || options.length == 0) {
return new SectionInformation(PaymentRequestUI.TYPE_SHIPPING_OPTIONS);
Expand Down Expand Up @@ -844,7 +834,7 @@ public int onSectionOptionSelected(@PaymentRequestUI.DataType int optionType,
}
}

updateOrderSummaryTotal((PaymentInstrument) option);
updateOrderSummary((PaymentInstrument) option);
mPaymentMethodsSection.setSelectedItem(option);
}

Expand Down Expand Up @@ -990,8 +980,8 @@ public boolean onPayClicked(PaymentOption selectedShippingAddress,
PaymentInstrument instrument = (PaymentInstrument) selectedPaymentMethod;
mPaymentAppRunning = true;

PaymentOption selectedContact =
mContactSection != null ? mContactSection.getSelectedItem() : null;
PaymentOption selectedContact = mContactSection != null ? mContactSection.getSelectedItem()
: null;
mPaymentResponseHelper = new PaymentResponseHelper(
selectedShippingAddress, selectedShippingOption, selectedContact, this);

Expand Down Expand Up @@ -1095,9 +1085,8 @@ public void run() {
}

private void respondCanMakePaymentQuery(boolean response) {
mClient.onCanMakePayment(response
? CanMakePaymentQueryResult.CAN_MAKE_PAYMENT
: CanMakePaymentQueryResult.CANNOT_MAKE_PAYMENT);
mClient.onCanMakePayment(response ? CanMakePaymentQueryResult.CAN_MAKE_PAYMENT
: CanMakePaymentQueryResult.CANNOT_MAKE_PAYMENT);
if (sObserverForTest != null) {
sObserverForTest.onPaymentRequestServiceCanMakePaymentQueryResponded();
}
Expand Down Expand Up @@ -1138,8 +1127,8 @@ public void onInstrumentsReady(PaymentApp app, List<PaymentInstrument> instrumen
if (instruments != null) {
for (int i = 0; i < instruments.size(); i++) {
PaymentInstrument instrument = instruments.get(i);
Set<String> instrumentMethodNames =
new HashSet<>(instrument.getInstrumentMethodNames());
Set<String> instrumentMethodNames = new HashSet<>(
instrument.getInstrumentMethodNames());
instrumentMethodNames.retainAll(mMethodData.keySet());
if (!instrumentMethodNames.isEmpty()) {
addPendingInstrument(instrument);
Expand All @@ -1160,8 +1149,8 @@ public void onInstrumentsReady(PaymentApp app, List<PaymentInstrument> instrumen
for (int i = 0; i < mPendingAutofillInstruments.size(); ++i) {
assert mPendingAutofillInstruments.get(i) instanceof AutofillPaymentInstrument;

String countryCode = AutofillAddress.getCountryCode((
(AutofillPaymentInstrument) mPendingAutofillInstruments.get(
String countryCode = AutofillAddress
.getCountryCode(((AutofillPaymentInstrument) mPendingAutofillInstruments.get(
i)).getBillingAddress());
if (!uniqueCountryCodes.contains(countryCode)) {
uniqueCountryCodes.add(countryCode);
Expand Down Expand Up @@ -1214,22 +1203,22 @@ public void onInstrumentsReady(PaymentApp app, List<PaymentInstrument> instrumen
*/
private boolean disconnectIfNoPaymentMethodsSupported() {
boolean waitingForPaymentApps = !mPendingApps.isEmpty() || !mPendingInstruments.isEmpty();
boolean foundPaymentMethods =
mPaymentMethodsSection != null && !mPaymentMethodsSection.isEmpty();
boolean foundPaymentMethods = mPaymentMethodsSection != null
&& !mPaymentMethodsSection.isEmpty();
boolean userCanAddCreditCard = mMerchantSupportsAutofillPaymentInstruments
&& !ChromeFeatureList.isEnabled(ChromeFeatureList.NO_CREDIT_CARD_ABORT);

if (!mArePaymentMethodsSupported
|| (getIsShowing() && !waitingForPaymentApps && !foundPaymentMethods
&& !userCanAddCreditCard)) {
&& !userCanAddCreditCard)) {
// All payment apps have responded, but none of them have instruments. It's possible to
// add credit cards, but the merchant does not support them either. The payment request
// must be rejected.
disconnectFromClientWithDebugMessage("Requested payment methods have no instruments",
PaymentErrorReason.NOT_SUPPORTED);
recordAbortReasonHistogram(mArePaymentMethodsSupported
? PaymentRequestMetrics.ABORT_REASON_NO_MATCHING_PAYMENT_METHOD
: PaymentRequestMetrics.ABORT_REASON_NO_SUPPORTED_PAYMENT_METHOD);
? PaymentRequestMetrics.ABORT_REASON_NO_MATCHING_PAYMENT_METHOD
: PaymentRequestMetrics.ABORT_REASON_NO_SUPPORTED_PAYMENT_METHOD);
if (sObserverForTest != null) sObserverForTest.onPaymentRequestServiceShowFailed();
return true;
}
Expand Down Expand Up @@ -1306,8 +1295,8 @@ public void onFocusChanged(
if (mShippingAddressesSection.getSelectedItem() == null) return;

assert mShippingAddressesSection.getSelectedItem() instanceof AutofillAddress;
AutofillAddress selectedAddress =
(AutofillAddress) mShippingAddressesSection.getSelectedItem();
AutofillAddress selectedAddress = (AutofillAddress) mShippingAddressesSection
.getSelectedItem();

// The label should only include the country if the view is focused.
if (willFocus) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,27 @@

package org.chromium.chrome.browser.payments.ui;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.annotation.Nullable;

/**
* The shopping cart contents and total.
*/
public class ShoppingCart {
private LineItem mTotal;
private List<LineItem> mContents;
@Nullable private List<LineItem> mContents;
@Nullable private List<LineItem> mAdditionalContents;

/**
* Builds the shopping cart UI data model.
*
* @param totalPrice The total price.
* @param contents The shopping cart contents. The breakdown of the total price. OK to be null.
*/
public ShoppingCart(LineItem totalPrice, List<LineItem> contents) {
public ShoppingCart(LineItem totalPrice, @Nullable List<LineItem> contents) {
mTotal = totalPrice;
mContents = contents;
}
Expand All @@ -43,20 +48,37 @@ public void setTotal(LineItem total) {
}

/**
* Returns the shopping cart items.
* Returns the shopping cart items, including both the original items and the additional items
* that vary depending on the selected payment instrument, e.g., debut card discounts.
*
* @return The shopping cart items. Can be null.
* @return The shopping cart items. Can be null. Should not be modified.
*/
public List<LineItem> getContents() {
return mContents;
@Nullable public List<LineItem> getContents() {
if (mContents == null && mAdditionalContents == null) return null;

List<LineItem> result = new ArrayList<>();
if (mContents != null) result.addAll(mContents);
if (mAdditionalContents != null) result.addAll(mAdditionalContents);

return Collections.unmodifiableList(result);
}

/**
* Updates the shopping cart items.
*
* @param contents The shopping cart items. Can be null.
*/
public void setContents(List<LineItem> contents) {
public void setContents(@Nullable List<LineItem> contents) {
mContents = contents;
}

/**
* Update the additional shopping cart items that vary depending on the selected payment
* instrument, e.g., debit card discounts.
*
* @param additionalContents The additional shopping cart items. Can be null.
*/
public void setAdditionalContents(@Nullable List<LineItem> additionalContents) {
mAdditionalContents = additionalContents;
}
}

0 comments on commit a01e730

Please sign in to comment.