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

// Copyright 2020 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 static com.google.common.truth.Truth.assertThat;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;

import androidx.test.filters.SmallTest;

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

import org.chromium.base.test.util.Feature;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.payments.Address;
import org.chromium.components.payments.ErrorStrings;
import org.chromium.components.payments.PayerData;
import org.chromium.components.payments.intent.WebPaymentIntentHelper;
import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentCurrencyAmount;
import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentDetailsModifier;
import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentItem;
import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentMethodData;
import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentOptions;
import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentShippingOption;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/** Tests for {@link WebPaymentIntentHelper}. */
@RunWith(ChromeJUnit4ClassRunner.class)
public class WebPaymentIntentHelperTest {
    @Rule public ExpectedException thrown = ExpectedException.none();

    // Used to receive the result of {@link #parsePaymentResponse}.
    private String mErrorString;
    private String mDetails;
    private String mMethodName;
    private PayerData mPayerData;

    private Intent createPaymentResponseWithMissingField(String missingField) {
        Intent intent = new Intent();
        Bundle extras = new Bundle();
        extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_DETAILS, "\"key\":\"value\"}");
        extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_METHOD_NAME, "maxPay");
        Bundle addressBundle = new Bundle();
        addressBundle.putString(Address.EXTRA_ADDRESS_COUNTRY, "Canada");
        String[] addressLine = {"111 Richmond Street West"};
        addressBundle.putStringArray(Address.EXTRA_ADDRESS_LINES, addressLine);
        addressBundle.putString(Address.EXTRA_ADDRESS_REGION, "Ontario");
        addressBundle.putString(Address.EXTRA_ADDRESS_CITY, "Toronto");
        addressBundle.putString(Address.EXTRA_ADDRESS_POSTAL_CODE, "M5H2G4");
        addressBundle.putString(Address.EXTRA_ADDRESS_RECIPIENT, "John Smith");
        addressBundle.putString(Address.EXTRA_ADDRESS_PHONE, "4169158200");
        extras.putBundle(WebPaymentIntentHelper.EXTRA_SHIPPING_ADDRESS, addressBundle);
        extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_NAME, "John Smith");
        extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_PHONE, "4169158200");
        extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_EMAIL, "[email protected]");
        extras.putString(WebPaymentIntentHelper.EXTRA_SHIPPING_OPTION_ID, "shippingId");

        // Redact the entry with missingField key.
        extras.remove(missingField);

        intent.putExtras(extras);
        return intent;
    }

    // Test the happy path of createPayIntent and verify the non-deprecated extras.
    @Test
    @SmallTest
    @Feature({"Payments"})
    public void createPayIntentTest() throws Throwable {
        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData =
                new PaymentMethodData("bobPayMethod", "{\"key\":\"value\"}");
        PaymentMethodData maxPayMethodData = new PaymentMethodData("maxPayMethod", "{}");
        methodDataMap.put("bobPay", bobPayMethodData);
        methodDataMap.put("maxPay", maxPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        List<PaymentItem> displayItems = new ArrayList<PaymentItem>();
        displayItems.add(new PaymentItem(new PaymentCurrencyAmount("CAD", "50")));
        displayItems.add(new PaymentItem(new PaymentCurrencyAmount("CAD", "150")));

        Map<String, PaymentDetailsModifier> modifiers =
                new HashMap<String, PaymentDetailsModifier>();
        PaymentDetailsModifier bobPaymodifier = new PaymentDetailsModifier(total, bobPayMethodData);
        modifiers.put("bobPay", bobPaymodifier);

        byte[][] certificateChain = new byte[][] {{0}};

        PaymentOptions paymentOptions =
                new PaymentOptions(
                        /* requestPayerName= */ true,
                        /* requestPayerEmail= */ true,
                        /* requestPayerPhone= */ true,
                        /* requestShipping= */ true,
                        /* shippingType= */ "delivery");

        List<PaymentShippingOption> shippingOptions = new ArrayList<PaymentShippingOption>();
        shippingOptions.add(
                new PaymentShippingOption(
                        "shippingId",
                        "Free shipping",
                        new PaymentCurrencyAmount("USD", "0"),
                        /* selected= */ true));

        Intent intent =
                WebPaymentIntentHelper.createPayIntent(
                        "package.name",
                        "activity.name",
                        "payment.request.id",
                        "merchant.name",
                        "schemeless.origin",
                        "schemeless.iframe.origin",
                        certificateChain,
                        methodDataMap,
                        total,
                        displayItems,
                        modifiers,
                        paymentOptions,
                        shippingOptions);
        Assert.assertEquals(WebPaymentIntentHelper.ACTION_PAY, intent.getAction());
        Assert.assertEquals("package.name", intent.getComponent().getPackageName());
        Assert.assertEquals("activity.name", intent.getComponent().getClassName());
        Bundle bundle = intent.getExtras();
        Assert.assertNotNull(bundle);
        Assert.assertEquals(
                "payment.request.id", bundle.get(WebPaymentIntentHelper.EXTRA_PAYMENT_REQUEST_ID));
        Assert.assertEquals(
                "merchant.name", bundle.get(WebPaymentIntentHelper.EXTRA_MERCHANT_NAME));
        Assert.assertEquals(
                "schemeless.origin", bundle.get(WebPaymentIntentHelper.EXTRA_TOP_ORIGIN));
        Assert.assertEquals(
                "schemeless.iframe.origin",
                bundle.get(WebPaymentIntentHelper.EXTRA_PAYMENT_REQUEST_ORIGIN));

        Parcelable[] certificateChainParcels =
                bundle.getParcelableArray(WebPaymentIntentHelper.EXTRA_TOP_CERTIFICATE_CHAIN);
        Assert.assertEquals(1, certificateChainParcels.length);
        assertThat(
                        ((Bundle) certificateChainParcels[0])
                                .getByteArray(WebPaymentIntentHelper.EXTRA_CERTIFICATE))
                .isEqualTo(new byte[] {0});

        Assert.assertEquals(
                new HashSet<>(Arrays.asList("bobPay", "maxPay")),
                new HashSet<>(
                        bundle.getStringArrayList(WebPaymentIntentHelper.EXTRA_METHOD_NAMES)));

        Bundle methodDataBundle = bundle.getParcelable(WebPaymentIntentHelper.EXTRA_METHOD_DATA);
        Assert.assertEquals(2, methodDataBundle.keySet().size());
        Assert.assertEquals("{\"key\":\"value\"}", methodDataBundle.getString("bobPay"));
        Assert.assertEquals("{}", methodDataBundle.getString("maxPay"));

        // The data field is a string because it is PaymentMethodData#stringifiedData.
        String expectedSerializedModifiers =
                "[{\"total\":{\"label\":\"\",\"amount\":{\"currency\":\"CAD\",\"value\":\"200\"}},"
                        + "\"supportedMethods\":[\"bobPayMethod\"],"
                        + "\"data\":\"{\\\"key\\\":\\\"value\\\"}\"}]";
        Assert.assertEquals(
                expectedSerializedModifiers, bundle.get(WebPaymentIntentHelper.EXTRA_MODIFIERS));
        Assert.assertEquals(
                "{\"currency\":\"CAD\",\"value\":\"200\"}",
                bundle.get(WebPaymentIntentHelper.EXTRA_TOTAL));

        Bundle expectedPaymentOptions =
                bundle.getBundle(WebPaymentIntentHelper.EXTRA_PAYMENT_OPTIONS);
        Assert.assertTrue(
                expectedPaymentOptions.getBoolean(
                        WebPaymentIntentHelper.EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_NAME));
        Assert.assertTrue(
                expectedPaymentOptions.getBoolean(
                        WebPaymentIntentHelper.EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_EMAIL));
        Assert.assertTrue(
                expectedPaymentOptions.getBoolean(
                        WebPaymentIntentHelper.EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_PHONE));
        Assert.assertTrue(
                expectedPaymentOptions.getBoolean(
                        WebPaymentIntentHelper.EXTRA_PAYMENT_OPTIONS_REQUEST_SHIPPING));
        Assert.assertEquals(
                "delivery",
                expectedPaymentOptions.getString(
                        WebPaymentIntentHelper.EXTRA_PAYMENT_OPTIONS_SHIPPING_TYPE));

        Parcelable[] expectedShippingOptions =
                bundle.getParcelableArray(WebPaymentIntentHelper.EXTRA_SHIPPING_OPTIONS);
        Assert.assertEquals(1, expectedShippingOptions.length);
        Bundle shippingOption = (Bundle) expectedShippingOptions[0];
        Assert.assertEquals(
                "shippingId",
                shippingOption.getString(PaymentShippingOption.EXTRA_SHIPPING_OPTION_ID));
        Assert.assertEquals(
                "Free shipping",
                shippingOption.getString(PaymentShippingOption.EXTRA_SHIPPING_OPTION_LABEL));
        Bundle amount =
                shippingOption.getBundle(PaymentShippingOption.EXTRA_SHIPPING_OPTION_AMOUNT);
        Assert.assertEquals("USD", amount.getString(PaymentCurrencyAmount.EXTRA_CURRENCY));
        Assert.assertEquals("0", amount.getString(PaymentCurrencyAmount.EXTRA_VALUE));
        Assert.assertTrue(
                shippingOption.getBoolean(PaymentShippingOption.EXTRA_SHIPPING_OPTION_SELECTED));
    }

    // Test the happy path of createPayIntent and verify the deprecated extras.
    @Test
    @SmallTest
    @Feature({"Payments"})
    public void createPayIntentDeprecatedExtraTest() throws Throwable {
        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        List<PaymentItem> displayItems = new ArrayList<PaymentItem>();
        displayItems.add(new PaymentItem(new PaymentCurrencyAmount("CAD", "50")));
        displayItems.add(new PaymentItem(new PaymentCurrencyAmount("CAD", "150")));

        Map<String, PaymentDetailsModifier> modifiers =
                new HashMap<String, PaymentDetailsModifier>();
        PaymentDetailsModifier modifier = new PaymentDetailsModifier(total, bobPayMethodData);
        modifiers.put("modifier_key", modifier);

        byte[][] certificateChain = new byte[][] {{0}};

        Intent intent =
                WebPaymentIntentHelper.createPayIntent(
                        "package.name",
                        "activity.name",
                        "payment.request.id",
                        "merchant.name",
                        "schemeless.origin",
                        "schemeless.iframe.origin",
                        certificateChain,
                        methodDataMap,
                        total,
                        displayItems,
                        modifiers,
                        /* paymentOptions= */ null,
                        /* shippingOptions= */ null);
        Bundle bundle = intent.getExtras();
        Assert.assertNotNull(bundle);

        Assert.assertEquals(
                "payment.request.id", bundle.get(WebPaymentIntentHelper.EXTRA_DEPRECATED_ID));
        Assert.assertEquals(
                "schemeless.origin", bundle.get(WebPaymentIntentHelper.EXTRA_DEPRECATED_ORIGIN));
        Assert.assertEquals(
                "schemeless.iframe.origin",
                bundle.get(WebPaymentIntentHelper.EXTRA_DEPRECATED_IFRAME_ORIGIN));

        Parcelable[] deprecatedCertificateChainParcels =
                bundle.getParcelableArray(
                        WebPaymentIntentHelper.EXTRA_DEPRECATED_CERTIFICATE_CHAIN);
        Assert.assertEquals(1, deprecatedCertificateChainParcels.length);
        assertThat(
                        ((Bundle) deprecatedCertificateChainParcels[0])
                                .getByteArray(WebPaymentIntentHelper.EXTRA_CERTIFICATE))
                .isEqualTo(new byte[] {0});

        Assert.assertEquals(
                "bobPay", bundle.get(WebPaymentIntentHelper.EXTRA_DEPRECATED_METHOD_NAME));
        Assert.assertEquals("null", bundle.get(WebPaymentIntentHelper.EXTRA_DEPRECATED_DATA));

        Bundle deprecatedDataMap =
                bundle.getParcelable(WebPaymentIntentHelper.EXTRA_DEPRECATED_DATA_MAP);
        Assert.assertEquals(1, deprecatedDataMap.keySet().size());
        Assert.assertEquals("null", deprecatedDataMap.getString("bobPay"));

        Assert.assertEquals(
                "{\"total\":{\"label\":\"\","
                        + "\"amount\":{\"currency\":\"CAD\",\"value\":\"200\"}},"
                        + "\"displayItems\":[]}",
                bundle.get(WebPaymentIntentHelper.EXTRA_DEPRECATED_DETAILS));
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void nullPackageNameExceptionTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("packageName should not be null or empty.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createPayIntent(
                /* packageName= */ null,
                "activity.name",
                "payment.request.id",
                "merchant.name",
                "schemeless.origin",
                "schemeless.iframe.origin",
                /* certificateChain= */ null,
                methodDataMap,
                total,
                /* displayItems= */ null,
                /* modifiers= */ null,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void nullActivityNameExceptionTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("activityName should not be null or empty.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createPayIntent(
                "package.name",
                /* activityName= */ null,
                "payment.request.id",
                "merchant.name",
                "schemeless.origin",
                "schemeless.iframe.origin",
                /* certificateChain= */ null,
                methodDataMap,
                total,
                /* displayItems= */ null,
                /* modifiers= */ null,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void nullIdExceptionTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("id should not be null or empty.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createPayIntent(
                "package.name",
                "activity.name",
                /* id= */ null,
                "merchant.name",
                "schemeless.origin",
                "schemeless.iframe.origin",
                /* certificateChain= */ null,
                methodDataMap,
                total,
                /* displayItems= */ null,
                /* modifiers= */ null,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void emptyIdExceptionTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("id should not be null or empty.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createPayIntent(
                "package.name",
                "activity.name",
                /* id= */ "",
                "merchant.name",
                "schemeless.origin",
                "schemeless.iframe.origin",
                /* certificateChain= */ null,
                methodDataMap,
                total,
                /* displayItems= */ null,
                /* modifiers= */ null,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void nullMerchantNameExceptionTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("merchantName should not be null.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createPayIntent(
                "package.name",
                "activity.name",
                "id",
                /* merchantName= */ null,
                "schemeless.origin",
                "schemeless.iframe.origin",
                /* certificateChain= */ null,
                methodDataMap,
                total,
                /* displayItems= */ null,
                /* modifiers= */ null,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void emptyMerchantNameNoExceptionTest() throws Throwable {
        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        Intent payIntent =
                WebPaymentIntentHelper.createPayIntent(
                        "package.name",
                        "activity.name",
                        "id",
                        /* merchantName= */ "",
                        "schemeless.origin",
                        "schemeless.iframe.origin",
                        /* certificateChain= */ null,
                        methodDataMap,
                        total,
                        /* displayItems= */ null,
                        /* modifiers= */ null,
                        /* paymentOptions= */ null,
                        /* shippingOptions= */ null);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void nullOriginExceptionTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("schemelessOrigin should not be null or empty.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createPayIntent(
                "package.name",
                "activity.name",
                "id",
                "merchant.name",
                /* schemelessOrigin= */ null,
                "schemeless.iframe.origin",
                /* certificateChain= */ null,
                methodDataMap,
                total,
                /* displayItems= */ null,
                /* modifiers= */ null,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void emptyOriginExceptionTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("schemelessOrigin should not be null or empty.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createPayIntent(
                "package.name",
                "activity.name",
                "id",
                "merchant.name",
                /* schemelessOrigin= */ "",
                "schemeless.iframe.origin",
                /* certificateChain= */ null,
                methodDataMap,
                total,
                /* displayItems= */ null,
                /* modifiers= */ null,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void nullIframeOriginExceptionTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("schemelessIframeOrigin should not be null or empty.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createPayIntent(
                "package.name",
                "activity.name",
                "id",
                "merchant.name",
                "schemeless.origin",
                /* schemelessIframeOrigin= */ null,
                /* certificateChain= */ null,
                methodDataMap,
                total,
                /* displayItems= */ null,
                /* modifiers= */ null,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void emptyIframeOriginExceptionTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("schemelessIframeOrigin should not be null or empty.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createPayIntent(
                "package.name",
                "activity.name",
                "id",
                "merchant.name",
                "schemeless.origin",
                /* schemelessIframeOrigin= */ "",
                /* certificateChain= */ null,
                methodDataMap,
                total,
                /* displayItems= */ null,
                /* modifiers= */ null,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void nullByteArrayCertifiateChainExceptionTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("certificateChain[0] should not be null.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        byte[][] certificateChain = new byte[][] {null};

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createPayIntent(
                "package.name",
                "activity.name",
                "id",
                "merchant.name",
                "schemeless.origin",
                "schemeless.iframe.origin",
                certificateChain,
                methodDataMap,
                total,
                /* displayItems= */ null,
                /* modifiers= */ null,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    // Verify that a null value in methodDataMap would trigger an exception.
    @Test
    @SmallTest
    @Feature({"Payments"})
    public void methodDataMapNullValueTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("methodDataMap's entry value should not be null.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        methodDataMap.put("bobPay", null);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createPayIntent(
                "package.name",
                "activity.name",
                "payment.request.id",
                "merchant.name",
                "schemeless.origin",
                "schemeless.iframe.origin",
                /* certificateChain= */ null,
                methodDataMap,
                total,
                /* displayItems= */ null,
                /* modifiers= */ null,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    // Verify that a null methodDataMap would trigger an exception.
    @Test
    @SmallTest
    @Feature({"Payments"})
    public void nullMethodDataMapTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("methodDataMap should not be null or empty.");

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createPayIntent(
                "package.name",
                "activity.name",
                "payment.request.id",
                "merchant.name",
                "schemeless.origin",
                "schemeless.iframe.origin",
                /* certificateChain= */ null,
                /* methodDataMap= */ null,
                total,
                /* displayItems= */ null,
                /* modifiers= */ null,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    // Verify that an empty methodDataMap would trigger an exception.
    @Test
    @SmallTest
    @Feature({"Payments"})
    public void emptyMethodDataMapTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("methodDataMap should not be null or empty.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createPayIntent(
                "package.name",
                "activity.name",
                "payment.request.id",
                "merchant.name",
                "schemeless.origin",
                "schemeless.iframe.origin",
                /* certificateChain= */ null,
                methodDataMap,
                total,
                /* displayItems= */ null,
                /* modifiers= */ null,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void nullTotalTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("total should not be null.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createPayIntent(
                "package.name",
                "activity.name",
                "id",
                "merchant.name",
                "schemeless.origin",
                "schemeless.iframe.origin",
                /* certificateChain= */ null,
                methodDataMap,
                /* total= */ null,
                /* displayItems= */ null,
                /* modifiers= */ null,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    // Verify that a null value in the modifier map would trigger an exception.
    @Test
    @SmallTest
    @Feature({"Payments"})
    public void modifierMapNullValueTest() throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("PaymentDetailsModifier should not be null.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        Map<String, PaymentDetailsModifier> modifiers =
                new HashMap<String, PaymentDetailsModifier>();
        modifiers.put("bobPay", null);

        WebPaymentIntentHelper.createPayIntent(
                "package.name",
                "activity.name",
                "payment.request.id",
                "merchant.name",
                "schemeless.origin",
                "schemeless.iframe.origin",
                /* certificateChain= */ null,
                methodDataMap,
                total,
                /* displayItems= */ null,
                modifiers,
                /* paymentOptions= */ null,
                /* shippingOptions= */ null);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void parsePaymentResponseMissingIntentDataTest() throws Throwable {
        mErrorString = null;
        WebPaymentIntentHelper.parsePaymentResponse(
                Activity.RESULT_OK,
                /* intent= */ null,
                /* requestedPaymentOptions= */ null,
                (errorString) -> mErrorString = errorString,
                (methodName, details, payerData) -> Assert.fail("Parsing should fail."));
        Assert.assertEquals(ErrorStrings.MISSING_INTENT_DATA, mErrorString);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void parsePaymentResponseMissingIntentExtrasTest() throws Throwable {
        Intent intent = new Intent();
        mErrorString = null;
        WebPaymentIntentHelper.parsePaymentResponse(
                Activity.RESULT_OK,
                intent,
                /* requestedPaymentOptions= */ null,
                (errorString) -> mErrorString = errorString,
                (methodName, details, payerData) -> Assert.fail("Parsing should fail."));
        Assert.assertEquals(ErrorStrings.MISSING_INTENT_EXTRAS, mErrorString);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void parsePaymentResponseResultCanceledTest() throws Throwable {
        Intent intent = new Intent();
        intent.putExtras(new Bundle());
        mErrorString = null;
        WebPaymentIntentHelper.parsePaymentResponse(
                Activity.RESULT_CANCELED,
                intent,
                /* requestedPaymentOptions= */ null,
                (errorString) -> mErrorString = errorString,
                (methodName, details, payerData) -> Assert.fail("Parsing should fail."));
        Assert.assertEquals(ErrorStrings.RESULT_CANCELED, mErrorString);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void parsePaymentResponseUnrecognizedActivityResultTest() throws Throwable {
        Intent intent = new Intent();
        intent.putExtras(new Bundle());
        mErrorString = null;
        WebPaymentIntentHelper.parsePaymentResponse(
                /* resultCode= */ 123,
                intent,
                /* requestedPaymentOptions= */ null,
                (errorString) -> mErrorString = errorString,
                (methodName, details, payerData) -> Assert.fail("Parsing should fail."));
        Assert.assertEquals(
                String.format(Locale.US, ErrorStrings.UNRECOGNIZED_ACTIVITY_RESULT, 123),
                mErrorString);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void parsePaymentResponseMissingDetailsTest() throws Throwable {
        Intent intent =
                createPaymentResponseWithMissingField(
                        WebPaymentIntentHelper.EXTRA_RESPONSE_DETAILS);
        mErrorString = null;
        WebPaymentIntentHelper.parsePaymentResponse(
                Activity.RESULT_OK,
                intent,
                /* requestedPaymentOptions= */ null,
                (errorString) -> mErrorString = errorString,
                (methodName, details, payerData) -> Assert.fail("Parsing should fail."));
        Assert.assertEquals(ErrorStrings.MISSING_DETAILS_FROM_PAYMENT_APP, mErrorString);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void parsePaymentResponseMissingMethodNameTest() throws Throwable {
        Intent intent =
                createPaymentResponseWithMissingField(
                        WebPaymentIntentHelper.EXTRA_RESPONSE_METHOD_NAME);
        mErrorString = null;
        WebPaymentIntentHelper.parsePaymentResponse(
                Activity.RESULT_OK,
                intent,
                /* requestedPaymentOptions= */ null,
                (errorString) -> mErrorString = errorString,
                (methodName, details, payerData) -> Assert.fail("Parsing should fail."));
        Assert.assertEquals(ErrorStrings.MISSING_METHOD_NAME_FROM_PAYMENT_APP, mErrorString);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void parsePaymentResponseMissingShippingAddressTest() throws Throwable {
        Intent intent =
                createPaymentResponseWithMissingField(
                        WebPaymentIntentHelper.EXTRA_SHIPPING_ADDRESS);
        mErrorString = null;
        WebPaymentIntentHelper.parsePaymentResponse(
                Activity.RESULT_OK,
                intent,
                new PaymentOptions(
                        /* requestPayerName= */ false,
                        /* requestPayerEmail= */ false,
                        /* requestPayerPhone= */ false,
                        /* requestShipping= */ true,
                        /* shippingType= */ "shipping"),
                (errorString) -> mErrorString = errorString,
                (methodName, details, payerData) -> Assert.fail("Parsing should fail."));
        Assert.assertEquals(ErrorStrings.SHIPPING_ADDRESS_INVALID, mErrorString);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void parsePaymentResponseMissingShippingOptionTest() throws Throwable {
        Intent intent =
                createPaymentResponseWithMissingField(
                        WebPaymentIntentHelper.EXTRA_SHIPPING_OPTION_ID);
        mErrorString = null;
        WebPaymentIntentHelper.parsePaymentResponse(
                Activity.RESULT_OK,
                intent,
                new PaymentOptions(
                        /* requestPayerName= */ false,
                        /* requestPayerEmail= */ false,
                        /* requestPayerPhone= */ false,
                        /* requestShipping= */ true,
                        /* shippingType= */ "shipping"),
                (errorString) -> mErrorString = errorString,
                (methodName, details, payerData) -> Assert.fail("Parsing should fail."));
        Assert.assertEquals(ErrorStrings.SHIPPING_OPTION_EMPTY, mErrorString);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void parsePaymentResponseMissingPayerNameTest() throws Throwable {
        Intent intent =
                createPaymentResponseWithMissingField(
                        WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_NAME);
        mErrorString = null;
        WebPaymentIntentHelper.parsePaymentResponse(
                Activity.RESULT_OK,
                intent,
                new PaymentOptions(
                        /* requestPayerName= */ true,
                        /* requestPayerEmail= */ false,
                        /* requestPayerPhone= */ false,
                        /* requestShipping= */ false,
                        /* shippingType= */ ""),
                (errorString) -> mErrorString = errorString,
                (methodName, details, payerData) -> Assert.fail("Parsing should fail."));
        Assert.assertEquals(ErrorStrings.PAYER_NAME_EMPTY, mErrorString);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void parsePaymentResponseMissingPayerEmailTest() throws Throwable {
        Intent intent =
                createPaymentResponseWithMissingField(
                        WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_EMAIL);
        mErrorString = null;
        WebPaymentIntentHelper.parsePaymentResponse(
                Activity.RESULT_OK,
                intent,
                new PaymentOptions(
                        /* requestPayerName= */ false,
                        /* requestPayerEmail= */ true,
                        /* requestPayerPhone= */ false,
                        /* requestShipping= */ false,
                        /* shippingType= */ ""),
                (errorString) -> mErrorString = errorString,
                (methodName, details, payerData) -> Assert.fail("Parsing should fail."));
        Assert.assertEquals(ErrorStrings.PAYER_EMAIL_EMPTY, mErrorString);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void parsePaymentResponseMissingPayerPhoneTest() throws Throwable {
        Intent intent =
                createPaymentResponseWithMissingField(
                        WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_PHONE);
        mErrorString = null;
        WebPaymentIntentHelper.parsePaymentResponse(
                Activity.RESULT_OK,
                intent,
                new PaymentOptions(
                        /* requestPayerName= */ false,
                        /* requestPayerEmail= */ false,
                        /* requestPayerPhone= */ true,
                        /* requestShipping= */ false,
                        /* shippingType= */ ""),
                (errorString) -> mErrorString = errorString,
                (methodName, details, payerData) -> Assert.fail("Parsing should fail."));
        Assert.assertEquals(ErrorStrings.PAYER_PHONE_EMPTY, mErrorString);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void parsePaymentResponseOKTest() throws Throwable {
        Intent intent = new Intent();
        Bundle extras = new Bundle();
        extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_DETAILS, "\"key\":\"value\"}");
        extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_METHOD_NAME, "maxPay");
        Bundle addressBundle = new Bundle();
        addressBundle.putString(Address.EXTRA_ADDRESS_COUNTRY, "Canada");
        String[] addressLine = {"111 Richmond Street West"};
        addressBundle.putStringArray(Address.EXTRA_ADDRESS_LINES, addressLine);
        addressBundle.putString(Address.EXTRA_ADDRESS_REGION, "Ontario");
        addressBundle.putString(Address.EXTRA_ADDRESS_CITY, "Toronto");
        addressBundle.putString(Address.EXTRA_ADDRESS_POSTAL_CODE, "M5H2G4");
        addressBundle.putString(Address.EXTRA_ADDRESS_RECIPIENT, "John Smith");
        addressBundle.putString(Address.EXTRA_ADDRESS_PHONE, "4169158200");
        extras.putBundle(WebPaymentIntentHelper.EXTRA_SHIPPING_ADDRESS, addressBundle);
        extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_NAME, "John Smith");
        extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_PHONE, "4169158200");
        extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_PAYER_EMAIL, "[email protected]");
        extras.putString(WebPaymentIntentHelper.EXTRA_SHIPPING_OPTION_ID, "shippingId");
        intent.putExtras(extras);
        mErrorString = null;
        WebPaymentIntentHelper.parsePaymentResponse(
                Activity.RESULT_OK,
                intent,
                /* requestedPaymentOptions= */ new PaymentOptions(
                        /* requestPayerName= */ true,
                        /* requestPayerEmail= */ true,
                        /* requestPayerPhone= */ true,
                        /* requestShipping= */ true,
                        /* shippingType= */ "shipping"),
                (errorString) -> Assert.fail("Parsing should succeed."),
                (methodName, details, payerData) -> {
                    mMethodName = methodName;
                    mDetails = details;
                    mPayerData = payerData;
                });
        Assert.assertEquals("maxPay", mMethodName);
        Assert.assertEquals("\"key\":\"value\"}", mDetails);

        // Verify payer's data.
        Assert.assertEquals("John Smith", mPayerData.payerName);
        Assert.assertEquals("4169158200", mPayerData.payerPhone);
        Assert.assertEquals("[email protected]", mPayerData.payerEmail);
        Address address = mPayerData.shippingAddress;
        Assert.assertEquals("Canada", address.country);
        Assert.assertEquals(1, address.addressLine.length);
        Assert.assertEquals("111 Richmond Street West", address.addressLine[0]);
        Assert.assertEquals("Ontario", address.region);
        Assert.assertEquals("Toronto", address.city);
        Assert.assertEquals("", address.dependentLocality);
        Assert.assertEquals("M5H2G4", address.postalCode);
        Assert.assertEquals("", address.sortingCode);
        Assert.assertEquals("", address.organization);
        Assert.assertEquals("John Smith", address.recipient);
        Assert.assertEquals("4169158200", address.phone);

        Assert.assertEquals("shippingId", mPayerData.selectedShippingOptionId);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void parsePaymentResponseDeprecatedDetailTest() throws Throwable {
        Intent intent = new Intent();
        Bundle extras = new Bundle();
        extras.putString(
                WebPaymentIntentHelper.EXTRA_DEPRECATED_RESPONSE_INSTRUMENT_DETAILS,
                "\"key\":\"value\"}");
        extras.putString(WebPaymentIntentHelper.EXTRA_RESPONSE_METHOD_NAME, "maxPay");
        intent.putExtras(extras);
        mErrorString = null;
        WebPaymentIntentHelper.parsePaymentResponse(
                Activity.RESULT_OK,
                intent,
                /* requestedPaymentOptions= */ null,
                (errorString) -> Assert.fail("Parsing should succeed."),
                (methodName, details, payerData) -> {
                    mMethodName = methodName;
                    mDetails = details;
                });
        Assert.assertEquals("maxPay", mMethodName);
        Assert.assertEquals("\"key\":\"value\"}", mDetails);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void createIsReadyToPayIntentWithIdentity() throws Throwable {
        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData =
                new PaymentMethodData("bobPayMethod", "{\"key\":\"value\"}");
        PaymentMethodData maxPayMethodData = new PaymentMethodData("maxPayMethod", "{}");
        methodDataMap.put("bobPay", bobPayMethodData);
        methodDataMap.put("maxPay", maxPayMethodData);

        byte[][] certificateChain = new byte[][] {{0}};

        Intent intent =
                WebPaymentIntentHelper.createIsReadyToPayIntent(
                        "package.name",
                        "service.name",
                        "schemeless.origin",
                        "schemeless.iframe.origin",
                        certificateChain,
                        methodDataMap,
                        /* clearIdFields= */ false);
        Assert.assertEquals("package.name", intent.getComponent().getPackageName());
        Assert.assertEquals("service.name", intent.getComponent().getClassName());
        Bundle bundle = intent.getExtras();
        Assert.assertNotNull(bundle);
        Assert.assertEquals(
                "schemeless.origin", bundle.get(WebPaymentIntentHelper.EXTRA_TOP_ORIGIN));
        Assert.assertEquals(
                "schemeless.iframe.origin",
                bundle.get(WebPaymentIntentHelper.EXTRA_PAYMENT_REQUEST_ORIGIN));

        Parcelable[] certificateChainParcels =
                bundle.getParcelableArray(WebPaymentIntentHelper.EXTRA_TOP_CERTIFICATE_CHAIN);
        Assert.assertEquals(1, certificateChainParcels.length);
        assertThat(
                        ((Bundle) certificateChainParcels[0])
                                .getByteArray(WebPaymentIntentHelper.EXTRA_CERTIFICATE))
                .isEqualTo(new byte[] {0});

        Assert.assertEquals(
                new HashSet(Arrays.asList("bobPay", "maxPay")),
                new HashSet(bundle.getStringArrayList(WebPaymentIntentHelper.EXTRA_METHOD_NAMES)));

        Bundle methodDataBundle = bundle.getParcelable(WebPaymentIntentHelper.EXTRA_METHOD_DATA);
        Assert.assertEquals(2, methodDataBundle.keySet().size());
        Assert.assertEquals("{\"key\":\"value\"}", methodDataBundle.getString("bobPay"));
        Assert.assertEquals("{}", methodDataBundle.getString("maxPay"));
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void createIsReadyToPayIntentWithoutIdentity() throws Throwable {
        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData =
                new PaymentMethodData("bobPayMethod", "{\"key\":\"value\"}");
        PaymentMethodData maxPayMethodData = new PaymentMethodData("maxPayMethod", "{}");
        methodDataMap.put("bobPay", bobPayMethodData);
        methodDataMap.put("maxPay", maxPayMethodData);

        byte[][] certificateChain = new byte[][] {{0}};

        Intent intent =
                WebPaymentIntentHelper.createIsReadyToPayIntent(
                        "package.name",
                        "service.name",
                        "schemeless.origin",
                        "schemeless.iframe.origin",
                        certificateChain,
                        methodDataMap,
                        /* clearIdFields= */ true);
        Assert.assertEquals("package.name", intent.getComponent().getPackageName());
        Assert.assertEquals("service.name", intent.getComponent().getClassName());
        Bundle bundle = intent.getExtras();
        Assert.assertNotNull(bundle);
        Assert.assertEquals(null, bundle.get(WebPaymentIntentHelper.EXTRA_TOP_ORIGIN));
        Assert.assertEquals(null, bundle.get(WebPaymentIntentHelper.EXTRA_PAYMENT_REQUEST_ORIGIN));

        Parcelable[] certificateChainParcels =
                bundle.getParcelableArray(WebPaymentIntentHelper.EXTRA_TOP_CERTIFICATE_CHAIN);
        Assert.assertEquals(null, certificateChainParcels);
        Assert.assertEquals(
                null, bundle.getStringArrayList(WebPaymentIntentHelper.EXTRA_METHOD_NAMES));

        Assert.assertEquals(null, bundle.getParcelable(WebPaymentIntentHelper.EXTRA_METHOD_DATA));
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void createIsReadyToPayIntentNullPackageNameExceptionTestWithIdentity()
            throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("packageName should not be null or empty.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createIsReadyToPayIntent(
                /* packageName= */ null,
                "service.name",
                "schemeless.origin",
                "schemeless.iframe.origin",
                /* certificateChain= */ null,
                methodDataMap,
                /* clearIdFields= */ false);
    }

    @Test
    @SmallTest
    @Feature({"Payments"})
    public void createIsReadyToPayIntentNullPackageNameExceptionTestWithoutIdentity()
            throws Throwable {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("packageName should not be null or empty.");

        Map<String, PaymentMethodData> methodDataMap = new HashMap<String, PaymentMethodData>();
        PaymentMethodData bobPayMethodData = new PaymentMethodData("method", "null");
        methodDataMap.put("bobPay", bobPayMethodData);

        PaymentItem total = new PaymentItem(new PaymentCurrencyAmount("CAD", "200"));

        WebPaymentIntentHelper.createIsReadyToPayIntent(
                /* packageName= */ null,
                "service.name",
                "schemeless.origin",
                "schemeless.iframe.origin",
                /* certificateChain= */ null,
                methodDataMap,
                /* clearIdFields= */ true);
    }
}