Commit 72fd06da authored by Rouslan Solomakhin's avatar Rouslan Solomakhin Committed by Commit Bot

[Web Pay][Chrome OS] Test for ignoring non-TWA payment apps.

This patch adds a browser test for ignoring non-TWA payment apps when
running inside of a TWA that provides app store payment capability to
Chrome. The test is for Chrome OS only, which is using the
cross-platform Android payment app factory. When this factory is ported
to Android, then the same test will work on Android as well.

Bug: 1061503
Change-Id: I8f366c0feca7604000e8efc5e47d57047ab68a37
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2347400
Commit-Queue: Rouslan Solomakhin <rouslan@chromium.org>
Reviewed-by: default avatarNick Burris <nburris@chromium.org>
Cr-Commit-Position: refs/heads/master@{#798622}
parent 1aad6376
...@@ -25,16 +25,58 @@ class AndroidPaymentAppFactoryTest ...@@ -25,16 +25,58 @@ class AndroidPaymentAppFactoryTest
base::test::ScopedFeatureList feature_list_; base::test::ScopedFeatureList feature_list_;
}; };
IN_PROC_BROWSER_TEST_F(AndroidPaymentAppFactoryTest, SmokeTest) { // Even if a service worker app for app store payment method is installed, it
NavigateTo("a.com", "/app_store_billing_tests/index.html"); // should be ignored.
ASSERT_EQ("success", content::EvalJs(GetActiveWebContents(), IN_PROC_BROWSER_TEST_F(AndroidPaymentAppFactoryTest,
content::JsReplace( IgnoreInstalledPlayBillingServiceWorker) {
"addSupportedMethod($1)", NavigateTo("a.com", "/payment_handler_installer.html");
"https://play.google.com/billing")));
ASSERT_EQ("success", ASSERT_EQ("success",
content::EvalJs(GetActiveWebContents(), "createPaymentRequest()")); content::EvalJs(GetActiveWebContents(),
ASSERT_EQ("false", "install('alicepay.com/app1/app.js', "
content::EvalJs(GetActiveWebContents(), "canMakePayment()")); "['https://play.google.com/billing'], false)"));
NavigateTo("b.com", "/can_make_payment_checker.html");
ASSERT_EQ("false", content::EvalJs(
GetActiveWebContents(),
"canMakePayment('https://play.google.com/billing')"));
}
// When an app store payment method app is available in a trusted web activity,
// then ignore other payment apps, since this is considered to be a digital
// goods purchase.
IN_PROC_BROWSER_TEST_F(AndroidPaymentAppFactoryTest,
IgnoreOtherPaymentAppsInTwaWhenHaveAppStoreBilling) {
std::string method_name = https_server()->GetURL("a.com", "/").spec();
method_name = method_name.substr(0, method_name.length() - 1);
ASSERT_NE('/', method_name[method_name.length() - 1]);
NavigateTo("a.com", "/payment_handler_installer.html");
ASSERT_EQ(
"success",
content::EvalJs(
GetActiveWebContents(),
content::JsReplace(
"install('payment_request_success_responder.js', [$1], false)",
method_name)));
// The "payment_request_success_responder.js" always replies with "{status:
// success}", so the |response| here has to be distinct.
std::string response = "App store payment method app response for test.";
test_controller()->SetTwaPackageName("com.example.app");
test_controller()->SetTwaPaymentApp("https://play.google.com/billing",
"{\"status\": \"" + response + "\"}");
#if defined(OS_CHROMEOS)
std::string expected_response = response;
#else
std::string expected_response = "success";
#endif // OS_CHROMEOS
NavigateTo("b.com", "/payment_handler_status.html");
ASSERT_EQ(expected_response,
content::EvalJs(
GetActiveWebContents(),
content::JsReplace(
"getStatusList(['https://play.google.com/billing', $1])",
method_name)));
} }
} // namespace } // namespace
......
...@@ -66,6 +66,8 @@ class PaymentRequestTestController { ...@@ -66,6 +66,8 @@ class PaymentRequestTestController {
void SetValidSsl(bool valid_ssl); void SetValidSsl(bool valid_ssl);
void SetCanMakePaymentEnabledPref(bool can_make_payment_enabled); void SetCanMakePaymentEnabledPref(bool can_make_payment_enabled);
void SetTwaPackageName(const std::string& twa_package_name); void SetTwaPackageName(const std::string& twa_package_name);
void SetTwaPaymentApp(const std::string& method_name,
const std::string& response);
// Get the WebContents of the Payment Handler for testing purpose, or null if // Get the WebContents of the Payment Handler for testing purpose, or null if
// nonexistent. To guarantee a non-null return, this function should be called // nonexistent. To guarantee a non-null return, this function should be called
...@@ -120,6 +122,8 @@ class PaymentRequestTestController { ...@@ -120,6 +122,8 @@ class PaymentRequestTestController {
bool valid_ssl_ = true; bool valid_ssl_ = true;
bool can_make_payment_pref_ = true; bool can_make_payment_pref_ = true;
std::string twa_package_name_; std::string twa_package_name_;
std::string twa_payment_app_method_name_;
std::string twa_payment_app_response_;
std::vector<AppDescription> app_descriptions_; std::vector<AppDescription> app_descriptions_;
#if !defined(OS_ANDROID) #if !defined(OS_ANDROID)
......
...@@ -112,6 +112,12 @@ void PaymentRequestTestController::SetTwaPackageName( ...@@ -112,6 +112,12 @@ void PaymentRequestTestController::SetTwaPackageName(
/*skip_ui_for_basic_card=*/false, twa_package_name_); /*skip_ui_for_basic_card=*/false, twa_package_name_);
} }
void PaymentRequestTestController::SetTwaPaymentApp(
const std::string& method_name,
const std::string& response) {
// Intentionally left blank.
}
void PaymentRequestTestController::OnCanMakePaymentCalled() { void PaymentRequestTestController::OnCanMakePaymentCalled() {
if (observer_) if (observer_)
observer_->OnCanMakePaymentCalled(); observer_->OnCanMakePaymentCalled();
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "chrome/browser/payments/chrome_payment_request_delegate.h" #include "chrome/browser/payments/chrome_payment_request_delegate.h"
#include "chrome/browser/payments/payment_request_factory.h" #include "chrome/browser/payments/payment_request_factory.h"
#include "components/payments/content/android_app_communication.h"
#include "components/payments/content/payment_request.h" #include "components/payments/content/payment_request.h"
#include "components/payments/content/payment_request_web_contents_manager.h" #include "components/payments/content/payment_request_web_contents_manager.h"
#include "components/payments/core/payment_prefs.h" #include "components/payments/core/payment_prefs.h"
...@@ -158,11 +159,21 @@ void PaymentRequestTestController::SetTwaPackageName( ...@@ -158,11 +159,21 @@ void PaymentRequestTestController::SetTwaPackageName(
UpdateDelegateFactory(); UpdateDelegateFactory();
} }
void PaymentRequestTestController::SetTwaPaymentApp(
const std::string& method_name,
const std::string& response) {
twa_payment_app_method_name_ = method_name;
twa_payment_app_response_ = response;
UpdateDelegateFactory();
}
void PaymentRequestTestController::UpdateDelegateFactory() { void PaymentRequestTestController::UpdateDelegateFactory() {
SetPaymentRequestFactoryForTesting(base::BindRepeating( SetPaymentRequestFactoryForTesting(base::BindRepeating(
[](PaymentRequest::ObserverForTest* observer_for_test, [](PaymentRequest::ObserverForTest* observer_for_test,
bool is_off_the_record, bool valid_ssl, PrefService* prefs, bool is_off_the_record, bool valid_ssl, PrefService* prefs,
const std::string& twa_package_name, const std::string& twa_package_name,
const std::string& twa_payment_app_method_name,
const std::string& twa_payment_app_response,
mojo::PendingReceiver<payments::mojom::PaymentRequest> receiver, mojo::PendingReceiver<payments::mojom::PaymentRequest> receiver,
content::RenderFrameHost* render_frame_host) { content::RenderFrameHost* render_frame_host) {
content::WebContents* web_contents = content::WebContents* web_contents =
...@@ -174,12 +185,19 @@ void PaymentRequestTestController::UpdateDelegateFactory() { ...@@ -174,12 +185,19 @@ void PaymentRequestTestController::UpdateDelegateFactory() {
PaymentRequestWebContentsManager* manager = PaymentRequestWebContentsManager* manager =
PaymentRequestWebContentsManager::GetOrCreateForWebContents( PaymentRequestWebContentsManager::GetOrCreateForWebContents(
web_contents); web_contents);
if (!twa_payment_app_method_name.empty()) {
AndroidAppCommunication::GetForBrowserContext(
web_contents->GetBrowserContext())
->SetAppForTesting(twa_package_name, twa_payment_app_method_name,
twa_payment_app_response);
}
manager->CreatePaymentRequest(render_frame_host, web_contents, manager->CreatePaymentRequest(render_frame_host, web_contents,
std::move(delegate), std::move(receiver), std::move(delegate), std::move(receiver),
observer_for_test); observer_for_test);
}, },
observer_converter_.get(), is_off_the_record_, valid_ssl_, prefs_.get(), observer_converter_.get(), is_off_the_record_, valid_ssl_, prefs_.get(),
twa_package_name_)); twa_package_name_, twa_payment_app_method_name_,
twa_payment_app_response_));
} }
void PaymentRequestTestController::OnCanMakePaymentCalled() { void PaymentRequestTestController::OnCanMakePaymentCalled() {
......
...@@ -83,9 +83,14 @@ class AndroidAppCommunication : public base::SupportsUserData::Data { ...@@ -83,9 +83,14 @@ class AndroidAppCommunication : public base::SupportsUserData::Data {
const std::string& payment_request_id, const std::string& payment_request_id,
InvokePaymentAppCallback callback) = 0; InvokePaymentAppCallback callback) = 0;
// Enables the testing mode. // Allows usage of a test browser context.
virtual void SetForTesting() = 0; virtual void SetForTesting() = 0;
// Simulates having this payment app.
virtual void SetAppForTesting(const std::string& package_name,
const std::string& method,
const std::string& response) = 0;
protected: protected:
explicit AndroidAppCommunication(content::BrowserContext* context); explicit AndroidAppCommunication(content::BrowserContext* context);
......
...@@ -170,6 +170,20 @@ arc::mojom::PaymentParametersPtr CreatePaymentParameters( ...@@ -170,6 +170,20 @@ arc::mojom::PaymentParametersPtr CreatePaymentParameters(
return parameters; return parameters;
} }
std::vector<std::unique_ptr<AndroidAppDescription>> CreateAppForTesting(
const std::string& package_name,
const std::string& method_name) {
auto activity = std::make_unique<AndroidActivityDescription>();
activity->name = package_name + ".Activity";
activity->default_payment_method = method_name;
auto app = std::make_unique<AndroidAppDescription>();
app->package = package_name;
app->activities.emplace_back(std::move(activity));
std::vector<std::unique_ptr<AndroidAppDescription>> app_descriptions;
app_descriptions.emplace_back(std::move(app));
return app_descriptions;
}
// Invokes the TWA Android app in Android subsystem on Chrome OS. // Invokes the TWA Android app in Android subsystem on Chrome OS.
class AndroidAppCommunicationChromeOS : public AndroidAppCommunication { class AndroidAppCommunicationChromeOS : public AndroidAppCommunication {
public: public:
...@@ -199,6 +213,13 @@ class AndroidAppCommunicationChromeOS : public AndroidAppCommunication { ...@@ -199,6 +213,13 @@ class AndroidAppCommunicationChromeOS : public AndroidAppCommunication {
return; return;
} }
if (!package_name_for_testing_.empty()) {
std::move(callback).Run(
/*error_message=*/base::nullopt,
CreateAppForTesting(package_name_for_testing_, method_for_testing_));
return;
}
auto* payment_app_service = get_app_service_.Run(context()); auto* payment_app_service = get_app_service_.Run(context());
if (!payment_app_service) { if (!payment_app_service) {
std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps, std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps,
...@@ -252,6 +273,15 @@ class AndroidAppCommunicationChromeOS : public AndroidAppCommunication { ...@@ -252,6 +273,15 @@ class AndroidAppCommunicationChromeOS : public AndroidAppCommunication {
const std::string& payment_request_id, const std::string& payment_request_id,
InvokePaymentAppCallback callback) override { InvokePaymentAppCallback callback) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::Optional<std::string> error_message;
if (package_name_for_testing_ == package_name) {
std::move(callback).Run(error_message,
/*is_activity_result_ok=*/true,
method_for_testing_, response_for_testing_);
return;
}
auto* payment_app_service = get_app_service_.Run(context()); auto* payment_app_service = get_app_service_.Run(context());
if (!payment_app_service) { if (!payment_app_service) {
std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps, std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps,
...@@ -261,7 +291,6 @@ class AndroidAppCommunicationChromeOS : public AndroidAppCommunication { ...@@ -261,7 +291,6 @@ class AndroidAppCommunicationChromeOS : public AndroidAppCommunication {
return; return;
} }
base::Optional<std::string> error_message;
auto parameters = CreatePaymentParameters( auto parameters = CreatePaymentParameters(
package_name, activity_name, stringified_method_data, top_level_origin, package_name, activity_name, stringified_method_data, top_level_origin,
payment_request_origin, payment_request_id, &error_message); payment_request_origin, payment_request_id, &error_message);
...@@ -278,14 +307,27 @@ class AndroidAppCommunicationChromeOS : public AndroidAppCommunication { ...@@ -278,14 +307,27 @@ class AndroidAppCommunicationChromeOS : public AndroidAppCommunication {
base::BindOnce(&OnPaymentAppResponse, std::move(callback))); base::BindOnce(&OnPaymentAppResponse, std::move(callback)));
} }
// AndroidAppCommunication implementation: // AndroidAppCommunication implementation.
void SetForTesting() override { void SetForTesting() override {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
get_app_service_ = base::BindRepeating( get_app_service_ = base::BindRepeating(
&arc::ArcPaymentAppBridge::GetForBrowserContextForTesting); &arc::ArcPaymentAppBridge::GetForBrowserContextForTesting);
} }
// AndroidAppCommunication implementation.
void SetAppForTesting(const std::string& package_name,
const std::string& method,
const std::string& response) override {
package_name_for_testing_ = package_name;
method_for_testing_ = method;
response_for_testing_ = response;
}
private: private:
std::string package_name_for_testing_;
std::string method_for_testing_;
std::string response_for_testing_;
base::RepeatingCallback<arc::ArcPaymentAppBridge*(content::BrowserContext*)> base::RepeatingCallback<arc::ArcPaymentAppBridge*(content::BrowserContext*)>
get_app_service_; get_app_service_;
}; };
......
...@@ -57,6 +57,11 @@ class AndroidAppCommunicationStub : public AndroidAppCommunication { ...@@ -57,6 +57,11 @@ class AndroidAppCommunicationStub : public AndroidAppCommunication {
// AndroidAppCommunication implementation. // AndroidAppCommunication implementation.
void SetForTesting() override {} void SetForTesting() override {}
// AndroidAppCommunication implementation.
void SetAppForTesting(const std::string& package_name,
const std::string& method,
const std::string& response) override {}
}; };
} // namespace } // namespace
......
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
#include <utility> #include <utility>
#include "base/check.h"
#include "components/payments/core/method_strings.h"
#include "components/payments/core/native_error_strings.h" #include "components/payments/core/native_error_strings.h"
#include "components/payments/core/payer_data.h" #include "components/payments/core/payer_data.h"
...@@ -140,6 +142,19 @@ void AndroidPaymentApp::UpdateWith( ...@@ -140,6 +142,19 @@ void AndroidPaymentApp::UpdateWith(
void AndroidPaymentApp::OnPaymentDetailsNotUpdated() {} void AndroidPaymentApp::OnPaymentDetailsNotUpdated() {}
bool AndroidPaymentApp::IsPreferred() const {
// This class used only on Chrome OS, where the only Android payment app
// available is the trusted web application (TWA) that launched this instance
// of Chrome with a TWA specific payment method, so this app should be
// preferred.
#if !defined(OS_CHROMEOS)
NOTREACHED();
#endif // OS_CHROMEOS
DCHECK_EQ(1U, GetAppMethodNames().size());
DCHECK_EQ(methods::kGooglePlayBilling, *GetAppMethodNames().begin());
return true;
}
void AndroidPaymentApp::OnPaymentAppResponse( void AndroidPaymentApp::OnPaymentAppResponse(
Delegate* delegate, Delegate* delegate,
const base::Optional<std::string>& error_message, const base::Optional<std::string>& error_message,
......
...@@ -72,6 +72,7 @@ class AndroidPaymentApp : public PaymentApp { ...@@ -72,6 +72,7 @@ class AndroidPaymentApp : public PaymentApp {
void UpdateWith( void UpdateWith(
mojom::PaymentRequestDetailsUpdatePtr details_update) override; mojom::PaymentRequestDetailsUpdatePtr details_update) override;
void OnPaymentDetailsNotUpdated() override; void OnPaymentDetailsNotUpdated() override;
bool IsPreferred() const override;
private: private:
void OnPaymentAppResponse(Delegate* delegate, void OnPaymentAppResponse(Delegate* delegate,
......
...@@ -187,7 +187,7 @@ class PaymentApp { ...@@ -187,7 +187,7 @@ class PaymentApp {
// Whether this app should be chosen over other available payment apps. For // Whether this app should be chosen over other available payment apps. For
// example, when the Play Billing payment app is available in a TWA. // example, when the Play Billing payment app is available in a TWA.
bool IsPreferred() const; virtual bool IsPreferred() const;
protected: protected:
PaymentApp(int icon_resource_id, Type type); PaymentApp(int icon_resource_id, Type type);
......
...@@ -661,7 +661,9 @@ void PaymentRequest::OnPaymentResponseAvailable( ...@@ -661,7 +661,9 @@ void PaymentRequest::OnPaymentResponseAvailable(
case PaymentApp::Type::AUTOFILL: case PaymentApp::Type::AUTOFILL:
selected_event = JourneyLogger::Event::EVENT_SELECTED_CREDIT_CARD; selected_event = JourneyLogger::Event::EVENT_SELECTED_CREDIT_CARD;
break; break;
case PaymentApp::Type::SERVICE_WORKER_APP: { case PaymentApp::Type::SERVICE_WORKER_APP:
// Intentionally fall through.
case PaymentApp::Type::NATIVE_MOBILE_APP: {
selected_event = IsGooglePaymentMethod(response->method_name) selected_event = IsGooglePaymentMethod(response->method_name)
? JourneyLogger::Event::EVENT_SELECTED_GOOGLE ? JourneyLogger::Event::EVENT_SELECTED_GOOGLE
: JourneyLogger::Event::EVENT_SELECTED_OTHER; : JourneyLogger::Event::EVENT_SELECTED_OTHER;
...@@ -669,8 +671,6 @@ void PaymentRequest::OnPaymentResponseAvailable( ...@@ -669,8 +671,6 @@ void PaymentRequest::OnPaymentResponseAvailable(
} }
case PaymentApp::Type::UNDEFINED: case PaymentApp::Type::UNDEFINED:
// Intentionally fall through. // Intentionally fall through.
case PaymentApp::Type::NATIVE_MOBILE_APP:
// Intentionally fall through.
case PaymentApp::Type::INTERNAL: case PaymentApp::Type::INTERNAL:
NOTREACHED(); NOTREACHED();
break; break;
......
...@@ -5,14 +5,39 @@ ...@@ -5,14 +5,39 @@
*/ */
/** /**
* Returns the status field from the payment handler's response. * Returns the status field from the payment handler's response for the given
* payment method identifier.
* @param {string} method - The payment method identifier to use. * @param {string} method - The payment method identifier to use.
* @return {string} - The status field or error message. * @return {string} - The status field or error message.
*/ */
async function getStatus(method) { // eslint-disable-line no-unused-vars async function getStatus(method) { // eslint-disable-line no-unused-vars
return getStatusInternal([{supportedMethods: method}]);
}
/**
* Returns the status field from the payment handler's response for the given
* list of payment method identifiers.
* @param {array<string>} methods - The list of payment methods to use.
* @return {string} - The status field or error message.
*/
async function getStatusList(methods) { // eslint-disable-line no-unused-vars
const methodData = [];
for (let method of methods) {
methodData.push({supportedMethods: method});
}
return getStatusInternal(methodData);
}
/**
* Returns the status field from the payment handler's response for given
* payment method data.
* @param {array<PaymentMethodData>} methodData - The method data to use.
* @return {string} - The status field or error message.
*/
async function getStatusInternal(methodData) {
try { try {
const request = new PaymentRequest( const request = new PaymentRequest(
[{supportedMethods: method}], methodData,
{total: {label: 'TEST', amount: {currency: 'USD', value: '0.01'}}}); {total: {label: 'TEST', amount: {currency: 'USD', value: '0.01'}}});
const response = await request.show(); const response = await request.show();
await response.complete(); await response.complete();
......
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