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 = [ ...@@ -316,6 +316,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/payments/CurrencyFormatterTest.java", "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/MockPackageManagerDelegate.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentHandlerChangePaymentMethodTest.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/PaymentManifestDownloaderTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentManifestParserTest.java", "javatests/src/org/chromium/chrome/browser/payments/PaymentManifestParserTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestAbortTest.java", "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestAbortTest.java",
......
...@@ -140,6 +140,34 @@ public abstract class PaymentInstrument extends EditableOption { ...@@ -140,6 +140,34 @@ public abstract class PaymentInstrument extends EditableOption {
return getInstrumentMethodNames().contains(method); 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. */ /** @return The country code (or null if none) associated with this payment instrument. */
@Nullable @Nullable
public String getCountryCode() { public String getCountryCode() {
......
...@@ -45,6 +45,7 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen ...@@ -45,6 +45,7 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
private final boolean mCanPreselect; private final boolean mCanPreselect;
private final Set<String> mPreferredRelatedApplicationIds; private final Set<String> mPreferredRelatedApplicationIds;
private final boolean mIsIncognito; private final boolean mIsIncognito;
private final SupportedDelegations mSupportedDelegations;
// Below variables are used for installable service worker payment app specifically. // Below variables are used for installable service worker payment app specifically.
private final boolean mNeedsInstallation; private final boolean mNeedsInstallation;
...@@ -100,6 +101,30 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen ...@@ -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. * Build a service worker payment app instance per origin.
* *
...@@ -124,11 +149,13 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen ...@@ -124,11 +149,13 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
* this payment app (only valid for basic-card payment * this payment app (only valid for basic-card payment
* method for now). * method for now).
* @param preferredRelatedApplicationIds A set of preferred related application Ids. * @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, public ServiceWorkerPaymentApp(WebContents webContents, long registrationId, URI scope,
@Nullable String name, @Nullable String userHint, String origin, @Nullable String name, @Nullable String userHint, String origin,
@Nullable BitmapDrawable icon, String[] methodNames, boolean explicitlyVerified, @Nullable BitmapDrawable icon, String[] methodNames, boolean explicitlyVerified,
Capabilities[] capabilities, String[] preferredRelatedApplicationIds) { Capabilities[] capabilities, String[] preferredRelatedApplicationIds,
SupportedDelegations supportedDelegations) {
// Do not display duplicate information. // Do not display duplicate information.
super(scope.toString(), TextUtils.isEmpty(name) ? origin : name, userHint, super(scope.toString(), TextUtils.isEmpty(name) ? origin : name, userHint,
TextUtils.isEmpty(name) ? null : origin, icon); TextUtils.isEmpty(name) ? null : origin, icon);
...@@ -152,6 +179,8 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen ...@@ -152,6 +179,8 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
mPreferredRelatedApplicationIds = new HashSet<>(); mPreferredRelatedApplicationIds = new HashSet<>();
Collections.addAll(mPreferredRelatedApplicationIds, preferredRelatedApplicationIds); Collections.addAll(mPreferredRelatedApplicationIds, preferredRelatedApplicationIds);
mSupportedDelegations = supportedDelegations;
ChromeActivity activity = ChromeActivity.fromWebContents(mWebContents); ChromeActivity activity = ChromeActivity.fromWebContents(mWebContents);
mIsIncognito = activity != null && activity.getCurrentTabModel().isIncognito(); mIsIncognito = activity != null && activity.getCurrentTabModel().isIncognito();
...@@ -175,10 +204,12 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen ...@@ -175,10 +204,12 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
* @param icon The drawable icon of the payment app. * @param icon The drawable icon of the payment app.
* @param methodName The supported method name. * @param methodName The supported method name.
* @param preferredRelatedApplicationIds A set of preferred related application Ids. * @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, public ServiceWorkerPaymentApp(WebContents webContents, @Nullable String name, String origin,
URI swUri, URI scope, boolean useCache, @Nullable BitmapDrawable icon, URI swUri, URI scope, boolean useCache, @Nullable BitmapDrawable icon,
String methodName, String[] preferredRelatedApplicationIds) { String methodName, String[] preferredRelatedApplicationIds,
SupportedDelegations supportedDelegations) {
// Do not display duplicate information. // Do not display duplicate information.
super(scope.toString(), TextUtils.isEmpty(name) ? origin : name, null, super(scope.toString(), TextUtils.isEmpty(name) ? origin : name, null,
TextUtils.isEmpty(name) ? null : origin, icon); TextUtils.isEmpty(name) ? null : origin, icon);
...@@ -198,6 +229,8 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen ...@@ -198,6 +229,8 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
mPreferredRelatedApplicationIds = new HashSet<>(); mPreferredRelatedApplicationIds = new HashSet<>();
Collections.addAll(mPreferredRelatedApplicationIds, preferredRelatedApplicationIds); Collections.addAll(mPreferredRelatedApplicationIds, preferredRelatedApplicationIds);
mSupportedDelegations = supportedDelegations;
ChromeActivity activity = ChromeActivity.fromWebContents(mWebContents); ChromeActivity activity = ChromeActivity.fromWebContents(mWebContents);
mIsIncognito = activity != null && activity.getCurrentTabModel().isIncognito(); mIsIncognito = activity != null && activity.getCurrentTabModel().isIncognito();
...@@ -404,6 +437,26 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen ...@@ -404,6 +437,26 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
return mCanPreselect; 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 @Override
public boolean isReadyForMicrotransaction() { public boolean isReadyForMicrotransaction() {
return true; // TODO(https://crbug.com/1000432): Implement microtransactions. return true; // TODO(https://crbug.com/1000432): Implement microtransactions.
......
...@@ -353,6 +353,13 @@ public class ServiceWorkerPaymentAppBridge implements PaymentAppFactory.PaymentA ...@@ -353,6 +353,13 @@ public class ServiceWorkerPaymentAppBridge implements PaymentAppFactory.PaymentA
return new ServiceWorkerPaymentApp.Capabilities[count]; 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 @CalledByNative
private static void addCapabilities(Object[] capabilities, int index, private static void addCapabilities(Object[] capabilities, int index,
int[] supportedCardNetworks, int[] supportedCardTypes) { int[] supportedCardNetworks, int[] supportedCardTypes) {
...@@ -365,8 +372,8 @@ public class ServiceWorkerPaymentAppBridge implements PaymentAppFactory.PaymentA ...@@ -365,8 +372,8 @@ public class ServiceWorkerPaymentAppBridge implements PaymentAppFactory.PaymentA
private static void onPaymentAppCreated(long registrationId, String scope, private static void onPaymentAppCreated(long registrationId, String scope,
@Nullable String name, @Nullable String userHint, String origin, @Nullable Bitmap icon, @Nullable String name, @Nullable String userHint, String origin, @Nullable Bitmap icon,
String[] methodNameArray, boolean explicitlyVerified, Object[] capabilities, String[] methodNameArray, boolean explicitlyVerified, Object[] capabilities,
String[] preferredRelatedApplications, WebContents webContents, String[] preferredRelatedApplications, Object supportedDelegations,
PaymentAppFactory.PaymentAppCreatedCallback callback) { WebContents webContents, PaymentAppFactory.PaymentAppCreatedCallback callback) {
ThreadUtils.assertOnUiThread(); ThreadUtils.assertOnUiThread();
Context context = ChromeActivity.fromWebContents(webContents); Context context = ChromeActivity.fromWebContents(webContents);
...@@ -380,15 +387,15 @@ public class ServiceWorkerPaymentAppBridge implements PaymentAppFactory.PaymentA ...@@ -380,15 +387,15 @@ public class ServiceWorkerPaymentAppBridge implements PaymentAppFactory.PaymentA
scopeUri, name, userHint, origin, scopeUri, name, userHint, origin,
icon == null ? null : new BitmapDrawable(context.getResources(), icon), icon == null ? null : new BitmapDrawable(context.getResources(), icon),
methodNameArray, explicitlyVerified, methodNameArray, explicitlyVerified,
(ServiceWorkerPaymentApp.Capabilities[]) capabilities, (ServiceWorkerPaymentApp.Capabilities[]) capabilities, preferredRelatedApplications,
preferredRelatedApplications)); (ServiceWorkerPaymentApp.SupportedDelegations) supportedDelegations));
} }
@CalledByNative @CalledByNative
private static void onInstallablePaymentAppCreated(@Nullable String name, String swUrl, private static void onInstallablePaymentAppCreated(@Nullable String name, String swUrl,
String scope, boolean useCache, @Nullable Bitmap icon, String methodName, String scope, boolean useCache, @Nullable Bitmap icon, String methodName,
String[] preferredRelatedApplications, WebContents webContents, String[] preferredRelatedApplications, Object supportedDelegations,
PaymentAppFactory.PaymentAppCreatedCallback callback) { WebContents webContents, PaymentAppFactory.PaymentAppCreatedCallback callback) {
ThreadUtils.assertOnUiThread(); ThreadUtils.assertOnUiThread();
Context context = ChromeActivity.fromWebContents(webContents); Context context = ChromeActivity.fromWebContents(webContents);
...@@ -406,7 +413,8 @@ public class ServiceWorkerPaymentAppBridge implements PaymentAppFactory.PaymentA ...@@ -406,7 +413,8 @@ public class ServiceWorkerPaymentAppBridge implements PaymentAppFactory.PaymentA
callback.onPaymentAppCreated(new ServiceWorkerPaymentApp(webContents, name, callback.onPaymentAppCreated(new ServiceWorkerPaymentApp(webContents, name,
scopeUri.getHost(), swUri, scopeUri, useCache, scopeUri.getHost(), swUri, scopeUri, useCache,
icon == null ? null : new BitmapDrawable(context.getResources(), icon), methodName, icon == null ? null : new BitmapDrawable(context.getResources(), icon), methodName,
preferredRelatedApplications)); preferredRelatedApplications,
(ServiceWorkerPaymentApp.SupportedDelegations) supportedDelegations));
} }
@CalledByNative @CalledByNative
......
// 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 { ...@@ -339,7 +339,8 @@ public class PaymentRequestPaymentAppAndBasicCardWithModifiersTest {
"https://bobpay.com" /* tertiarylabel */, icon /* icon */, "https://bobpay.com" /* tertiarylabel */, icon /* icon */,
bobpayMethodNames /* methodNames */, true /* explicitlyVerified */, bobpayMethodNames /* methodNames */, true /* explicitlyVerified */,
bobpayCapabilities /* capabilities */, bobpayCapabilities /* capabilities */,
new String[0] /* preferredRelatedApplicationIds */)); new String[0] /* preferredRelatedApplicationIds */,
new ServiceWorkerPaymentApp.SupportedDelegations()));
callback.onPaymentAppCreated( callback.onPaymentAppCreated(
new ServiceWorkerPaymentApp(webContents, 0 /* registrationId */, new ServiceWorkerPaymentApp(webContents, 0 /* registrationId */,
UriUtils.parseUriFromString("https://alicepay.com") /* scope */, UriUtils.parseUriFromString("https://alicepay.com") /* scope */,
...@@ -347,7 +348,8 @@ public class PaymentRequestPaymentAppAndBasicCardWithModifiersTest { ...@@ -347,7 +348,8 @@ public class PaymentRequestPaymentAppAndBasicCardWithModifiersTest {
"https://alicepay.com" /* tertiarylabel */, icon /* icon */, "https://alicepay.com" /* tertiarylabel */, icon /* icon */,
alicepayMethodNames /* methodNames */, true /* explicitlyVerified */, alicepayMethodNames /* methodNames */, true /* explicitlyVerified */,
alicepayCapabilities /* capabilities */, alicepayCapabilities /* capabilities */,
new String[0] /* preferredRelatedApplicationIds */)); new String[0] /* preferredRelatedApplicationIds */,
new ServiceWorkerPaymentApp.SupportedDelegations()));
callback.onAllPaymentAppsCreated(); callback.onAllPaymentAppsCreated();
}); });
mPaymentRequestTestRule.triggerUIAndWait( mPaymentRequestTestRule.triggerUIAndWait(
......
...@@ -46,16 +46,19 @@ public class PaymentRequestServiceWorkerPaymentAppTest { ...@@ -46,16 +46,19 @@ public class PaymentRequestServiceWorkerPaymentAppTest {
"payment_request_bobpay_and_basic_card_with_modifier_optional_data_test.html"); "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 supportedMethodNames The supported payment methods of the mock payment app.
* @param capabilities The capabilities of the mocked 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 withIcon Whether provide payment app icon.
* @param supportedDelegations The supported delegations of the mock payment app.
*/ */
private void installMockServiceWorkerPaymentApp(final String[] supportedMethodNames, private void installMockServiceWorkerPaymentApp(final String[] supportedMethodNames,
final ServiceWorkerPaymentApp.Capabilities[] capabilities, final boolean withName, final ServiceWorkerPaymentApp.Capabilities[] capabilities, final String name,
final boolean withIcon) { final boolean withIcon,
ServiceWorkerPaymentApp.SupportedDelegations supportedDelegations) {
PaymentAppFactory.getInstance().addAdditionalFactory( PaymentAppFactory.getInstance().addAdditionalFactory(
(webContents, methodNames, mayCrawlUnused, callback) -> { (webContents, methodNames, mayCrawlUnused, callback) -> {
ChromeActivity activity = ChromeActivity.fromWebContents(webContents); ChromeActivity activity = ChromeActivity.fromWebContents(webContents);
...@@ -66,16 +69,52 @@ public class PaymentRequestServiceWorkerPaymentAppTest { ...@@ -66,16 +69,52 @@ public class PaymentRequestServiceWorkerPaymentAppTest {
: null; : null;
callback.onPaymentAppCreated(new ServiceWorkerPaymentApp(webContents, callback.onPaymentAppCreated(new ServiceWorkerPaymentApp(webContents,
0 /* registrationId */, 0 /* registrationId */,
UriUtils.parseUriFromString("https://bobpay.com") /* scope */, UriUtils.parseUriFromString("https://bobpay.com") /* scope */, name,
withName ? "BobPay" : null /* name */, "test@bobpay.com" /* userHint */, "test@bobpay.com" /* userHint */, "https://bobpay.com" /* origin */,
"https://bobpay.com" /* origin */, icon /* icon */, icon /* icon */, supportedMethodNames /* methodNames */,
supportedMethodNames /* methodNames */, true /* explicitlyVerified */, true /* explicitlyVerified */, capabilities /* capabilities */,
capabilities /* capabilities */, new String[0] /* preferredRelatedApplicationIds */,
new String[0] /* preferredRelatedApplicationIds */)); supportedDelegations));
callback.onAllPaymentAppsCreated(); 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 @Test
@MediumTest @MediumTest
@Feature({"Payments"}) @Feature({"Payments"})
...@@ -405,4 +444,84 @@ public class PaymentRequestServiceWorkerPaymentAppTest { ...@@ -405,4 +444,84 @@ public class PaymentRequestServiceWorkerPaymentAppTest {
mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput()); mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
Assert.assertNull(mPaymentRequestTestRule.getSelectedPaymentInstrumentLabel()); 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 ...@@ -971,6 +971,9 @@ public class PaymentRequestTestRule extends ChromeTabbedActivityTestRule
@Override @Override
public void onPaymentRequestReadyForInput(PaymentRequestUI ui) { public void onPaymentRequestReadyForInput(PaymentRequestUI ui) {
ThreadUtils.assertOnUiThread(); 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); mReadyForInput.notifyCalled(ui);
} }
...@@ -995,6 +998,9 @@ public class PaymentRequestTestRule extends ChromeTabbedActivityTestRule ...@@ -995,6 +998,9 @@ public class PaymentRequestTestRule extends ChromeTabbedActivityTestRule
@Override @Override
public void onPaymentRequestReadyToPay(PaymentRequestUI ui) { public void onPaymentRequestReadyToPay(PaymentRequestUI ui) {
ThreadUtils.assertOnUiThread(); 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); mReadyToPay.notifyCalled(ui);
} }
......
...@@ -95,6 +95,13 @@ void OnGotAllPaymentApps( ...@@ -95,6 +95,13 @@ void OnGotAllPaymentApps(
env, app_info.second->capabilities[i].supported_card_types)); 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. // TODO(crbug.com/846077): Find a proper way to make use of user hint.
Java_ServiceWorkerPaymentAppBridge_onPaymentAppCreated( Java_ServiceWorkerPaymentAppBridge_onPaymentAppCreated(
env, app_info.second->registration_id, env, app_info.second->registration_id,
...@@ -109,10 +116,17 @@ void OnGotAllPaymentApps( ...@@ -109,10 +116,17 @@ void OnGotAllPaymentApps(
ToJavaArrayOfStrings(env, app_info.second->enabled_methods), ToJavaArrayOfStrings(env, app_info.second->enabled_methods),
app_info.second->has_explicitly_verified_methods, jcapabilities, app_info.second->has_explicitly_verified_methods, jcapabilities,
ToJavaArrayOfStrings(env, preferred_related_application_ids), ToJavaArrayOfStrings(env, preferred_related_application_ids),
jweb_contents, jcallback); jsupported_delegations, jweb_contents, jcallback);
} }
for (const auto& installable_app : installable_apps) { 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( Java_ServiceWorkerPaymentAppBridge_onInstallablePaymentAppCreated(
env, ConvertUTF8ToJavaString(env, installable_app.second->name), env, ConvertUTF8ToJavaString(env, installable_app.second->name),
ConvertUTF8ToJavaString(env, installable_app.second->sw_js_url), ConvertUTF8ToJavaString(env, installable_app.second->sw_js_url),
...@@ -123,7 +137,7 @@ void OnGotAllPaymentApps( ...@@ -123,7 +137,7 @@ void OnGotAllPaymentApps(
: gfx::ConvertToJavaBitmap(installable_app.second->icon.get()), : gfx::ConvertToJavaBitmap(installable_app.second->icon.get()),
ConvertUTF8ToJavaString(env, installable_app.first.spec()), ConvertUTF8ToJavaString(env, installable_app.first.spec()),
ToJavaArrayOfStrings(env, installable_app.second->preferred_app_ids), ToJavaArrayOfStrings(env, installable_app.second->preferred_app_ids),
jweb_contents, jcallback); jsupported_delegations, jweb_contents, jcallback);
} }
Java_ServiceWorkerPaymentAppBridge_onAllPaymentAppsCreated(env, jcallback); Java_ServiceWorkerPaymentAppBridge_onAllPaymentAppsCreated(env, jcallback);
......
...@@ -272,3 +272,64 @@ function buyWithVisaModifier() { // eslint-disable-line no-unused-vars ...@@ -272,3 +272,64 @@ function buyWithVisaModifier() { // eslint-disable-line no-unused-vars
print('exception thrown<br>' + error.message); 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. ...@@ -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="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="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="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> <pre id="result"></pre>
<script src="util.js"></script> <script src="util.js"></script>
<script src="bobpay_and_basic_card_with_modifier_optional_data.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