Commit 67278f4b authored by Sahel Sharify's avatar Sahel Sharify Committed by Commit Bot

[Payments] Enable payment handlers to declare supported delegations.

This cl is an android only change which allows service worker based
payment handlers on Android to declare their supported delegations
during registration.

The payment sheet is also changed to adaptively show/hide shipping/contact
details section depending on whether or not the user selected payment handler
will provide shipping address/payer's contact information. Whenever multiple
payment handlers exist, payment handlers which provide merchant requested
information are sorted before the rest.

With this change skipping the payment sheet to the payment handler
window when shipping/contact details are requested will be possible
as long as the payment handler can provide this information.

Bug: 984694
Change-Id: I5693a7bd60d7a65b0f9ff5d1c0734867240b57fb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1902077Reviewed-by: default avatarRouslan Solomakhin <rouslan@chromium.org>
Commit-Queue: Sahel Sharify <sahel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#714647}
parent b523a690
......@@ -316,6 +316,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/payments/CurrencyFormatterTest.java",
"javatests/src/org/chromium/chrome/browser/payments/MockPackageManagerDelegate.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentHandlerChangePaymentMethodTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentHandlerEnableDelegationsTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentManifestParserTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestAbortTest.java",
......
......@@ -140,6 +140,34 @@ public abstract class PaymentInstrument extends EditableOption {
return getInstrumentMethodNames().contains(method);
}
/**
* @return Whether the instrument can collect and return shipping address.
*/
public boolean handlesShippingAddress() {
return false;
}
/**
* @return Whether the instrument can collect and return payer's name.
*/
public boolean handlesPayerName() {
return false;
}
/**
* @return Whether the instrument can collect and return payer's email.
*/
public boolean handlesPayerEmail() {
return false;
}
/**
* @return Whether the instrument can collect and return payer's phone.
*/
public boolean handlesPayerPhone() {
return false;
}
/** @return The country code (or null if none) associated with this payment instrument. */
@Nullable
public String getCountryCode() {
......
......@@ -237,16 +237,25 @@ public class PaymentRequestImpl
private static final Comparator<Completable> COMPLETENESS_COMPARATOR =
(a, b) -> (compareCompletablesByCompleteness(b, a));
private boolean mRequestShipping;
private boolean mRequestPayerName;
private boolean mRequestPayerPhone;
private boolean mRequestPayerEmail;
/**
* Sorts the payment instruments by several rules:
* Rule 1: Non-autofill before autofill.
* Rule 2: Complete instruments before incomplete intsruments.
* Rule 3: Exact type matching instruments before non-exact type matching instruments.
* Rule 4: Preselectable instruments before non-preselectable instruments.
* Rule 5: Frequently and recently used instruments before rarely and non-recently used
* Rule 5: When shipping address is requested instruments which will handle shipping address
* before others.
* Rule 6: When payer's contact information is requested instruments which will handle more
* required contact fields (name, email, phone) come before others.
* Rule 7: Frequently and recently used instruments before rarely and non-recently used
* instruments.
*/
private static final Comparator<PaymentInstrument> PAYMENT_INSTRUMENT_COMPARATOR = (a, b) -> {
private final Comparator<PaymentInstrument> mPaymentInstrumentComparator = (a, b) -> {
// Payment apps (not autofill) first.
int autofill = (a.isAutofillInstrument() ? 1 : 0) - (b.isAutofillInstrument() ? 1 : 0);
if (autofill != 0) return autofill;
......@@ -268,6 +277,32 @@ public class PaymentRequestImpl
int canPreselect = (b.canPreselect() ? 1 : 0) - (a.canPreselect() ? 1 : 0);
if (canPreselect != 0) return canPreselect;
// Payment apps which handle shipping address before others.
if (mRequestShipping) {
int canHandleShipping =
(b.handlesShippingAddress() ? 1 : 0) - (a.handlesShippingAddress() ? 1 : 0);
if (canHandleShipping != 0) return canHandleShipping;
}
// Payment apps which handle more contact information fields come first.
int aSupportedContactDelegationsNum = 0;
int bSupportedContactDelegationsNum = 0;
if (mRequestPayerName) {
if (a.handlesPayerName()) aSupportedContactDelegationsNum++;
if (b.handlesPayerName()) bSupportedContactDelegationsNum++;
}
if (mRequestPayerEmail) {
if (a.handlesPayerEmail()) aSupportedContactDelegationsNum++;
if (b.handlesPayerEmail()) bSupportedContactDelegationsNum++;
}
if (mRequestPayerPhone) {
if (a.handlesPayerPhone()) aSupportedContactDelegationsNum++;
if (b.handlesPayerPhone()) bSupportedContactDelegationsNum++;
}
if (bSupportedContactDelegationsNum != aSupportedContactDelegationsNum) {
return bSupportedContactDelegationsNum - aSupportedContactDelegationsNum > 0 ? 1 : -1;
}
// More frequently and recently used instruments first.
return compareInstrumentsByFrecency(b, a);
};
......@@ -368,10 +403,6 @@ public class PaymentRequestImpl
private String mId;
private Map<String, PaymentMethodData> mMethodData;
private boolean mRequestShipping;
private boolean mRequestPayerName;
private boolean mRequestPayerPhone;
private boolean mRequestPayerEmail;
private int mShippingType;
private SectionInformation mShippingAddressesSection;
private ContactDetailsSection mContactSection;
......@@ -710,6 +741,12 @@ public class PaymentRequestImpl
/** @return Whether the UI was built. */
private boolean buildUI(ChromeActivity activity) {
// Payment methods section must be ready before building the rest of the UI. This is because
// shipping and contact sections (when requested by merchant) are populated depending on
// whether or not the selected payment instrument (if such exists) can provide the required
// information.
assert mPaymentMethodsSection != null;
assert activity != null;
// Catch any time the user switches tabs. Because the dialog is modal, a user shouldn't be
......@@ -739,19 +776,16 @@ public class PaymentRequestImpl
mOverviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver);
}
if (mRequestShipping && !mWaitForUpdatedDetails) {
if (shouldShowShippingSection() && !mWaitForUpdatedDetails) {
createShippingSection(activity, mAutofillProfiles);
}
if (mRequestPayerName || mRequestPayerPhone || mRequestPayerEmail) {
if (shouldShowContactSection()) {
mContactSection = new ContactDetailsSection(
activity, mAutofillProfiles, mContactEditor, mJourneyLogger);
}
mUI = new PaymentRequestUI(activity, this, mRequestShipping,
/* requestShippingOption= */ mRequestShipping,
mRequestPayerName || mRequestPayerPhone || mRequestPayerEmail,
mMerchantSupportsAutofillPaymentInstruments,
mUI = new PaymentRequestUI(activity, this, mMerchantSupportsAutofillPaymentInstruments,
!PaymentPreferencesUtil.isPaymentCompleteOnce(), mMerchantName, mTopLevelOrigin,
SecurityStateModel.getSecurityLevelForWebContents(mWebContents),
new ShippingStrings(mShippingType));
......@@ -1262,7 +1296,8 @@ public class PaymentRequestImpl
return;
}
if (mRequestShipping && (mUiShippingOptions.isEmpty() || !TextUtils.isEmpty(details.error))
if (shouldShowShippingSection()
&& (mUiShippingOptions.isEmpty() || !TextUtils.isEmpty(details.error))
&& mShippingAddressesSection.getSelectedItem() != null) {
mShippingAddressesSection.getSelectedItem().setInvalid();
mShippingAddressesSection.setSelectedItemIndex(SectionInformation.INVALID_SELECTION);
......@@ -1292,7 +1327,7 @@ public class PaymentRequestImpl
// Do not create shipping section When UI is not built yet. This happens when the show
// promise gets resolved before all instruments are ready.
if (mUI != null && mRequestShipping) {
if (mUI != null && shouldShowShippingSection()) {
createShippingSection(chromeActivity, mAutofillProfiles);
}
......@@ -1344,7 +1379,7 @@ public class PaymentRequestImpl
providePaymentInformation();
} else {
mUI.updateOrderSummarySection(mUiShoppingCart);
if (mRequestShipping) {
if (shouldShowShippingSection()) {
mUI.updateSection(PaymentRequestUI.DataType.SHIPPING_OPTIONS, mUiShippingOptions);
}
}
......@@ -1677,6 +1712,20 @@ public class PaymentRequestImpl
mPaymentInformationCallback = callback;
return PaymentRequestUI.SelectionResult.ASYNCHRONOUS_VALIDATION;
} else if (optionType == PaymentRequestUI.DataType.PAYMENT_METHODS) {
if (shouldShowShippingSection() && mShippingAddressesSection == null) {
ChromeActivity activity = ChromeActivity.fromWebContents(mWebContents);
assert activity != null;
createShippingSection(activity, mAutofillProfiles);
}
if (shouldShowContactSection() && mContactSection == null) {
ChromeActivity activity = ChromeActivity.fromWebContents(mWebContents);
assert activity != null;
mContactSection = new ContactDetailsSection(
activity, mAutofillProfiles, mContactEditor, mJourneyLogger);
}
mUI.selectedPaymentMethodUpdated(
new PaymentInformation(mUiShoppingCart, mShippingAddressesSection,
mUiShippingOptions, mContactSection, mPaymentMethodsSection));
PaymentInstrument paymentInstrument = (PaymentInstrument) option;
if (paymentInstrument instanceof AutofillPaymentInstrument) {
AutofillPaymentInstrument card = (AutofillPaymentInstrument) paymentInstrument;
......@@ -1746,6 +1795,38 @@ public class PaymentRequestImpl
return PaymentRequestUI.SelectionResult.NONE;
}
@Override
public boolean shouldShowShippingSection() {
if (!mRequestShipping) return false;
if (mPaymentMethodsSection == null) return true;
PaymentInstrument selectedInstrument =
(PaymentInstrument) mPaymentMethodsSection.getSelectedItem();
return selectedInstrument == null || !selectedInstrument.handlesShippingAddress();
}
@Override
public boolean shouldShowContactSection() {
PaymentInstrument selectedInstrument = (mPaymentMethodsSection == null)
? null
: (PaymentInstrument) mPaymentMethodsSection.getSelectedItem();
if (mRequestPayerName
&& (selectedInstrument == null || !selectedInstrument.handlesPayerName())) {
return true;
}
if (mRequestPayerPhone
&& (selectedInstrument == null || !selectedInstrument.handlesPayerPhone())) {
return true;
}
if (mRequestPayerEmail
&& (selectedInstrument == null || !selectedInstrument.handlesPayerEmail())) {
return true;
}
return false;
}
private void editAddress(final AutofillAddress toEdit) {
if (toEdit != null) {
// Log the edit of a shipping address.
......@@ -2045,7 +2126,7 @@ public class PaymentRequestImpl
activity.getResources().getString(R.string.payments_error_message));
}
if (mRequestShipping && hasShippingAddressError(errors.shippingAddress)) {
if (shouldShowShippingSection() && hasShippingAddressError(errors.shippingAddress)) {
mRetryQueue.add(() -> {
mAddressEditor.setAddressErrors(errors.shippingAddress);
AutofillAddress selectedAddress =
......@@ -2054,8 +2135,7 @@ public class PaymentRequestImpl
});
}
if ((mRequestPayerName || mRequestPayerPhone || mRequestPayerEmail)
&& hasPayerError(errors.payer)) {
if (shouldShowContactSection() && hasPayerError(errors.payer)) {
mRetryQueue.add(() -> {
mContactEditor.setPayerErrors(errors.payer);
AutofillContact selectedContact =
......@@ -2348,7 +2428,7 @@ public class PaymentRequestImpl
}
}
Collections.sort(mPendingInstruments, PAYMENT_INSTRUMENT_COMPARATOR);
Collections.sort(mPendingInstruments, mPaymentInstrumentComparator);
// Possibly pre-select the first instrument on the list.
int selection = !mPendingInstruments.isEmpty() && mPendingInstruments.get(0).canPreselect()
......
......@@ -45,6 +45,7 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
private final boolean mCanPreselect;
private final Set<String> mPreferredRelatedApplicationIds;
private final boolean mIsIncognito;
private final SupportedDelegations mSupportedDelegations;
// Below variables are used for installable service worker payment app specifically.
private final boolean mNeedsInstallation;
......@@ -100,6 +101,30 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
}
}
/**
* This class represents the supported delegations of a service worker based payment app.
*/
protected static class SupportedDelegations {
private final boolean mShippingAddress;
private final boolean mPayerName;
private final boolean mPayerPhone;
private final boolean mPayerEmail;
SupportedDelegations(boolean shippingAddress, boolean payerName, boolean payerPhone,
boolean payerEmail) {
mShippingAddress = shippingAddress;
mPayerName = payerName;
mPayerPhone = payerPhone;
mPayerEmail = payerEmail;
}
SupportedDelegations() {
mShippingAddress = false;
mPayerName = false;
mPayerPhone = false;
mPayerEmail = false;
}
}
/**
* Build a service worker payment app instance per origin.
*
......@@ -124,11 +149,13 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
* this payment app (only valid for basic-card payment
* method for now).
* @param preferredRelatedApplicationIds A set of preferred related application Ids.
* @param supportedDelegations Supported delegations of the payment app.
*/
public ServiceWorkerPaymentApp(WebContents webContents, long registrationId, URI scope,
@Nullable String name, @Nullable String userHint, String origin,
@Nullable BitmapDrawable icon, String[] methodNames, boolean explicitlyVerified,
Capabilities[] capabilities, String[] preferredRelatedApplicationIds) {
Capabilities[] capabilities, String[] preferredRelatedApplicationIds,
SupportedDelegations supportedDelegations) {
// Do not display duplicate information.
super(scope.toString(), TextUtils.isEmpty(name) ? origin : name, userHint,
TextUtils.isEmpty(name) ? null : origin, icon);
......@@ -152,6 +179,8 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
mPreferredRelatedApplicationIds = new HashSet<>();
Collections.addAll(mPreferredRelatedApplicationIds, preferredRelatedApplicationIds);
mSupportedDelegations = supportedDelegations;
ChromeActivity activity = ChromeActivity.fromWebContents(mWebContents);
mIsIncognito = activity != null && activity.getCurrentTabModel().isIncognito();
......@@ -175,10 +204,12 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
* @param icon The drawable icon of the payment app.
* @param methodName The supported method name.
* @param preferredRelatedApplicationIds A set of preferred related application Ids.
* @param supportedDelegations Supported delegations of the payment app.
*/
public ServiceWorkerPaymentApp(WebContents webContents, @Nullable String name, String origin,
URI swUri, URI scope, boolean useCache, @Nullable BitmapDrawable icon,
String methodName, String[] preferredRelatedApplicationIds) {
String methodName, String[] preferredRelatedApplicationIds,
SupportedDelegations supportedDelegations) {
// Do not display duplicate information.
super(scope.toString(), TextUtils.isEmpty(name) ? origin : name, null,
TextUtils.isEmpty(name) ? null : origin, icon);
......@@ -198,6 +229,8 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
mPreferredRelatedApplicationIds = new HashSet<>();
Collections.addAll(mPreferredRelatedApplicationIds, preferredRelatedApplicationIds);
mSupportedDelegations = supportedDelegations;
ChromeActivity activity = ChromeActivity.fromWebContents(mWebContents);
mIsIncognito = activity != null && activity.getCurrentTabModel().isIncognito();
......@@ -404,6 +437,26 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
return mCanPreselect;
}
@Override
public boolean handlesShippingAddress() {
return mSupportedDelegations.mShippingAddress;
}
@Override
public boolean handlesPayerName() {
return mSupportedDelegations.mPayerName;
}
@Override
public boolean handlesPayerEmail() {
return mSupportedDelegations.mPayerEmail;
}
@Override
public boolean handlesPayerPhone() {
return mSupportedDelegations.mPayerPhone;
}
@Override
public boolean isReadyForMicrotransaction() {
return true; // TODO(https://crbug.com/1000432): Implement microtransactions.
......
......@@ -353,6 +353,13 @@ public class ServiceWorkerPaymentAppBridge implements PaymentAppFactory.PaymentA
return new ServiceWorkerPaymentApp.Capabilities[count];
}
@CalledByNative
private static Object createSupportedDelegations(
boolean shippingAddress, boolean payerName, boolean payerPhone, boolean payerEmail) {
return new ServiceWorkerPaymentApp.SupportedDelegations(
shippingAddress, payerName, payerPhone, payerEmail);
}
@CalledByNative
private static void addCapabilities(Object[] capabilities, int index,
int[] supportedCardNetworks, int[] supportedCardTypes) {
......@@ -365,8 +372,8 @@ public class ServiceWorkerPaymentAppBridge implements PaymentAppFactory.PaymentA
private static void onPaymentAppCreated(long registrationId, String scope,
@Nullable String name, @Nullable String userHint, String origin, @Nullable Bitmap icon,
String[] methodNameArray, boolean explicitlyVerified, Object[] capabilities,
String[] preferredRelatedApplications, WebContents webContents,
PaymentAppFactory.PaymentAppCreatedCallback callback) {
String[] preferredRelatedApplications, Object supportedDelegations,
WebContents webContents, PaymentAppFactory.PaymentAppCreatedCallback callback) {
ThreadUtils.assertOnUiThread();
Context context = ChromeActivity.fromWebContents(webContents);
......@@ -380,15 +387,15 @@ public class ServiceWorkerPaymentAppBridge implements PaymentAppFactory.PaymentA
scopeUri, name, userHint, origin,
icon == null ? null : new BitmapDrawable(context.getResources(), icon),
methodNameArray, explicitlyVerified,
(ServiceWorkerPaymentApp.Capabilities[]) capabilities,
preferredRelatedApplications));
(ServiceWorkerPaymentApp.Capabilities[]) capabilities, preferredRelatedApplications,
(ServiceWorkerPaymentApp.SupportedDelegations) supportedDelegations));
}
@CalledByNative
private static void onInstallablePaymentAppCreated(@Nullable String name, String swUrl,
String scope, boolean useCache, @Nullable Bitmap icon, String methodName,
String[] preferredRelatedApplications, WebContents webContents,
PaymentAppFactory.PaymentAppCreatedCallback callback) {
String[] preferredRelatedApplications, Object supportedDelegations,
WebContents webContents, PaymentAppFactory.PaymentAppCreatedCallback callback) {
ThreadUtils.assertOnUiThread();
Context context = ChromeActivity.fromWebContents(webContents);
......@@ -406,7 +413,8 @@ public class ServiceWorkerPaymentAppBridge implements PaymentAppFactory.PaymentA
callback.onPaymentAppCreated(new ServiceWorkerPaymentApp(webContents, name,
scopeUri.getHost(), swUri, scopeUri, useCache,
icon == null ? null : new BitmapDrawable(context.getResources(), icon), methodName,
preferredRelatedApplications));
preferredRelatedApplications,
(ServiceWorkerPaymentApp.SupportedDelegations) supportedDelegations));
}
@CalledByNative
......
......@@ -194,6 +194,18 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View
* Called when the user clicks on 'Settings' to control card and address options.
*/
void onCardAndAddressSettingsClicked();
/**
* Returns true when shipping address is requested and the selected payment method cannot
* provide it.
*/
boolean shouldShowShippingSection();
/**
* Returns true when payer's contact details is requested and the selected payment method
* cannot provide it.
*/
boolean shouldShowContactSection();
}
/**
......@@ -267,9 +279,6 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View
private final Context mContext;
private final Client mClient;
private final boolean mRequestShipping;
private final boolean mRequestShippingOption;
private final boolean mRequestContactDetails;
private final boolean mShowDataSource;
private final DimmingDialog mDialog;
......@@ -319,10 +328,6 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View
*
* @param activity The activity on top of which the UI should be displayed.
* @param client The consumer of the PaymentRequest UI.
* @param requestShipping Whether the UI should show the shipping address selection.
* @param requestShippingOption Whether the UI should show the shipping option selection.
* @param requestContact Whether the UI should show the payer name, email address and
* phone number selection.
* @param canAddCards Whether the UI should show the [+ADD CARD] button. This can be
* false, for example, when the merchant does not accept credit
* cards, so there's no point in adding cards within PaymentRequest
......@@ -338,15 +343,11 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View
* @param securityLevel The security level of the page that invoked PaymentRequest.
* @param shippingStrings The string resource identifiers to use in the shipping sections.
*/
public PaymentRequestUI(Activity activity, Client client, boolean requestShipping,
boolean requestShippingOption, boolean requestContact, boolean canAddCards,
public PaymentRequestUI(Activity activity, Client client, boolean canAddCards,
boolean showDataSource, String title, String origin, int securityLevel,
ShippingStrings shippingStrings) {
mContext = activity;
mClient = client;
mRequestShipping = requestShipping;
mRequestShippingOption = requestShippingOption;
mRequestContactDetails = requestContact;
mShowDataSource = showDataSource;
mAnimatorTranslation = mContext.getResources().getDimensionPixelSize(
R.dimen.payments_ui_translation);
......@@ -372,13 +373,11 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View
public void onResult(PaymentInformation result) {
mIsClientCheckingSelection = false;
updateOrderSummarySection(result.getShoppingCart());
if (mRequestShipping) {
if (mClient.shouldShowShippingSection()) {
updateSection(DataType.SHIPPING_ADDRESSES, result.getShippingAddresses());
}
if (mRequestShippingOption) {
updateSection(DataType.SHIPPING_OPTIONS, result.getShippingOptions());
}
if (mRequestContactDetails) {
if (mClient.shouldShowContactSection()) {
updateSection(DataType.CONTACT_DETAILS, result.getContactDetails());
}
updateSection(DataType.PAYMENT_METHODS, result.getPaymentMethods());
......@@ -426,15 +425,12 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View
public void onResult(PaymentInformation result) {
updateOrderSummarySection(result.getShoppingCart());
if (mRequestShipping) {
if (mClient.shouldShowShippingSection()) {
updateSection(DataType.SHIPPING_ADDRESSES, result.getShippingAddresses());
}
if (mRequestShippingOption) {
updateSection(DataType.SHIPPING_OPTIONS, result.getShippingOptions());
}
if (mRequestContactDetails) {
if (mClient.shouldShowContactSection()) {
updateSection(DataType.CONTACT_DETAILS, result.getContactDetails());
}
......@@ -538,24 +534,37 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View
new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
if (mRequestShipping) {
mSectionSeparators.add(new SectionSeparator(mPaymentContainerLayout));
// The shipping breakout sections are only added if they are needed.
SectionSeparator shippingSectionSeparator = new SectionSeparator(mPaymentContainerLayout);
mSectionSeparators.add(shippingSectionSeparator);
mPaymentContainerLayout.addView(mShippingAddressSection,
new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
// The shipping breakout sections are visible only if they are needed.
if (!mClient.shouldShowShippingSection()) {
mShippingAddressSection.setVisibility(View.GONE);
shippingSectionSeparator.setVisibility(View.GONE);
}
if (!methodSectionOrderV2) {
mSectionSeparators.add(new SectionSeparator(mPaymentContainerLayout));
mPaymentContainerLayout.addView(mPaymentMethodSection,
new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
if (mRequestContactDetails) {
// Contact details are optional, depending on the merchant website.
mSectionSeparators.add(new SectionSeparator(mPaymentContainerLayout));
mPaymentContainerLayout.addView(mContactDetailsSection, new LinearLayout.LayoutParams(
SectionSeparator contactSectionSeparator = new SectionSeparator(mPaymentContainerLayout);
mSectionSeparators.add(contactSectionSeparator);
mPaymentContainerLayout.addView(mContactDetailsSection,
new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
// Contact details are optional, depending on the merchant website, and whether or not the
// selected payment app can provide them.
if (!mClient.shouldShowContactSection()) {
mContactDetailsSection.setVisibility(View.GONE);
contactSectionSeparator.setVisibility(View.GONE);
}
mRequestView.addOnLayoutChangeListener(new PeekingAnimator());
......@@ -662,9 +671,10 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View
}
/**
* Updates the UI to account for changes in payment information.
* Updates the UI to account for changes in different sections information.
*
* @param section The shipping options.
* @param whichSection The type of the updated section.
* @param section The updated section information.
*/
public void updateSection(@DataType int whichSection, SectionInformation section) {
if (whichSection == DataType.SHIPPING_ADDRESSES) {
......@@ -673,7 +683,7 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View
} else if (whichSection == DataType.SHIPPING_OPTIONS) {
mShippingOptionsSectionInformation = section;
mShippingOptionSection.update(section);
showShippingOptionSectionIfNecessary();
addShippingOptionSectionIfNecessary();
} else if (whichSection == DataType.CONTACT_DETAILS) {
mContactDetailsSectionInformation = section;
mContactDetailsSection.update(section);
......@@ -691,9 +701,9 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View
if (isFinishingEditItem) notifyReadyForInput();
}
// Only show shipping option section once there are shipping options.
private void showShippingOptionSectionIfNecessary() {
if (!mRequestShippingOption || mShippingOptionsSectionInformation.isEmpty()
// Only add shipping option section once there are shipping options.
private void addShippingOptionSectionIfNecessary() {
if (!mClient.shouldShowShippingSection() || mShippingOptionsSectionInformation.isEmpty()
|| mPaymentContainerLayout.indexOfChild(mShippingOptionSection) != -1) {
return;
}
......@@ -710,6 +720,66 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View
mPaymentContainerLayout.requestLayout();
}
/**
* Notifies the UI about the changes in selected payment method.
*
* @param paymentInformation The updated payment information.
*/
public void selectedPaymentMethodUpdated(PaymentInformation paymentInformation) {
if (mClient.shouldShowShippingSection()
&& mShippingAddressSection.getVisibility() == View.GONE) {
updateSection(DataType.SHIPPING_ADDRESSES, paymentInformation.getShippingAddresses());
updateSection(DataType.SHIPPING_OPTIONS, paymentInformation.getShippingOptions());
// Show shipping address section and its separator.
mShippingAddressSection.setVisibility(View.VISIBLE);
int addressSectionIndex = mPaymentContainerLayout.indexOfChild(mShippingAddressSection);
mPaymentContainerLayout.getChildAt(addressSectionIndex - 1).setVisibility(View.VISIBLE);
// Show shipping option section (if it exists) and its separator.
int shippingOptionSectionIndex =
mPaymentContainerLayout.indexOfChild(mShippingOptionSection);
if (shippingOptionSectionIndex != -1) {
mShippingOptionSection.setVisibility(View.VISIBLE);
mPaymentContainerLayout.getChildAt(shippingOptionSectionIndex - 1)
.setVisibility(View.VISIBLE);
}
} else if (!mClient.shouldShowShippingSection()
&& mShippingAddressSection.getVisibility() == View.VISIBLE) {
// Hide shipping address section and its separator.
mShippingAddressSection.setVisibility(View.GONE);
int addressSectionIndex = mPaymentContainerLayout.indexOfChild(mShippingAddressSection);
mPaymentContainerLayout.getChildAt(addressSectionIndex - 1).setVisibility(View.GONE);
// Hide shipping option section (if exists) and its separator.
int shippingOptionSectionIndex =
mPaymentContainerLayout.indexOfChild(mShippingOptionSection);
if (shippingOptionSectionIndex != -1) {
mShippingOptionSection.setVisibility(View.GONE);
mPaymentContainerLayout.getChildAt(shippingOptionSectionIndex - 1)
.setVisibility(View.GONE);
}
}
if (mClient.shouldShowContactSection()
&& mContactDetailsSection.getVisibility() == View.GONE) {
updateSection(DataType.CONTACT_DETAILS, paymentInformation.getContactDetails());
// Show contact details section and its separator.
mContactDetailsSection.setVisibility(View.VISIBLE);
int contactSectionIndex = mPaymentContainerLayout.indexOfChild(mContactDetailsSection);
mPaymentContainerLayout.getChildAt(contactSectionIndex - 1).setVisibility(View.VISIBLE);
} else if (!mClient.shouldShowContactSection()
&& mContactDetailsSection.getVisibility() == View.VISIBLE) {
// Hide contact details section and its separator.
mContactDetailsSection.setVisibility(View.GONE);
int contactSectionIndex = mPaymentContainerLayout.indexOfChild(mContactDetailsSection);
mPaymentContainerLayout.getChildAt(contactSectionIndex - 1).setVisibility(View.GONE);
}
mPaymentContainerLayout.requestLayout();
}
@Override
public void onEditableOptionChanged(
final PaymentRequestSection section, EditableOption option) {
......@@ -947,13 +1017,13 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View
}
private void updatePayButtonEnabled() {
boolean contactInfoOk = !mRequestContactDetails
boolean contactInfoOk = !mClient.shouldShowContactSection()
|| (mContactDetailsSectionInformation != null
&& mContactDetailsSectionInformation.getSelectedItem() != null);
boolean shippingInfoOk = !mRequestShipping
boolean shippingInfoOk = !mClient.shouldShowShippingSection()
|| (mShippingAddressSectionInformation != null
&& mShippingAddressSectionInformation.getSelectedItem() != null);
boolean shippingOptionInfoOk = !mRequestShippingOption
boolean shippingOptionInfoOk = !mClient.shouldShowShippingSection()
|| (mShippingOptionsSectionInformation != null
&& mShippingOptionsSectionInformation.getSelectedItem() != null);
mPayButton.setEnabled(contactInfoOk && shippingInfoOk && shippingOptionInfoOk
......@@ -1084,9 +1154,13 @@ public class PaymentRequestUI implements DialogInterface.OnDismissListener, View
private void updateSectionVisibility() {
startSectionResizeAnimation();
mOrderSummarySection.focusSection(mSelectedSection == mOrderSummarySection);
if (mClient.shouldShowShippingSection()) {
mShippingAddressSection.focusSection(mSelectedSection == mShippingAddressSection);
mShippingOptionSection.focusSection(mSelectedSection == mShippingOptionSection);
}
if (mClient.shouldShowContactSection()) {
mContactDetailsSection.focusSection(mSelectedSection == mContactDetailsSection);
}
mPaymentMethodSection.focusSection(mSelectedSection == mPaymentMethodSection);
updateSectionButtons();
}
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.payments;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.view.View;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ui.DisableAnimationsTestRule;
import org.chromium.content_public.browser.test.util.JavaScriptUtils;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.net.test.ServerCertificate;
/** An integration test for shipping address and payer's contact information delegation. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
PaymentRequestTestRule.ENABLE_EXPERIMENTAL_WEB_PLATFORM_FEATURES})
@MediumTest
public class PaymentHandlerEnableDelegationsTest {
// Disable animations to reduce flakiness.
@ClassRule
public static DisableAnimationsTestRule sNoAnimationsRule = new DisableAnimationsTestRule();
// Open a tab on the blank page first to initiate the native bindings required by the test
// server.
@Rule
public PaymentRequestTestRule mRule = new PaymentRequestTestRule("about:blank");
// Host the tests on https://127.0.0.1, because file:// URLs cannot have service workers.
private EmbeddedTestServer mServer;
@Before
public void setUp() throws Throwable {
mServer = EmbeddedTestServer.createAndStartHTTPSServer(
InstrumentationRegistry.getContext(), ServerCertificate.CERT_OK);
mRule.startMainActivityWithURL(
mServer.getURL("/components/test/data/payments/payment_handler.html"));
// Find the web contents where JavaScript will be executed and instrument the browser
// payment sheet.
mRule.openPage();
}
private void installPaymentHandlerWithDelegations(String delegations) throws Throwable {
Assert.assertEquals("\"success\"",
JavaScriptUtils.runJavascriptWithAsyncResult(
mRule.getActivity().getCurrentWebContents(),
"install().then(result => {domAutomationController.send(result);});"));
Assert.assertEquals("\"success\"",
JavaScriptUtils.runJavascriptWithAsyncResult(
mRule.getActivity().getCurrentWebContents(),
"enableDelegations(" + delegations
+ ").then(result => {domAutomationController.send(result);});"));
}
@After
public void tearDown() {
mServer.stopAndDestroyServer();
}
private void createPaymentRequestAndWaitFor(String paymentOptions, CallbackHelper helper)
throws Throwable {
int callCount = helper.getCallCount();
Assert.assertEquals("\"success\"",
mRule.runJavaScriptCodeInCurrentTab(
"paymentRequestWithOptions(" + paymentOptions + ");"));
helper.waitForCallback(callCount);
}
@Test
@Feature({"Payments"})
@MediumTest
public void testShippingDelegation() throws Throwable {
installPaymentHandlerWithDelegations("['shippingAddress']");
// The pay button should be enabled when shipping address is requested and the selected
// payment instrument can provide it.
createPaymentRequestAndWaitFor("{requestShipping: true}", mRule.getReadyToPay());
}
@Test
@Feature({"Payments"})
@MediumTest
public void testContactDelegation() throws Throwable {
installPaymentHandlerWithDelegations("['payerName', 'payerEmail', 'payerPhone']");
// The pay button should be enabled when payer's contact information is requested and the
// selected payment instrument can provide it.
createPaymentRequestAndWaitFor(
"{requestPayerName: true, requestPayerEmail: true, requestPayerPhone: true}",
mRule.getReadyToPay());
}
@Test
@Feature({"Payments"})
@MediumTest
public void testShippingAndContactInfoDelegation() throws Throwable {
installPaymentHandlerWithDelegations(
"['shippingAddress', 'payerName', 'payerEmail', 'payerPhone']");
// The pay button should be enabled when shipping address and payer's contact information
// are requested and the selected payment instrument can provide them.
createPaymentRequestAndWaitFor(
"{requestShipping: true, requestPayerName: true, requestPayerEmail: true,"
+ " requestPayerPhone: true}",
mRule.getReadyToPay());
}
@Test
@Feature({"Payments"})
@MediumTest
public void testPartialDelegationShippingNotSupported() throws Throwable {
installPaymentHandlerWithDelegations("['payerName', 'payerEmail', 'payerPhone']");
createPaymentRequestAndWaitFor(
"{requestShipping: true, requestPayerName: true, requestPayerEmail: true}",
mRule.getReadyForInput());
// Shipping section must exist in payment sheet since shipping address is requested and
// won't be provided by the selected payment handler.
Assert.assertEquals(View.VISIBLE,
mRule.getPaymentRequestUI().getShippingAddressSectionForTest().getVisibility());
}
@Test
@Feature({"Payments"})
@MediumTest
public void testPartialDelegationContactInfoNotSupported() throws Throwable {
installPaymentHandlerWithDelegations("['shippingAddress']");
createPaymentRequestAndWaitFor(
"{requestShipping: true, requestPayerName: true, requestPayerEmail: true}",
mRule.getReadyForInput());
// Contact section must exist in payment sheet since payer's name and email are requested
// and won't be provided by the selected payment handler.
Assert.assertEquals(View.VISIBLE,
mRule.getPaymentRequestUI().getContactDetailsSectionForTest().getVisibility());
}
}
......@@ -339,7 +339,8 @@ public class PaymentRequestPaymentAppAndBasicCardWithModifiersTest {
"https://bobpay.com" /* tertiarylabel */, icon /* icon */,
bobpayMethodNames /* methodNames */, true /* explicitlyVerified */,
bobpayCapabilities /* capabilities */,
new String[0] /* preferredRelatedApplicationIds */));
new String[0] /* preferredRelatedApplicationIds */,
new ServiceWorkerPaymentApp.SupportedDelegations()));
callback.onPaymentAppCreated(
new ServiceWorkerPaymentApp(webContents, 0 /* registrationId */,
UriUtils.parseUriFromString("https://alicepay.com") /* scope */,
......@@ -347,7 +348,8 @@ public class PaymentRequestPaymentAppAndBasicCardWithModifiersTest {
"https://alicepay.com" /* tertiarylabel */, icon /* icon */,
alicepayMethodNames /* methodNames */, true /* explicitlyVerified */,
alicepayCapabilities /* capabilities */,
new String[0] /* preferredRelatedApplicationIds */));
new String[0] /* preferredRelatedApplicationIds */,
new ServiceWorkerPaymentApp.SupportedDelegations()));
callback.onAllPaymentAppsCreated();
});
mPaymentRequestTestRule.triggerUIAndWait(
......
......@@ -46,16 +46,19 @@ public class PaymentRequestServiceWorkerPaymentAppTest {
"payment_request_bobpay_and_basic_card_with_modifier_optional_data_test.html");
/**
* Installs a mock service worker based payment app for testing.
* Installs a mock service worker based payment app with given supported delegations for
* testing.
*
* @param supportedMethodNames The supported payment methods of the mock payment app.
* @param capabilities The capabilities of the mocked payment app.
* @param withName Whether provide payment app name.
* @param name The name of the mocked payment app.
* @param withIcon Whether provide payment app icon.
* @param supportedDelegations The supported delegations of the mock payment app.
*/
private void installMockServiceWorkerPaymentApp(final String[] supportedMethodNames,
final ServiceWorkerPaymentApp.Capabilities[] capabilities, final boolean withName,
final boolean withIcon) {
final ServiceWorkerPaymentApp.Capabilities[] capabilities, final String name,
final boolean withIcon,
ServiceWorkerPaymentApp.SupportedDelegations supportedDelegations) {
PaymentAppFactory.getInstance().addAdditionalFactory(
(webContents, methodNames, mayCrawlUnused, callback) -> {
ChromeActivity activity = ChromeActivity.fromWebContents(webContents);
......@@ -66,16 +69,52 @@ public class PaymentRequestServiceWorkerPaymentAppTest {
: null;
callback.onPaymentAppCreated(new ServiceWorkerPaymentApp(webContents,
0 /* registrationId */,
UriUtils.parseUriFromString("https://bobpay.com") /* scope */,
withName ? "BobPay" : null /* name */, "test@bobpay.com" /* userHint */,
"https://bobpay.com" /* origin */, icon /* icon */,
supportedMethodNames /* methodNames */, true /* explicitlyVerified */,
capabilities /* capabilities */,
new String[0] /* preferredRelatedApplicationIds */));
UriUtils.parseUriFromString("https://bobpay.com") /* scope */, name,
"test@bobpay.com" /* userHint */, "https://bobpay.com" /* origin */,
icon /* icon */, supportedMethodNames /* methodNames */,
true /* explicitlyVerified */, capabilities /* capabilities */,
new String[0] /* preferredRelatedApplicationIds */,
supportedDelegations));
callback.onAllPaymentAppsCreated();
});
}
/**
* Installs a mock service worker based payment app with no supported delegations for testing.
*
* @param supportedMethodNames The supported payment methods of the mock payment app.
* @param capabilities The capabilities of the mocked payment app.
* @param withName Whether provide payment app name.
* @param withIcon Whether provide payment app icon.
*/
private void installMockServiceWorkerPaymentApp(final String[] supportedMethodNames,
final ServiceWorkerPaymentApp.Capabilities[] capabilities, final boolean withName,
final boolean withIcon) {
installMockServiceWorkerPaymentApp(supportedMethodNames, capabilities,
withName ? "BobPay" : null, withIcon,
new ServiceWorkerPaymentApp.SupportedDelegations());
}
/**
* Installs a mock service worker based payment app for bobpay with given supported delegations
* for testing.
*
* @param shippingAddress Whether or not the mock payment app provides shipping address.
* @param payerName Whether or not the mock payment app provides payer's name.
* @param payerPhone Whether or not the mock payment app provides payer's phone number.
* @param payerEmail Whether or not the mock payment app provides payer's email address.
* @param name The name of the mocked payment app.
*/
private void installMockServiceWorkerPaymentAppWithDelegations(final boolean shippingAddress,
final boolean payerName, final boolean payerPhone, final boolean payerEmail,
final String name) {
String[] supportedMethodNames = {"https://bobpay.xyz"};
installMockServiceWorkerPaymentApp(supportedMethodNames,
new ServiceWorkerPaymentApp.Capabilities[0], name, true /*withIcon*/,
new ServiceWorkerPaymentApp.SupportedDelegations(
shippingAddress, payerName, payerPhone, payerEmail));
}
@Test
@MediumTest
@Feature({"Payments"})
......@@ -405,4 +444,84 @@ public class PaymentRequestServiceWorkerPaymentAppTest {
mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
Assert.assertNull(mPaymentRequestTestRule.getSelectedPaymentInstrumentLabel());
}
@Test
@MediumTest
@Feature({"Payments"})
public void testPaymentAppProvidingShippingComesFirst() throws TimeoutException {
installMockServiceWorkerPaymentAppWithDelegations(false /*shippingAddress*/,
false /*payerName*/, false /*payerPhone*/, false /*payerEmail*/,
"noSupportedDelegation" /*name*/);
installMockServiceWorkerPaymentAppWithDelegations(true /*shippingAddress*/,
false /*payerName*/, false /*payerPhone*/, false /*payerEmail*/,
"shippingSupported" /*name */);
ServiceWorkerPaymentAppBridge.setCanMakePaymentForTesting(true);
mPaymentRequestTestRule.triggerUIAndWait(
"buy_with_shipping_requested", mPaymentRequestTestRule.getReadyForInput());
Assert.assertEquals(2, mPaymentRequestTestRule.getNumberOfPaymentInstruments());
// The payment app which provides shipping address must be preselected.
Assert.assertTrue(mPaymentRequestTestRule.getSelectedPaymentInstrumentLabel().contains(
"shippingSupported"));
}
@Test
@MediumTest
@Feature({"Payments"})
public void testPaymentAppProvidingContactComesFirst() throws TimeoutException {
installMockServiceWorkerPaymentAppWithDelegations(false /*shippingAddress*/,
false /*payerName*/, false /*payerPhone*/, false /*payerEmail*/,
"noSupportedDelegation" /*name*/);
installMockServiceWorkerPaymentAppWithDelegations(false /*shippingAddress*/,
true /*payerName*/, true /*payerPhone*/, true /*payerEmail*/,
"contactSupported" /*name */);
installMockServiceWorkerPaymentAppWithDelegations(false /*shippingAddress*/,
false /*payerName*/, false /*payerPhone*/, true /*payerEmail*/,
"emailOnlySupported" /*name */);
ServiceWorkerPaymentAppBridge.setCanMakePaymentForTesting(true);
mPaymentRequestTestRule.triggerUIAndWait(
"buy_with_contact_requested", mPaymentRequestTestRule.getReadyForInput());
Assert.assertEquals(3, mPaymentRequestTestRule.getNumberOfPaymentInstruments());
// The payment app which provides full contact details must be preselected.
Assert.assertTrue(mPaymentRequestTestRule.getSelectedPaymentInstrumentLabel().contains(
"contactSupported"));
// The payment app which partially provides the required contact details comes before the
// one that provides no contact information.
Assert.assertTrue(mPaymentRequestTestRule.getPaymentMethodSuggestionLabel(1).contains(
"emailOnlySupported"));
}
@Test
@MediumTest
@Feature({"Payments"})
public void testPaymentAppProvidingAllRequiredInfoComesFirst() throws TimeoutException {
installMockServiceWorkerPaymentAppWithDelegations(true /*shippingAddress*/,
false /*payerName*/, false /*payerPhone*/, false /*payerEmail*/,
"shippingSupported" /*name */);
installMockServiceWorkerPaymentAppWithDelegations(false /*shippingAddress*/,
true /*payerName*/, true /*payerPhone*/, true /*payerEmail*/,
"contactSupported" /*name */);
installMockServiceWorkerPaymentAppWithDelegations(true /*shippingAddress*/,
true /*payerName*/, true /*payerPhone*/, true /*payerEmail*/,
"shippingAndContactSupported" /*name*/);
ServiceWorkerPaymentAppBridge.setCanMakePaymentForTesting(true);
mPaymentRequestTestRule.triggerUIAndWait("buy_with_shipping_and_contact_requested",
mPaymentRequestTestRule.getReadyForInput());
Assert.assertEquals(3, mPaymentRequestTestRule.getNumberOfPaymentInstruments());
// The payment app which provides all required information must be preselected.
Assert.assertTrue(mPaymentRequestTestRule.getSelectedPaymentInstrumentLabel().contains(
"shippingAndContactSupported"));
// The payment app which provides shipping comes before the one which provides contact
// details when both required by merchant.
Assert.assertTrue(mPaymentRequestTestRule.getPaymentMethodSuggestionLabel(1).contains(
"shippingSupported"));
}
}
......@@ -971,6 +971,9 @@ public class PaymentRequestTestRule extends ChromeTabbedActivityTestRule
@Override
public void onPaymentRequestReadyForInput(PaymentRequestUI ui) {
ThreadUtils.assertOnUiThread();
// This happens when the payment request is created by a direct js function call rather than
// calling the js function via triggerUIAndWait() which sets the mUI.
if (mUI == null) mUI = ui;
mReadyForInput.notifyCalled(ui);
}
......@@ -995,6 +998,9 @@ public class PaymentRequestTestRule extends ChromeTabbedActivityTestRule
@Override
public void onPaymentRequestReadyToPay(PaymentRequestUI ui) {
ThreadUtils.assertOnUiThread();
// This happens when the payment request is created by a direct js function call rather than
// calling the js function via triggerUIAndWait() which sets the mUI.
if (mUI == null) mUI = ui;
mReadyToPay.notifyCalled(ui);
}
......
......@@ -95,6 +95,13 @@ void OnGotAllPaymentApps(
env, app_info.second->capabilities[i].supported_card_types));
}
base::android::ScopedJavaLocalRef<jobject> jsupported_delegations =
Java_ServiceWorkerPaymentAppBridge_createSupportedDelegations(
env, app_info.second->supported_delegations.shipping_address,
app_info.second->supported_delegations.payer_name,
app_info.second->supported_delegations.payer_phone,
app_info.second->supported_delegations.payer_email);
// TODO(crbug.com/846077): Find a proper way to make use of user hint.
Java_ServiceWorkerPaymentAppBridge_onPaymentAppCreated(
env, app_info.second->registration_id,
......@@ -109,10 +116,17 @@ void OnGotAllPaymentApps(
ToJavaArrayOfStrings(env, app_info.second->enabled_methods),
app_info.second->has_explicitly_verified_methods, jcapabilities,
ToJavaArrayOfStrings(env, preferred_related_application_ids),
jweb_contents, jcallback);
jsupported_delegations, jweb_contents, jcallback);
}
for (const auto& installable_app : installable_apps) {
base::android::ScopedJavaLocalRef<jobject> jsupported_delegations =
Java_ServiceWorkerPaymentAppBridge_createSupportedDelegations(
env, installable_app.second->supported_delegations.shipping_address,
installable_app.second->supported_delegations.payer_name,
installable_app.second->supported_delegations.payer_phone,
installable_app.second->supported_delegations.payer_email);
Java_ServiceWorkerPaymentAppBridge_onInstallablePaymentAppCreated(
env, ConvertUTF8ToJavaString(env, installable_app.second->name),
ConvertUTF8ToJavaString(env, installable_app.second->sw_js_url),
......@@ -123,7 +137,7 @@ void OnGotAllPaymentApps(
: gfx::ConvertToJavaBitmap(installable_app.second->icon.get()),
ConvertUTF8ToJavaString(env, installable_app.first.spec()),
ToJavaArrayOfStrings(env, installable_app.second->preferred_app_ids),
jweb_contents, jcallback);
jsupported_delegations, jweb_contents, jcallback);
}
Java_ServiceWorkerPaymentAppBridge_onAllPaymentAppsCreated(env, jcallback);
......
......@@ -272,3 +272,64 @@ function buyWithVisaModifier() { // eslint-disable-line no-unused-vars
print('exception thrown<br>' + error.message);
}
}
/**
* Creates a payment request with required information and calls request.show()
* to launch PaymentRequest UI. To ensure that UI gets shown two payment methods
* are supported: One url-based and one 'basic-card'.
* @param {Object} options The list of requested paymentOptions.
* @return {string} The 'success' or error message.
*/
function paymentRequestWithOptions(options) { // eslint-disable-line no-unused-vars, max-len
try {
const request = new PaymentRequest(
[
{supportedMethods: 'https://bobpay.xyz'},
{supportedMethods: 'basic-card'},
],
{
total: {label: 'Total', amount: {currency: 'USD', value: '5.00'}},
shippingOptions: [{
id: 'freeShippingOption',
label: 'Free global shipping',
amount: {
currency: 'USD',
value: '0',
},
selected: true,
}],
},
options);
request.show();
return 'success';
} catch (e) {
return e.toString();
}
}
/**
* Launches the PaymentRequest UI with 'basic-card' and 'bobpay' as payment
* methods and shipping address requested.
*/
function buyWithShippingRequested() { // eslint-disable-line no-unused-vars
paymentRequestWithOptions({requestShipping: true, requestPayerName: false,
requestPayerEmail: false, requestPayerPhone: false});
}
/**
* Launches the PaymentRequest UI with 'basic-card' and 'bobpay' as payment
* methods and payer's contact details requested.
*/
function buyWithContactRequested() { // eslint-disable-line no-unused-vars
paymentRequestWithOptions({requestPayerName: true, requestPayerEmail: true,
requestPayerPhone: true});
}
/**
* Launches the PaymentRequest UI with 'basic-card' and 'bobpay' as payment
* methods and both shipping address and payer's contact details requested.
*/
function buyWithShippingAndContactRequested() { // eslint-disable-line no-unused-vars, max-len
paymentRequestWithOptions({requestShipping: true, requestPayerName: true,
requestPayerEmail: true, requestPayerPhone: true});
}
......@@ -18,6 +18,9 @@ found in the LICENSE file.
<button onclick="buyWithVisaDebitModifier()" id="buy_with_visa_debit_modifier">Visa debit card modifier test</button>
<button onclick="buyWithCreditModifier()" id="buy_with_credit_modifier">Credit card modifier test</button>
<button onclick="buyWithVisaModifier()" id="buy_with_visa_modifier">Visa card modifier test</button>
<button onclick="buyWithShippingRequested()" id="buy_with_shipping_requested">Shipping requested test</button>
<button onclick="buyWithContactRequested()" id="buy_with_contact_requested">Contact details requested test</button>
<button onclick="buyWithShippingAndContactRequested()" id="buy_with_shipping_and_contact_requested">Shipping and contact details requested test</button>
<pre id="result"></pre>
<script src="util.js"></script>
<script src="bobpay_and_basic_card_with_modifier_optional_data.js"></script>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment