Commit 55ebeca0 authored by Rouslan Solomakhin's avatar Rouslan Solomakhin Committed by Commit Bot

[Web Payment] Stop "canmakepayment" firing for short string methods.

Before this patch, a service worker on Android that declared support
for "interledger", "basic-card", and one URL-based payment method
would receive a "canmakepayment" event from a Payment Request for
"interledger".

This patch skips sending "canmakepayment" to the service worker on
Android if the Payment Request is only for standardized payment methods.

After this patch, "canmakepayment" events are sent only for URL-based
payment methods (that have been explicitly verified).

Bug: 1040994
Change-Id: I3d908e6b6e628b3ca7290adfed394a7be6041f6a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1994305Reviewed-by: default avatarSahel Sharify <sahel@chromium.org>
Commit-Queue: Rouslan Solomakhin <rouslan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#732859}
parent 0a9b6224
...@@ -221,11 +221,11 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen ...@@ -221,11 +221,11 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
public void getInstruments(String id, Map<String, PaymentMethodData> methodDataMap, public void getInstruments(String id, Map<String, PaymentMethodData> methodDataMap,
String origin, String iframeOrigin, byte[][] unusedCertificateChain, String origin, String iframeOrigin, byte[][] unusedCertificateChain,
Map<String, PaymentDetailsModifier> modifiers, final InstrumentsCallback callback) { Map<String, PaymentDetailsModifier> modifiers, final InstrumentsCallback callback) {
// Do not send canMakePayment event when in incognito mode or basic-card is the only // Do not send canMakePayment event when in incognito mode or only standardized payment
// supported payment method or this app needs installation for the payment request or this // methods are supported or this app needs installation for the payment request or this app
// app has not been explicitly verified. // has not been explicitly verified.
if (mIsIncognito || isOnlySupportBasiccard(methodDataMap) || mNeedsInstallation if (mIsIncognito || isOnlySupportStandardizedPaymentMethods(methodDataMap)
|| !mExplicitlyVerified) { || mNeedsInstallation || !mExplicitlyVerified) {
new Handler().post(() -> { new Handler().post(() -> {
List<PaymentInstrument> instruments = List<PaymentInstrument> instruments =
Collections.singletonList(ServiceWorkerPaymentApp.this); Collections.singletonList(ServiceWorkerPaymentApp.this);
...@@ -244,12 +244,19 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen ...@@ -244,12 +244,19 @@ public class ServiceWorkerPaymentApp extends PaymentInstrument implements Paymen
}); });
} }
// Returns true if 'basic-card' is the only supported payment method of this payment app in the // Returns true if only standardized payment methods are supported.
// payment request. private boolean isOnlySupportStandardizedPaymentMethods(
private boolean isOnlySupportBasiccard(Map<String, PaymentMethodData> methodDataMap) { Map<String, PaymentMethodData> methodDataMap) {
Set<String> requestMethods = new HashSet<>(methodDataMap.keySet()); Set<String> requestMethods = new HashSet<>(methodDataMap.keySet());
requestMethods.retainAll(mMethodNames); requestMethods.retainAll(mMethodNames);
return requestMethods.size() == 1 && requestMethods.contains(MethodStrings.BASIC_CARD); assert !requestMethods.isEmpty();
Set<String> standardizedPaymentMethods = new HashSet<>();
standardizedPaymentMethods.add(MethodStrings.BASIC_CARD);
standardizedPaymentMethods.add(MethodStrings.INTERLEDGER);
standardizedPaymentMethods.add(MethodStrings.PAYEE_CREDIT_TRANSFER);
standardizedPaymentMethods.add(MethodStrings.PAYER_CREDIT_TRANSFER);
standardizedPaymentMethods.add(MethodStrings.TOKENIZED_CARD);
return standardizedPaymentMethods.containsAll(requestMethods);
} }
// Matches ||requestMethodData|.supportedNetwokrs for 'basic-card' payment method with the // Matches ||requestMethodData|.supportedNetwokrs for 'basic-card' payment method with the
......
// Copyright 2020 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.
#include "build/build_config.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/payments/payment_request_test_controller.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/payments/core/features.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h"
#include "net/dns/mock_host_resolver.h"
#if defined(OS_ANDROID)
#include "chrome/test/base/android/android_browser_test.h"
#else
#include "chrome/test/base/in_process_browser_test.h"
#endif // defined(OS_ANDROID)
// This test suite verifies that the the "canmakepayment" event does not fire
// for standardized payment methods. The test uses hasEnrolledInstrument() which
// returns "false" for standardized payment methods when "canmakepayment" is
// suppressed on desktop, "true" on Android. The platform discrepancy is tracked
// in https://crbug.com/994799 and should be solved in
// https://crbug.com/1022512.
namespace payments {
#if defined(OS_ANDROID)
static constexpr char kTestFileName[] = "can_make_payment_false_responder.js";
static constexpr char kExpectedResult[] = "true";
#else
static constexpr char kTestFileName[] = "can_make_payment_true_responder.js";
static constexpr char kExpectedResult[] = "false";
#endif // defined(OS_ANDROID)
class PaymentRequestCanMakePaymentEventTest : public PlatformBrowserTest {
public:
PaymentRequestCanMakePaymentEventTest() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
// HTTPS server only serves a valid cert for localhost, so this is needed to
// load pages from "a.com" without an interstitial.
command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
https_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
ASSERT_TRUE(https_server_->InitializeAndListen());
https_server_->ServeFilesFromSourceDirectory(
"components/test/data/payments");
https_server_->StartAcceptingConnections();
}
content::WebContents* GetActiveWebContents() {
return chrome_test_utils::GetActiveWebContents(this);
}
void NavigateTo(const std::string& host, const std::string& file_path) {
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
https_server_->GetURL(host, file_path)));
}
std::string GetPaymentMethodForHost(const std::string& host) {
return https_server_->GetURL(host, "/").spec();
}
private:
std::unique_ptr<net::EmbeddedTestServer> https_server_;
};
// A payment handler with two standardized payment methods ("interledger" and
// "basic-card") and one URL-based payment method (its own scope) does not
// receive a "canmakepayment" event from a PaymentRequest for "interledger"
// payment method.
IN_PROC_BROWSER_TEST_F(PaymentRequestCanMakePaymentEventTest,
TwoStandardOneUrl) {
NavigateTo("a.com", "/payment_handler_installer.html");
EXPECT_EQ("success",
content::EvalJs(GetActiveWebContents(),
"install('" + std::string(kTestFileName) +
"', ['interledger', 'basic-card'], true)"));
NavigateTo("b.com", "/has_enrolled_instrument_checker.html");
EXPECT_EQ(kExpectedResult,
content::EvalJs(GetActiveWebContents(),
"hasEnrolledInstrument('interledger')"));
}
// A payment handler with two standardized payment methods ("interledger" and
// "basic-card") does not receive a "canmakepayment" event from a PaymentRequest
// for "interledger" payment method.
IN_PROC_BROWSER_TEST_F(PaymentRequestCanMakePaymentEventTest, TwoStandard) {
NavigateTo("a.com", "/payment_handler_installer.html");
EXPECT_EQ("success",
content::EvalJs(GetActiveWebContents(),
"install('" + std::string(kTestFileName) +
"', ['interledger', 'basic-card'], false)"));
NavigateTo("b.com", "/has_enrolled_instrument_checker.html");
EXPECT_EQ(kExpectedResult,
content::EvalJs(GetActiveWebContents(),
"hasEnrolledInstrument('interledger')"));
}
// A payment handler with one standardized payment method ("interledger") does
// not receive a "canmakepayment" event from a PaymentRequest for "interledger"
// payment method.
IN_PROC_BROWSER_TEST_F(PaymentRequestCanMakePaymentEventTest, OneStandard) {
NavigateTo("a.com", "/payment_handler_installer.html");
EXPECT_EQ("success",
content::EvalJs(GetActiveWebContents(),
"install('" + std::string(kTestFileName) +
"', ['interledger'], false)"));
NavigateTo("b.com", "/has_enrolled_instrument_checker.html");
EXPECT_EQ(kExpectedResult,
content::EvalJs(GetActiveWebContents(),
"hasEnrolledInstrument('interledger')"));
}
} // namespace payments
...@@ -532,6 +532,7 @@ if (is_android) { ...@@ -532,6 +532,7 @@ if (is_android) {
"../browser/payments/payment_handler_exploit_browsertest.cc", "../browser/payments/payment_handler_exploit_browsertest.cc",
"../browser/payments/payment_handler_just_in_time_installation_browsertest.cc", "../browser/payments/payment_handler_just_in_time_installation_browsertest.cc",
"../browser/payments/payment_request_can_make_payment_browsertest.cc", "../browser/payments/payment_request_can_make_payment_browsertest.cc",
"../browser/payments/payment_request_can_make_payment_event_browsertest.cc",
"../browser/payments/personal_data_manager_test_util.cc", "../browser/payments/personal_data_manager_test_util.cc",
"../browser/payments/personal_data_manager_test_util.h", "../browser/payments/personal_data_manager_test_util.h",
"../browser/ssl/crlset_browsertest.cc", "../browser/ssl/crlset_browsertest.cc",
...@@ -1905,6 +1906,7 @@ if (!is_android) { ...@@ -1905,6 +1906,7 @@ if (!is_android) {
"../browser/payments/payment_handler_just_in_time_installation_browsertest.cc", "../browser/payments/payment_handler_just_in_time_installation_browsertest.cc",
"../browser/payments/payment_manifest_parser_browsertest.cc", "../browser/payments/payment_manifest_parser_browsertest.cc",
"../browser/payments/payment_request_can_make_payment_browsertest.cc", "../browser/payments/payment_request_can_make_payment_browsertest.cc",
"../browser/payments/payment_request_can_make_payment_event_browsertest.cc",
"../browser/payments/personal_data_manager_test_util.cc", "../browser/payments/personal_data_manager_test_util.cc",
"../browser/payments/personal_data_manager_test_util.h", "../browser/payments/personal_data_manager_test_util.h",
"../browser/payments/service_worker_payment_app_finder_browsertest.cc", "../browser/payments/service_worker_payment_app_finder_browsertest.cc",
......
/*
* Copyright 2020 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.
*/
self.addEventListener('canmakepayment', (evt) => {
evt.respondWith(false);
});
/*
* Copyright 2020 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.
*/
self.addEventListener('canmakepayment', (evt) => {
evt.respondWith(true);
});
<!DOCTYPE html>
<!--
Copyright 2020 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.
-->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
<title>Has Enrolled Instrument Checker</title>
</head>
<body>
<script src="has_enrolled_instrument_checker.js"></script>
</body>
</html>
/*
* Copyright 2020 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.
*/
/**
* Checks whether the given payment method has an enrolled instrument.
* @param {string} method - The payment method identifier to check.
* @return {string} - 'true', 'false', or error message on failure.
*/
async function hasEnrolledInstrument(method) { // eslint-disable-line no-unused-vars, max-len
try {
const request = new PaymentRequest(
[{supportedMethods: method}],
{total: {label: 'TEST', amount: {currency: 'USD', value: '0.01'}}});
const result = await request.hasEnrolledInstrument();
return result ? 'true' : 'false';
} catch (e) {
return e.message;
}
}
<!DOCTYPE html>
<!--
Copyright 2020 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.
-->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
<title>Payment Handler Installer</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<script src="payment_handler_installer.js"></script>
</body>
</html>
/*
* Copyright 2020 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.
*/
/**
* Installs the given payment handler with the given payment methods.
* @param {string} swUrl - The relative URL of the service worker JavaScript
* file to install.
* @param {string[]} methods - The list of payment methods that this service
* worker supports.
* @param {bool} ownScopeMethod - Whether this service worker should support its
* own scope as a payment method.
* @return {string} - 'success' or error message on failure.
*/
async function install(swUrl, methods, ownScopeMethod) { // eslint-disable-line no-unused-vars, max-len
try {
await navigator.serviceWorker.register(swUrl);
const registration = await navigator.serviceWorker.ready;
for (let method of methods) {
await registration.paymentManager.instruments.set(
'instrument-for-' + method, {name: 'Instrument Name', method});
}
if (ownScopeMethod) {
await registration.paymentManager.instruments.set(
'instrument-for-own-scope',
{name: 'Instrument Name', method: registration.scope});
}
return 'success';
} catch (e) {
return e.message;
}
}
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