chromium/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerPaymentAppTest.java

// Copyright 2016 The Chromium Authors
// 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.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;

import androidx.test.filters.MediumTest;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.ActivityUtils;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.payments.PaymentAppFactoryDelegate;
import org.chromium.components.payments.PaymentAppFactoryInterface;
import org.chromium.components.payments.PaymentAppService;
import org.chromium.components.payments.PaymentAppServiceBridge;
import org.chromium.components.payments.PaymentFeatureList;
import org.chromium.components.payments.SupportedDelegations;
import org.chromium.content_public.browser.WebContents;

import java.util.concurrent.TimeoutException;

/** A payment integration test for service worker based payment apps. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({
    ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
    // Prevent crawling the web for real payment apps.
    "disable-features=" + PaymentFeatureList.SERVICE_WORKER_PAYMENT_APPS
})
public class PaymentRequestServiceWorkerPaymentAppTest {
    @Rule
    public PaymentRequestTestRule mPaymentRequestTestRule =
            new PaymentRequestTestRule(
                    "payment_request_bobpay_and_basic_card_with_modifier_optional_data_test.html");

    /**
     * Installs a mock service worker based payment app with given supported delegations for
     * testing.
     *
     * @param scope Service worker scope that identifies the payment app. Must be unique.
     * @param supportedMethodNames The supported payment methods of the mock payment app.
     * @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(
            String scope,
            String[] supportedMethodNames,
            String name,
            boolean withIcon,
            SupportedDelegations supportedDelegations) {
        PaymentAppService.getInstance()
                .addFactory(
                        new PaymentAppFactoryInterface() {
                            @Override
                            public void create(PaymentAppFactoryDelegate delegate) {
                                WebContents webContents = delegate.getParams().getWebContents();
                                Activity activity =
                                        ActivityUtils.getActivityFromWebContents(webContents);
                                BitmapDrawable icon =
                                        withIcon
                                                ? new BitmapDrawable(
                                                        activity.getResources(),
                                                        Bitmap.createBitmap(
                                                                new int[] {Color.RED},
                                                                /* width= */ 1,
                                                                /* height= */ 1,
                                                                Bitmap.Config.ARGB_8888))
                                                : null;
                                delegate.onCanMakePaymentCalculated(true);
                                delegate.onPaymentAppCreated(
                                        new MockPaymentApp(
                                                /* identifier= */ scope,
                                                name,
                                                icon,
                                                supportedMethodNames,
                                                supportedDelegations));
                                delegate.onDoneCreatingPaymentApps(this);
                            }
                        });
    }

    /**
     * Installs a mock service worker based payment app with no supported delegations for testing.
     *
     * @param scope The service worker scope that identifies this payment app. Must be unique.
     * @param supportedMethodNames The supported payment methods of the mock payment app.
     * @param withName Whether provide payment app name.
     * @param withIcon Whether provide payment app icon.
     */
    private void installMockServiceWorkerPaymentApp(
            String scope, String[] supportedMethodNames, boolean withName, boolean withIcon) {
        installMockServiceWorkerPaymentApp(
                scope,
                supportedMethodNames,
                withName ? "BobPay" : null,
                withIcon,
                new SupportedDelegations());
    }

    /**
     * Installs a mock service worker based payment app for bobpay with given supported delegations
     * for testing.
     *
     * @param scope The service worker scope that identifies this payment app. Must be unique.
     * @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(
            String scope,
            boolean shippingAddress,
            boolean payerName,
            boolean payerPhone,
            boolean payerEmail,
            String name) {
        String[] supportedMethodNames = {"https://bobpay.xyz"};
        installMockServiceWorkerPaymentApp(
                scope,
                supportedMethodNames,
                name,
                /* withIcon= */ true,
                new SupportedDelegations(shippingAddress, payerName, payerPhone, payerEmail));
    }

    @Test
    @MediumTest
    @Feature({"Payments"})
    public void testNoSupportedPaymentMethods() throws TimeoutException {
        mPaymentRequestTestRule.clickNodeAndWait(
                "buy_with_bobpay", mPaymentRequestTestRule.getShowFailed());
        mPaymentRequestTestRule.expectResultContains(
                new String[] {"show() rejected", "The payment method", "not supported"});
    }

    @Test
    @MediumTest
    @Feature({"Payments"})
    public void testHasSupportedPaymentMethods() throws TimeoutException {
        String[] supportedMethodNames = {"https://bobpay.test"};
        installMockServiceWorkerPaymentApp("https://bobpay.test", supportedMethodNames, true, true);

        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
        // Payment sheet skips to the app since it is the only available app.
        mPaymentRequestTestRule.clickNodeAndWait("buy", mPaymentRequestTestRule.getDismissed());
    }

    @Test
    @MediumTest
    @Feature({"Payments"})
    public void testDoNotCallCanMakePayment() throws TimeoutException {
        String[] supportedMethodNames1 = {"https://bobpay.test"};
        installMockServiceWorkerPaymentApp(
                "https://bobpay.test", supportedMethodNames1, true, true);

        String[] supportedMethodNames2 = {"https://kylepay.test/webpay"};
        installMockServiceWorkerPaymentApp(
                "https://kylepay.test/webpay", supportedMethodNames2, true, true);

        // Sets setCanMakePaymentForTesting(false) to return false for CanMakePayment since there is
        // no real sw payment app, so if CanMakePayment is called then no payment apps will be
        // available, otherwise CanMakePayment is not called.
        PaymentAppServiceBridge.setCanMakePaymentForTesting(false);

        mPaymentRequestTestRule.triggerUIAndWait("buy", mPaymentRequestTestRule.getReadyForInput());
        Assert.assertEquals(2, mPaymentRequestTestRule.getNumberOfPaymentApps());
    }

    @Test
    @MediumTest
    @Feature({"Payments"})
    public void testCanPreselect() throws TimeoutException {
        String[] supportedMethodNames = {"https://bobpay.test"};
        installMockServiceWorkerPaymentApp("https://bobpay.test", supportedMethodNames, true, true);

        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);

        // Payment sheet skips to the app since it is the only available app.
        mPaymentRequestTestRule.clickNodeAndWait("buy", mPaymentRequestTestRule.getDismissed());
    }

    @Test
    @MediumTest
    @Feature({"Payments"})
    public void testCanNotPreselectWithoutName() throws TimeoutException {
        String[] supportedMethodNames = {"https://bobpay.test"};
        installMockServiceWorkerPaymentApp(
                "https://bobpay.test", supportedMethodNames, false, true);

        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);

        mPaymentRequestTestRule.triggerUIAndWait("buy", mPaymentRequestTestRule.getReadyForInput());
        Assert.assertNull(mPaymentRequestTestRule.getSelectedPaymentAppLabel());
    }

    @Test
    @MediumTest
    @Feature({"Payments"})
    public void testCanNotPreselectWithoutIcon() throws TimeoutException {
        String[] supportedMethodNames = {"https://bobpay.test"};
        installMockServiceWorkerPaymentApp(
                "https://bobpay.test", supportedMethodNames, true, false);

        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);

        mPaymentRequestTestRule.triggerUIAndWait("buy", mPaymentRequestTestRule.getReadyForInput());
        Assert.assertNull(mPaymentRequestTestRule.getSelectedPaymentAppLabel());
    }

    @Test
    @MediumTest
    @Feature({"Payments"})
    public void testCanNotPreselectWithoutNameAndIcon() throws TimeoutException {
        String[] supportedMethodNames = {"https://bobpay.test"};
        installMockServiceWorkerPaymentApp(
                "https://bobpay.test", supportedMethodNames, false, false);

        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);

        mPaymentRequestTestRule.triggerUIAndWait("buy", mPaymentRequestTestRule.getReadyForInput());
        Assert.assertNull(mPaymentRequestTestRule.getSelectedPaymentAppLabel());
    }

    @Test
    @MediumTest
    @Feature({"Payments"})
    public void testPaymentAppProvidingShippingComesFirst() throws TimeoutException {
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://alicepay.test",
                /* shippingAddress= */ false,
                /* payerName= */ false,
                /* payerPhone= */ false,
                /* payerEmail= */ false,
                /* name= */ "noSupportedDelegation");
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://bobpay.test",
                /* shippingAddress= */ true,
                /* payerName= */ false,
                /* payerPhone= */ false,
                /* payerEmail= */ false,
                /* name= */ "shippingSupported1");
        // Install the second app supporting shipping delegation to force showing payment sheet.
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://charliepay.test",
                /* shippingAddress= */ true,
                /* payerName= */ false,
                /* payerPhone= */ false,
                /* payerEmail= */ false,
                /* name= */ "shippingSupported2");

        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);

        mPaymentRequestTestRule.triggerUIAndWait(
                "buy_with_shipping_requested", mPaymentRequestTestRule.getReadyForInput());
        Assert.assertEquals(3, mPaymentRequestTestRule.getNumberOfPaymentApps());

        // The payment app which provides shipping address must be preselected.
        Assert.assertTrue(
                mPaymentRequestTestRule.getSelectedPaymentAppLabel().contains("shippingSupported"));
    }

    @Test
    @MediumTest
    @Feature({"Payments"})
    public void testPaymentAppProvidingContactComesFirst() throws TimeoutException {
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://alicepay.test",
                /* shippingAddress= */ false,
                /* payerName= */ false,
                /* payerPhone= */ false,
                /* payerEmail= */ false,
                /* name= */ "noSupportedDelegation");
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://bobpay.test",
                /* shippingAddress= */ false,
                /* payerName= */ true,
                /* payerPhone= */ true,
                /* payerEmail= */ true,
                /* name= */ "contactSupported");
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://charliepay.test",
                /* shippingAddress= */ false,
                /* payerName= */ false,
                /* payerPhone= */ false,
                /* payerEmail= */ true,
                /* name= */ "emailOnlySupported");
        // Install the second app supporting contact delegation to force showing payment sheet.
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://davepay.test",
                /* shippingAddress= */ false,
                /* payerName= */ true,
                /* payerPhone= */ true,
                /* payerEmail= */ true,
                /* name= */ "contactSupported2");

        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);

        mPaymentRequestTestRule.triggerUIAndWait(
                "buy_with_contact_requested", mPaymentRequestTestRule.getReadyForInput());
        Assert.assertEquals(4, mPaymentRequestTestRule.getNumberOfPaymentApps());

        // The payment app which provides full contact details must be preselected.
        Assert.assertTrue(
                mPaymentRequestTestRule.getSelectedPaymentAppLabel().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(2)
                        .contains("emailOnlySupported"));
    }

    @Test
    @MediumTest
    @Feature({"Payments"})
    public void testPaymentAppProvidingAllRequiredInfoComesFirst() throws TimeoutException {
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://alicepay.test",
                /* shippingAddress= */ true,
                /* payerName= */ false,
                /* payerPhone= */ false,
                /* payerEmail= */ false,
                /* name= */ "shippingSupported");
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://bobpay.test",
                /* shippingAddress= */ false,
                /* payerName= */ true,
                /* payerPhone= */ true,
                /* payerEmail= */ true,
                /* name= */ "contactSupported");
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://charliepay.test",
                /* shippingAddress= */ true,
                /* payerName= */ true,
                /* payerPhone= */ true,
                /* payerEmail= */ true,
                /* name= */ "shippingAndContactSupported");
        // Install the second app supporting both shipping and contact delegations to force showing
        // payment sheet.
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://davepay.test",
                /* shippingAddress= */ true,
                /* payerName= */ true,
                /* payerPhone= */ true,
                /* payerEmail= */ true,
                /* name= */ "shippingAndContactSupported2");

        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);

        mPaymentRequestTestRule.triggerUIAndWait(
                "buy_with_shipping_and_contact_requested",
                mPaymentRequestTestRule.getReadyForInput());
        Assert.assertEquals(4, mPaymentRequestTestRule.getNumberOfPaymentApps());

        // The payment app which provides all required information must be preselected.
        Assert.assertTrue(
                mPaymentRequestTestRule
                        .getSelectedPaymentAppLabel()
                        .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(2)
                        .contains("shippingSupported"));
    }

    @Test
    @MediumTest
    @Feature({"Payments"})
    public void testSkipsToSinglePaymentAppProvidingShipping() throws TimeoutException {
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://alicepay.test",
                /* shippingAddress= */ false,
                /* payerName= */ false,
                /* payerPhone= */ false,
                /* payerEmail= */ false,
                /* name= */ "noSupportedDelegation");
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://bobpay.test",
                /* shippingAddress= */ true,
                /* payerName= */ false,
                /* payerPhone= */ false,
                /* payerEmail= */ false,
                /* name= */ "shippingSupported");

        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
        mPaymentRequestTestRule.clickNodeAndWait(
                "buy_with_shipping_requested", mPaymentRequestTestRule.getDismissed());
    }

    @Test
    @MediumTest
    @Feature({"Payments"})
    public void testSkipsToSinglePaymentAppProvidingContact() throws TimeoutException {
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://alicepay.test",
                /* shippingAddress= */ false,
                /* payerName= */ false,
                /* payerPhone= */ false,
                /* payerEmail= */ false,
                /* name= */ "noSupportedDelegation");
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://bobpay.test",
                /* shippingAddress= */ false,
                /* payerName= */ true,
                /* payerPhone= */ true,
                /* payerEmail= */ true,
                /* name= */ "contactSupported");
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://charliepay.test",
                /* shippingAddress= */ false,
                /* payerName= */ false,
                /* payerPhone= */ false,
                /* payerEmail= */ true,
                /* name= */ "emailOnlySupported");

        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
        mPaymentRequestTestRule.clickNodeAndWait(
                "buy_with_contact_requested", mPaymentRequestTestRule.getDismissed());
    }

    @Test
    @MediumTest
    @Feature({"Payments"})
    public void testSkipsToSinglePaymentAppProvidingAllRequiredInfo() throws TimeoutException {
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://alicepay.test",
                /* shippingAddress= */ true,
                /* payerName= */ false,
                /* payerPhone= */ false,
                /* payerEmail= */ false,
                /* name= */ "shippingSupported");
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://bobpay.test",
                /* shippingAddress= */ false,
                /* payerName= */ true,
                /* payerPhone= */ true,
                /* payerEmail= */ true,
                /* name= */ "contactSupported");
        installMockServiceWorkerPaymentAppWithDelegations(
                /* scope= */ "https://charliepay.test",
                /* shippingAddress= */ true,
                /* payerName= */ true,
                /* payerPhone= */ true,
                /* payerEmail= */ true,
                /* name= */ "shippingAndContactSupported");

        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
        mPaymentRequestTestRule.clickNodeAndWait(
                "buy_with_shipping_and_contact_requested", mPaymentRequestTestRule.getDismissed());
    }
}