1 // Copyright 2020 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.payments; 6 7 import android.graphics.Bitmap; 8 import android.graphics.Color; 9 import android.graphics.drawable.BitmapDrawable; 10 11 import androidx.test.filters.MediumTest; 12 13 import org.junit.Assert; 14 import org.junit.ClassRule; 15 import org.junit.Rule; 16 import org.junit.Test; 17 import org.junit.runner.RunWith; 18 19 import org.chromium.base.test.util.CommandLineFlags; 20 import org.chromium.base.test.util.Feature; 21 import org.chromium.chrome.R; 22 import org.chromium.chrome.browser.app.ChromeActivity; 23 import org.chromium.chrome.browser.autofill.AutofillTestHelper; 24 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile; 25 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard; 26 import org.chromium.chrome.browser.flags.ChromeSwitches; 27 import org.chromium.chrome.test.ChromeJUnit4ClassRunner; 28 import org.chromium.components.payments.PaymentAppFactoryDelegate; 29 import org.chromium.components.payments.PaymentAppFactoryInterface; 30 import org.chromium.components.payments.PaymentAppService; 31 import org.chromium.components.payments.PaymentFeatureList; 32 import org.chromium.components.payments.SupportedDelegations; 33 import org.chromium.content_public.browser.WebContents; 34 import org.chromium.ui.test.util.DisableAnimationsTestRule; 35 36 import java.util.concurrent.TimeoutException; 37 38 /** 39 * A payment integration test for service worker based payment apps. 40 */ 41 @RunWith(ChromeJUnit4ClassRunner.class) 42 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, 43 // For all the tests in this file, we expect abort exception when there is no supported 44 // payment apps instead of showing payment request UI. 45 "enable-features=" + PaymentFeatureList.STRICT_HAS_ENROLLED_AUTOFILL_INSTRUMENT + "," 46 + PaymentFeatureList.SCROLL_TO_EXPAND_PAYMENT_HANDLER, 47 // Prevent crawling the web for real payment apps. 48 "disable-features=" + PaymentFeatureList.SERVICE_WORKER_PAYMENT_APPS}) 49 public class PaymentRequestServiceWorkerExpandablePaymentHandlerTest { 50 // Disable animations to reduce flakiness. 51 @ClassRule 52 public static DisableAnimationsTestRule sNoAnimationsRule = new DisableAnimationsTestRule(); 53 54 @Rule 55 public PaymentRequestTestRule mPaymentRequestTestRule = new PaymentRequestTestRule( 56 "payment_request_bobpay_and_basic_card_with_modifier_optional_data_test.html"); 57 58 /** 59 * Installs a mock service worker based payment app with given supported delegations for 60 * testing. 61 * 62 * @param scope Service worker scope that identifies the payment app. Must be 63 * unique. 64 * @param supportedMethodNames The supported payment methods of the mock payment app. 65 * @param name The name of the mocked payment app. 66 * @param withIcon Whether provide payment app icon. 67 * @param supportedDelegations The supported delegations of the mock payment app. 68 */ installMockServiceWorkerPaymentApp(String scope, String[] supportedMethodNames, String name, boolean withIcon, SupportedDelegations supportedDelegations)69 private void installMockServiceWorkerPaymentApp(String scope, String[] supportedMethodNames, 70 String name, boolean withIcon, SupportedDelegations supportedDelegations) { 71 PaymentAppService.getInstance().addFactory(new PaymentAppFactoryInterface() { 72 @Override 73 public void create(PaymentAppFactoryDelegate delegate) { 74 WebContents webContents = delegate.getParams().getWebContents(); 75 ChromeActivity activity = ChromeActivity.fromWebContents(webContents); 76 BitmapDrawable icon = withIcon 77 ? new BitmapDrawable(activity.getResources(), 78 Bitmap.createBitmap(new int[] {Color.RED}, 1 /* width */, 79 1 /* height */, Bitmap.Config.ARGB_8888)) 80 : null; 81 delegate.onCanMakePaymentCalculated(true); 82 delegate.onPaymentAppCreated(new MockPaymentApp(/*identifier=*/scope, name, icon, 83 supportedMethodNames, supportedDelegations)); 84 delegate.onDoneCreatingPaymentApps(this); 85 } 86 }); 87 } 88 89 /** 90 * Installs a mock service worker based payment app with no supported delegations for testing. 91 * 92 * @param scope The service worker scope that identifies this payment app. Must 93 * be unique. 94 * @param supportedMethodNames The supported payment methods of the mock payment app. 95 * @param withName Whether provide payment app name. 96 * @param withIcon Whether provide payment app icon. 97 */ installMockServiceWorkerPaymentApp( String scope, String[] supportedMethodNames, boolean withName, boolean withIcon)98 private void installMockServiceWorkerPaymentApp( 99 String scope, String[] supportedMethodNames, boolean withName, boolean withIcon) { 100 installMockServiceWorkerPaymentApp(scope, supportedMethodNames, withName ? "BobPay" : null, 101 withIcon, new SupportedDelegations()); 102 } 103 104 /** 105 * Installs a mock service worker based payment app for bobpay with given supported delegations 106 * for testing. 107 * 108 * @param scope The service worker scope that identifies this payment app. Must be 109 * unique. 110 * @param shippingAddress Whether or not the mock payment app provides shipping address. 111 * @param payerName Whether or not the mock payment app provides payer's name. 112 * @param payerPhone Whether or not the mock payment app provides payer's phone number. 113 * @param payerEmail Whether or not the mock payment app provides payer's email address. 114 * @param name The name of the mocked payment app. 115 */ installMockServiceWorkerPaymentAppWithDelegations(String scope, boolean shippingAddress, boolean payerName, boolean payerPhone, boolean payerEmail, String name)116 private void installMockServiceWorkerPaymentAppWithDelegations(String scope, 117 boolean shippingAddress, boolean payerName, boolean payerPhone, boolean payerEmail, 118 String name) { 119 String[] supportedMethodNames = {"https://bobpay.xyz"}; 120 installMockServiceWorkerPaymentApp(scope, supportedMethodNames, name, true /*withIcon*/, 121 new SupportedDelegations(shippingAddress, payerName, payerPhone, payerEmail)); 122 } 123 124 /** 125 * Adds a credit cart to ensure that autofill app is available. 126 */ addCreditCard()127 public void addCreditCard() throws TimeoutException { 128 AutofillTestHelper helper = new AutofillTestHelper(); 129 String billingAddressId = helper.setProfile( 130 new AutofillProfile("", "https://example.com", true, "" /* honorific prefix */, 131 "John Smith", "Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", 132 "US", "310-310-6000", "john.smith@gmail.com", "en-US")); 133 helper.setCreditCard(new CreditCard("", "https://example.com", true, true, "Jon Doe", 134 "4111111111111111", "1111", "12", "2050", "visa", R.drawable.visa_card, 135 billingAddressId, "" /* serverId */)); 136 } 137 138 @Test 139 @MediumTest 140 @Feature({"Payments"}) testNoSupportedPaymentMethods()141 public void testNoSupportedPaymentMethods() throws TimeoutException { 142 mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getShowFailed()); 143 mPaymentRequestTestRule.expectResultContains( 144 new String[] {"show() rejected", "The payment method", "not supported"}); 145 } 146 147 @Test 148 @MediumTest 149 @Feature({"Payments"}) testHasSupportedPaymentMethods()150 public void testHasSupportedPaymentMethods() throws TimeoutException { 151 String[] supportedMethodNames = {"https://bobpay.com"}; 152 installMockServiceWorkerPaymentApp("https://bobpay.com", supportedMethodNames, true, true); 153 154 PaymentAppServiceBridge.setCanMakePaymentForTesting(true); 155 // Payment sheet skips to the app since it is the only available app. 156 mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getDismissed()); 157 } 158 159 @Test 160 @MediumTest 161 @Feature({"Payments"}) testDoNotCallCanMakePayment()162 public void testDoNotCallCanMakePayment() throws TimeoutException { 163 // Add a credit card to force showing payment sheet UI. 164 addCreditCard(); 165 String[] supportedMethodNames = {"basic-card"}; 166 installMockServiceWorkerPaymentApp("https://bobpay.com", supportedMethodNames, true, true); 167 168 // Sets setCanMakePaymentForTesting(false) to return false for CanMakePayment since there is 169 // no real sw payment app, so if CanMakePayment is called then no payment apps will be 170 // available, otherwise CanMakePayment is not called. 171 PaymentAppServiceBridge.setCanMakePaymentForTesting(false); 172 173 mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput()); 174 Assert.assertEquals(2, mPaymentRequestTestRule.getNumberOfPaymentApps()); 175 } 176 177 @Test 178 @MediumTest 179 @Feature({"Payments"}) testCanPreselect()180 public void testCanPreselect() throws TimeoutException { 181 String[] supportedMethodNames = {"https://bobpay.com"}; 182 installMockServiceWorkerPaymentApp("https://bobpay.com", supportedMethodNames, true, true); 183 184 PaymentAppServiceBridge.setCanMakePaymentForTesting(true); 185 186 // Payment sheet skips to the app since it is the only available app. 187 mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getDismissed()); 188 } 189 190 @Test 191 @MediumTest 192 @Feature({"Payments"}) testCanNotPreselectWithoutName()193 public void testCanNotPreselectWithoutName() throws TimeoutException { 194 String[] supportedMethodNames = {"https://bobpay.com"}; 195 installMockServiceWorkerPaymentApp("https://bobpay.com", supportedMethodNames, false, true); 196 197 PaymentAppServiceBridge.setCanMakePaymentForTesting(true); 198 199 mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput()); 200 Assert.assertNull(mPaymentRequestTestRule.getSelectedPaymentAppLabel()); 201 } 202 203 @Test 204 @MediumTest 205 @Feature({"Payments"}) testCanNotPreselectWithoutIcon()206 public void testCanNotPreselectWithoutIcon() throws TimeoutException { 207 String[] supportedMethodNames = {"https://bobpay.com"}; 208 installMockServiceWorkerPaymentApp("https://bobpay.com", supportedMethodNames, true, false); 209 210 PaymentAppServiceBridge.setCanMakePaymentForTesting(true); 211 212 mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput()); 213 Assert.assertNull(mPaymentRequestTestRule.getSelectedPaymentAppLabel()); 214 } 215 216 @Test 217 @MediumTest 218 @Feature({"Payments"}) testCanNotPreselectWithoutNameAndIcon()219 public void testCanNotPreselectWithoutNameAndIcon() throws TimeoutException { 220 String[] supportedMethodNames = {"https://bobpay.com"}; 221 installMockServiceWorkerPaymentApp( 222 "https://bobpay.com", supportedMethodNames, false, false); 223 224 PaymentAppServiceBridge.setCanMakePaymentForTesting(true); 225 226 mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput()); 227 Assert.assertNull(mPaymentRequestTestRule.getSelectedPaymentAppLabel()); 228 } 229 230 @Test 231 @MediumTest 232 @Feature({"Payments"}) testPaymentAppProvidingShippingComesFirst()233 public void testPaymentAppProvidingShippingComesFirst() throws TimeoutException { 234 installMockServiceWorkerPaymentAppWithDelegations("https://alicepay.com" /*scope*/, 235 false /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/, 236 false /*payerEmail*/, "noSupportedDelegation" /*name*/); 237 installMockServiceWorkerPaymentAppWithDelegations("https://bobpay.com" /*scope*/, 238 true /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/, 239 false /*payerEmail*/, "shippingSupported1" /*name */); 240 // Install the second app supporting shipping delegation to force showing payment sheet. 241 installMockServiceWorkerPaymentAppWithDelegations("https://charliepay.com" /*scope*/, 242 true /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/, 243 false /*payerEmail*/, "shippingSupported2" /*name */); 244 245 PaymentAppServiceBridge.setCanMakePaymentForTesting(true); 246 247 mPaymentRequestTestRule.triggerUIAndWait( 248 "buy_with_shipping_requested", mPaymentRequestTestRule.getReadyForInput()); 249 Assert.assertEquals(3, mPaymentRequestTestRule.getNumberOfPaymentApps()); 250 251 // The payment app which provides shipping address must be preselected. 252 Assert.assertTrue( 253 mPaymentRequestTestRule.getSelectedPaymentAppLabel().contains("shippingSupported")); 254 } 255 256 @Test 257 @MediumTest 258 @Feature({"Payments"}) testPaymentAppProvidingContactComesFirst()259 public void testPaymentAppProvidingContactComesFirst() throws TimeoutException { 260 installMockServiceWorkerPaymentAppWithDelegations("https://alicepay.com" /*scope*/, 261 false /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/, 262 false /*payerEmail*/, "noSupportedDelegation" /*name*/); 263 installMockServiceWorkerPaymentAppWithDelegations("https://bobpay.com" /*scope*/, 264 false /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/, 265 true /*payerEmail*/, "contactSupported" /*name */); 266 installMockServiceWorkerPaymentAppWithDelegations("https://charliepay.com" /*scope*/, 267 false /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/, 268 true /*payerEmail*/, "emailOnlySupported" /*name */); 269 // Install the second app supporting contact delegation to force showing payment sheet. 270 installMockServiceWorkerPaymentAppWithDelegations("https://davepay.com" /*scope*/, 271 false /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/, 272 true /*payerEmail*/, "contactSupported2" /*name */); 273 274 PaymentAppServiceBridge.setCanMakePaymentForTesting(true); 275 276 mPaymentRequestTestRule.triggerUIAndWait( 277 "buy_with_contact_requested", mPaymentRequestTestRule.getReadyForInput()); 278 Assert.assertEquals(4, mPaymentRequestTestRule.getNumberOfPaymentApps()); 279 280 // The payment app which provides full contact details must be preselected. 281 Assert.assertTrue( 282 mPaymentRequestTestRule.getSelectedPaymentAppLabel().contains("contactSupported")); 283 // The payment app which partially provides the required contact details comes before the 284 // one that provides no contact information. 285 Assert.assertTrue(mPaymentRequestTestRule.getPaymentMethodSuggestionLabel(2).contains( 286 "emailOnlySupported")); 287 } 288 289 @Test 290 @MediumTest 291 @Feature({"Payments"}) testPaymentAppProvidingAllRequiredInfoComesFirst()292 public void testPaymentAppProvidingAllRequiredInfoComesFirst() throws TimeoutException { 293 installMockServiceWorkerPaymentAppWithDelegations("https://alicepay.com" /*scope*/, 294 true /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/, 295 false /*payerEmail*/, "shippingSupported" /*name */); 296 installMockServiceWorkerPaymentAppWithDelegations("https://bobpay.com" /*scope*/, 297 false /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/, 298 true /*payerEmail*/, "contactSupported" /*name */); 299 installMockServiceWorkerPaymentAppWithDelegations("https://charliepay.com" /*scope*/, 300 true /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/, 301 true /*payerEmail*/, "shippingAndContactSupported" /*name*/); 302 // Install the second app supporting both shipping and contact delegations to force showing 303 // payment sheet. 304 installMockServiceWorkerPaymentAppWithDelegations("https://davepay.com" /*scope*/, 305 true /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/, 306 true /*payerEmail*/, "shippingAndContactSupported2" /*name*/); 307 308 PaymentAppServiceBridge.setCanMakePaymentForTesting(true); 309 310 mPaymentRequestTestRule.triggerUIAndWait("buy_with_shipping_and_contact_requested", 311 mPaymentRequestTestRule.getReadyForInput()); 312 Assert.assertEquals(4, mPaymentRequestTestRule.getNumberOfPaymentApps()); 313 314 // The payment app which provides all required information must be preselected. 315 Assert.assertTrue(mPaymentRequestTestRule.getSelectedPaymentAppLabel().contains( 316 "shippingAndContactSupported")); 317 // The payment app which provides shipping comes before the one which provides contact 318 // details when both required by merchant. 319 Assert.assertTrue(mPaymentRequestTestRule.getPaymentMethodSuggestionLabel(2).contains( 320 "shippingSupported")); 321 } 322 323 @Test 324 @MediumTest 325 @Feature({"Payments"}) testSkipsToSinglePaymentAppProvidingShipping()326 public void testSkipsToSinglePaymentAppProvidingShipping() throws TimeoutException { 327 installMockServiceWorkerPaymentAppWithDelegations("https://alicepay.com" /*scope*/, 328 false /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/, 329 false /*payerEmail*/, "noSupportedDelegation" /*name*/); 330 installMockServiceWorkerPaymentAppWithDelegations("https://bobpay.com" /*scope*/, 331 true /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/, 332 false /*payerEmail*/, "shippingSupported" /*name */); 333 334 PaymentAppServiceBridge.setCanMakePaymentForTesting(true); 335 mPaymentRequestTestRule.openPageAndClickNodeAndWait( 336 "buy_with_shipping_requested", mPaymentRequestTestRule.getDismissed()); 337 } 338 339 @Test 340 @MediumTest 341 @Feature({"Payments"}) testSkipsToSinglePaymentAppProvidingContact()342 public void testSkipsToSinglePaymentAppProvidingContact() throws TimeoutException { 343 installMockServiceWorkerPaymentAppWithDelegations("https://alicepay.com" /*scope*/, 344 false /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/, 345 false /*payerEmail*/, "noSupportedDelegation" /*name*/); 346 installMockServiceWorkerPaymentAppWithDelegations("https://bobpay.com" /*scope*/, 347 false /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/, 348 true /*payerEmail*/, "contactSupported" /*name */); 349 installMockServiceWorkerPaymentAppWithDelegations("https://charliepay.com" /*scope*/, 350 false /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/, 351 true /*payerEmail*/, "emailOnlySupported" /*name */); 352 353 PaymentAppServiceBridge.setCanMakePaymentForTesting(true); 354 mPaymentRequestTestRule.openPageAndClickNodeAndWait( 355 "buy_with_contact_requested", mPaymentRequestTestRule.getDismissed()); 356 } 357 358 @Test 359 @MediumTest 360 @Feature({"Payments"}) testSkipsToSinglePaymentAppProvidingAllRequiredInfo()361 public void testSkipsToSinglePaymentAppProvidingAllRequiredInfo() throws TimeoutException { 362 installMockServiceWorkerPaymentAppWithDelegations("https://alicepay.com" /*scope*/, 363 true /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/, 364 false /*payerEmail*/, "shippingSupported" /*name */); 365 installMockServiceWorkerPaymentAppWithDelegations("https://bobpay.com" /*scope*/, 366 false /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/, 367 true /*payerEmail*/, "contactSupported" /*name */); 368 installMockServiceWorkerPaymentAppWithDelegations("https://charliepay.com" /*scope*/, 369 true /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/, 370 true /*payerEmail*/, "shippingAndContactSupported" /*name*/); 371 372 PaymentAppServiceBridge.setCanMakePaymentForTesting(true); 373 mPaymentRequestTestRule.openPageAndClickNodeAndWait( 374 "buy_with_shipping_and_contact_requested", mPaymentRequestTestRule.getDismissed()); 375 } 376 } 377