Commit 289daef6 authored by Rouslan Solomakhin's avatar Rouslan Solomakhin Committed by Commit Bot

[Web Payment] Query IS_READY_TO_PAY service.

Before this patch, all TWA payment apps on Chrome OS were available for
payment, even if they implemented an IS_READY_TO_PAY service that
replied "false" to the query (when
chrome://flags/#enable-web-payments-experimental-features flag was
enabled).

This patch queries the IS_READY_TO_PAY service of the TWA payment app on
Chrome OS and enables payments only if the service is absent or returns
"true".

After this patch, the TWA payment app can control at runtime whether
it's able to handle payments.

Bug: 1061503
Change-Id: Idb267c0bbe2d8bb9173bcfde1b9a25e47a7fc505
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2269744
Commit-Queue: Rouslan Solomakhin <rouslan@chromium.org>
Reviewed-by: default avatarDanyao Wang <danyao@chromium.org>
Reviewed-by: default avatarLiquan (Max) Gu <maxlg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#798600}
parent a1dee93c
......@@ -13,27 +13,26 @@ namespace payments {
AndroidPaymentApp::AndroidPaymentApp(
const std::set<std::string>& payment_method_names,
const std::map<std::string, std::set<std::string>>& stringified_method_data,
std::unique_ptr<std::map<std::string, std::set<std::string>>>
stringified_method_data,
const GURL& top_level_origin,
const GURL& payment_request_origin,
const std::string& payment_request_id,
std::unique_ptr<AndroidAppDescription> description,
base::WeakPtr<AndroidAppCommunication> communication)
: PaymentApp(/*icon_resource_id=*/0, PaymentApp::Type::NATIVE_MOBILE_APP),
stringified_method_data_(stringified_method_data),
stringified_method_data_(std::move(stringified_method_data)),
top_level_origin_(top_level_origin),
payment_request_origin_(payment_request_origin),
payment_request_id_(payment_request_id),
description_(std::move(description)),
communication_(communication) {
DCHECK(!payment_method_names.empty());
DCHECK_EQ(payment_method_names.size(), stringified_method_data_.size());
DCHECK_EQ(payment_method_names.size(), stringified_method_data_->size());
DCHECK_EQ(*payment_method_names.begin(),
stringified_method_data_.begin()->first);
stringified_method_data_->begin()->first);
DCHECK(description_);
DCHECK(!description_->package.empty());
DCHECK_EQ(1U, description_->service_names.size());
DCHECK(!description_->service_names.front().empty());
DCHECK_EQ(1U, description_->activities.size());
DCHECK(!description_->activities.front()->name.empty());
......@@ -49,7 +48,7 @@ void AndroidPaymentApp::InvokePaymentApp(Delegate* delegate) {
communication_->InvokePaymentApp(
description_->package, description_->activities.front()->name,
stringified_method_data_, top_level_origin_, payment_request_origin_,
*stringified_method_data_, top_level_origin_, payment_request_origin_,
payment_request_id_,
base::BindOnce(&AndroidPaymentApp::OnPaymentAppResponse,
weak_ptr_factory_.GetWeakPtr(), delegate));
......
......@@ -32,8 +32,9 @@ class AndroidPaymentApp : public PaymentApp {
// merchant. The set of keys should match exactly the |payment_method_names|.
// It is the responsibility of the creator of AndroidPaymentApp to filter out
// the data from merchant that is not in |payment_method_names|.
AndroidPaymentApp(const std::set<std::string>& payment_method_names,
const std::map<std::string, std::set<std::string>>&
AndroidPaymentApp(
const std::set<std::string>& payment_method_names,
std::unique_ptr<std::map<std::string, std::set<std::string>>>
stringified_method_data,
const GURL& top_level_origin,
const GURL& payment_request_origin,
......@@ -79,7 +80,8 @@ class AndroidPaymentApp : public PaymentApp {
const std::string& payment_method_identifier,
const std::string& stringified_details);
const std::map<std::string, std::set<std::string>> stringified_method_data_;
const std::unique_ptr<std::map<std::string, std::set<std::string>>>
stringified_method_data_;
const GURL top_level_origin_;
const GURL payment_request_origin_;
const std::string payment_request_id_;
......
......@@ -55,6 +55,7 @@ class AppFinder : public base::SupportsUserData::Data {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(nullptr, delegate_.get());
DCHECK_NE(nullptr, delegate.get());
DCHECK_EQ(0U, number_of_pending_is_ready_to_pay_queries_);
DCHECK_EQ(nullptr, communication_.get());
DCHECK_NE(nullptr, communication.get());
DCHECK(delegate->GetSpec()->details().id.has_value());
......@@ -103,30 +104,95 @@ class AppFinder : public base::SupportsUserData::Data {
continue;
}
// Move each activity in the given |app| to its own AndroidAppDescription
// in |single_activity_apps|, so the code can treat each PAY intent as its
// own payment app. This allows Android apps to implement PAY intent in
// multiple activities with different names and icons for different use
// cases.
SplitPotentiallyMultipleActivities(std::move(app), &single_activity_apps);
}
number_of_pending_is_ready_to_pay_queries_ = single_activity_apps.size();
if (number_of_pending_is_ready_to_pay_queries_ == 0U) {
OnDoneCreatingPaymentApps();
return;
}
for (size_t i = 0; i < single_activity_apps.size(); ++i) {
auto app = std::move(single_activity_apps[i]);
std::unique_ptr<AndroidAppDescription> single_activity_app =
std::move(single_activity_apps[i]);
const std::string& default_method =
app->activities.front()->default_payment_method;
single_activity_app->activities.front()->default_payment_method;
DCHECK_EQ(methods::kGooglePlayBilling, default_method);
std::set<std::string> supported_payment_methods = {default_method};
delegate_->OnPaymentAppCreated(std::make_unique<AndroidPaymentApp>(
std::set<std::string> payment_method_names =
base::STLSetIntersection<std::set<std::string>>(
delegate_->GetSpec()->payment_method_identifiers_set(),
supported_payment_methods),
data_util::FilterStringifiedMethodData(
supported_payment_methods);
std::unique_ptr<std::map<std::string, std::set<std::string>>>
stringified_method_data = data_util::FilterStringifiedMethodData(
delegate_->GetSpec()->stringified_method_data(),
supported_payment_methods),
supported_payment_methods);
// TODO(crbug.com/1022512): Download the web app manifest for
// |default_payment_method_name| to verify Android app signature.
// Skip querying IS_READY_TO_PAY service when Chrome is off-the-record or
// when the app does not implement the IS_READY_TO_PAY service.
if (delegate_->IsOffTheRecord() ||
single_activity_app->service_names.empty()) {
OnIsReadyToPay(std::move(single_activity_app), payment_method_names,
std::move(stringified_method_data),
/*error_message=*/base::nullopt,
/*is_ready_to_pay=*/true);
continue;
}
std::map<std::string, std::set<std::string>>
stringified_method_data_copy = *stringified_method_data;
communication_->IsReadyToPay(
single_activity_app->package,
single_activity_app->service_names.front(),
stringified_method_data_copy, delegate_->GetTopOrigin(),
delegate_->GetFrameOrigin(),
delegate_->GetSpec()->details().id.value(),
base::BindOnce(&AppFinder::OnIsReadyToPay,
weak_ptr_factory_.GetWeakPtr(),
std::move(single_activity_app), payment_method_names,
std::move(stringified_method_data)));
}
}
void OnIsReadyToPay(
std::unique_ptr<AndroidAppDescription> app_description,
const std::set<std::string>& payment_method_names,
std::unique_ptr<std::map<std::string, std::set<std::string>>>
stringified_method_data,
const base::Optional<std::string>& error_message,
bool is_ready_to_pay) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_LT(0U, number_of_pending_is_ready_to_pay_queries_);
// The browser could be shutting down.
if (!communication_ || !delegate_) {
OnDoneCreatingPaymentApps();
return;
}
if (error_message.has_value()) {
delegate_->OnPaymentAppCreationError(error_message.value());
} else if (is_ready_to_pay) {
delegate_->OnPaymentAppCreated(std::make_unique<AndroidPaymentApp>(
payment_method_names, std::move(stringified_method_data),
delegate_->GetTopOrigin(), delegate_->GetFrameOrigin(),
delegate_->GetSpec()->details().id.value(), std::move(app),
communication_));
delegate_->GetSpec()->details().id.value(),
std::move(app_description), communication_));
}
if (--number_of_pending_is_ready_to_pay_queries_ == 0)
OnDoneCreatingPaymentApps();
}
......@@ -139,6 +205,7 @@ class AppFinder : public base::SupportsUserData::Data {
base::SupportsUserData* owner_;
base::WeakPtr<PaymentAppFactory::Delegate> delegate_;
size_t number_of_pending_is_ready_to_pay_queries_ = 0;
base::WeakPtr<AndroidAppCommunication> communication_;
base::WeakPtrFactory<AppFinder> weak_ptr_factory_{this};
......
......@@ -54,6 +54,8 @@ class MockPaymentAppFactoryDelegate : public PaymentAppFactory::Delegate {
/*observer=*/nullptr, /*app_locale=*/"en-US");
}
void set_is_off_the_record() { is_off_the_record_ = true; }
// PaymentAppFactory::Delegate implementation:
content::WebContents* GetWebContents() override { return web_contents_; }
const GURL& GetTopOrigin() override { return top_origin_; }
......@@ -65,7 +67,7 @@ class MockPaymentAppFactoryDelegate : public PaymentAppFactory::Delegate {
MOCK_CONST_METHOD0(GetPaymentManifestWebDataService,
scoped_refptr<PaymentManifestWebDataService>());
MOCK_METHOD0(MayCrawlForInstallablePaymentApps, bool());
MOCK_CONST_METHOD0(IsOffTheRecord, bool());
bool IsOffTheRecord() const override { return is_off_the_record_; }
PaymentRequestSpec* GetSpec() const override { return spec_.get(); }
MOCK_CONST_METHOD0(GetTwaPackageName, std::string());
MOCK_METHOD0(ShowProcessingSpinner, void());
......@@ -90,6 +92,7 @@ class MockPaymentAppFactoryDelegate : public PaymentAppFactory::Delegate {
GURL top_origin_;
GURL frame_origin_;
std::unique_ptr<PaymentRequestSpec> spec_;
bool is_off_the_record_ = false;
base::WeakPtrFactory<PaymentAppFactory::Delegate> weak_ptr_factory_{this};
};
......@@ -129,6 +132,7 @@ TEST_F(AndroidPaymentAppFactoryTest, FactoryReturnsErrorWithoutArc) {
.Times(support_->AreAndroidAppsSupportedOnThisPlatform() ? 1 : 0);
EXPECT_CALL(delegate_, OnPaymentAppCreated(testing::_)).Times(0);
support_->ExpectNoListOfPaymentAppsQuery();
support_->ExpectNoIsReadyToPayQuery();
factory_.Create(delegate_.GetWeakPtr());
}
......@@ -146,6 +150,7 @@ TEST_F(AndroidPaymentAppFactoryTest, NoErrorsWhenNoApps) {
EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
EXPECT_CALL(delegate_, OnPaymentAppCreated(testing::_)).Times(0);
support_->ExpectQueryListOfPaymentAppsAndRespond({});
support_->ExpectNoIsReadyToPayQuery();
factory_.Create(delegate_.GetWeakPtr());
}
......@@ -157,8 +162,83 @@ MATCHER_P3(PaymentAppMatches, type, package, method, "") {
}
// The payment app factory should return the TWA payment app when running in TWA
// mode.
TEST_F(AndroidPaymentAppFactoryTest, FindTheTwaPaymentAppInTwaMode) {
// mode, even when it does not have an IS_READY_TO_PAY service.
TEST_F(AndroidPaymentAppFactoryTest, FindAppsThatDoNotHaveReadyToPayService) {
// Enable invoking Android payment apps on those platforms that support it.
auto scoped_initialization_ = support_->CreateScopedInitialization();
EXPECT_CALL(delegate_, GetTwaPackageName())
.WillRepeatedly(testing::Return("com.example.app"));
EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
EXPECT_CALL(delegate_,
OnPaymentAppCreated(PaymentAppMatches(
PaymentApp::Type::NATIVE_MOBILE_APP, "com.example.app",
"https://play.google.com/billing")))
.Times(support_->AreAndroidAppsSupportedOnThisPlatform() ? 1 : 0);
// This app does not have an IS_READY_TO_PAY service.
std::vector<std::unique_ptr<AndroidAppDescription>> apps;
apps.emplace_back(std::make_unique<AndroidAppDescription>());
apps.back()->package = "com.example.app";
apps.back()->activities.emplace_back(
std::make_unique<AndroidActivityDescription>());
apps.back()->activities.back()->name = "com.example.app.Activity";
apps.back()->activities.back()->default_payment_method =
"https://play.google.com/billing";
support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
// There is no IS_READY_TO_PAY service to invoke.
support_->ExpectNoIsReadyToPayQuery();
factory_.Create(delegate_.GetWeakPtr());
}
// The payment app factory should return one payment app and should not query
// the IS_READY_TO_PAY service, because of being off the record.
TEST_F(AndroidPaymentAppFactoryTest,
DoNotQueryReadyToPaySericeWhenOffTheRecord) {
// Enable invoking Android payment apps on those platforms that support it.
auto scoped_initialization_ = support_->CreateScopedInitialization();
// Simulate being off the record.
delegate_.set_is_off_the_record();
EXPECT_CALL(delegate_, GetTwaPackageName())
.WillRepeatedly(testing::Return("com.example.app"));
EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
EXPECT_CALL(delegate_,
OnPaymentAppCreated(PaymentAppMatches(
PaymentApp::Type::NATIVE_MOBILE_APP, "com.example.app",
"https://play.google.com/billing")))
.Times(support_->AreAndroidAppsSupportedOnThisPlatform() ? 1 : 0);
std::vector<std::unique_ptr<AndroidAppDescription>> apps;
apps.emplace_back(std::make_unique<AndroidAppDescription>());
apps.back()->package = "com.example.app";
apps.back()->service_names.push_back("com.example.app.Service");
apps.back()->activities.emplace_back(
std::make_unique<AndroidActivityDescription>());
apps.back()->activities.back()->name = "com.example.app.Activity";
apps.back()->activities.back()->default_payment_method =
"https://play.google.com/billing";
support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
// The IS_READY_TO_PAY service should not be invoked when off the record.
support_->ExpectNoIsReadyToPayQuery();
factory_.Create(delegate_.GetWeakPtr());
}
// The payment app factory should return the TWA payment app that returns true
// from IS_READY_TO_PAY service when running in TWA mode.
TEST_F(AndroidPaymentAppFactoryTest,
FindTheTwaPaymentAppThatIsReadyToPayInTwaMode) {
// Enable invoking Android payment apps on those platforms that support it.
auto scoped_initialization_ = support_->CreateScopedInitialization();
......@@ -183,6 +263,34 @@ TEST_F(AndroidPaymentAppFactoryTest, FindTheTwaPaymentAppInTwaMode) {
apps.back()->activities.back()->default_payment_method =
"https://play.google.com/billing";
support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
support_->ExpectQueryIsReadyToPayAndRespond(/*is_ready_to_pay=*/true);
factory_.Create(delegate_.GetWeakPtr());
}
// The payment app factory should return no payment apps when IS_READY_TO_PAY
// service returns false.
TEST_F(AndroidPaymentAppFactoryTest, IgnoreAppsThatAreNotReadyToPay) {
// Enable invoking Android payment apps on those platforms that support it.
auto scoped_initialization_ = support_->CreateScopedInitialization();
EXPECT_CALL(delegate_, GetTwaPackageName())
.WillRepeatedly(testing::Return("com.example.app"));
EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
EXPECT_CALL(delegate_, OnPaymentAppCreated(testing::_)).Times(0);
std::vector<std::unique_ptr<AndroidAppDescription>> apps;
apps.emplace_back(std::make_unique<AndroidAppDescription>());
apps.back()->package = "com.example.app";
apps.back()->service_names.push_back("com.example.app.Service");
apps.back()->activities.emplace_back(
std::make_unique<AndroidActivityDescription>());
apps.back()->activities.back()->name = "com.example.app.Activity";
apps.back()->activities.back()->default_payment_method =
"https://play.google.com/billing";
support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
support_->ExpectQueryIsReadyToPayAndRespond(/*is_ready_to_pay=*/false);
factory_.Create(delegate_.GetWeakPtr());
}
......@@ -230,6 +338,7 @@ TEST_F(AndroidPaymentAppFactoryTest, FindTheCorrectTwaAppInTwaMode) {
"https://play.google.com/billing";
support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
support_->ExpectQueryIsReadyToPayAndRespond(/*is_ready_to_pay=*/true);
factory_.Create(delegate_.GetWeakPtr());
}
......@@ -256,6 +365,7 @@ TEST_F(AndroidPaymentAppFactoryTest, IgnoreNonTwaAppsInTwaMode) {
apps.back()->activities.back()->default_payment_method =
"https://play.google.com/billing";
support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
support_->ExpectNoIsReadyToPayQuery();
factory_.Create(delegate_.GetWeakPtr());
}
......@@ -290,6 +400,7 @@ TEST_F(AndroidPaymentAppFactoryTest, DoNotLookForAppsForNonTwaMethod) {
EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
EXPECT_CALL(delegate_, OnPaymentAppCreated(testing::_)).Times(0);
support_->ExpectNoListOfPaymentAppsQuery();
support_->ExpectNoIsReadyToPayQuery();
factory_.Create(delegate_.GetWeakPtr());
}
......@@ -316,6 +427,7 @@ TEST_F(AndroidPaymentAppFactoryTest, IgnoreNonTwaMethodInTheTwa) {
apps.back()->activities.back()->default_payment_method =
"https://example.test";
support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
support_->ExpectNoIsReadyToPayQuery();
factory_.Create(delegate_.GetWeakPtr());
}
......@@ -357,7 +469,9 @@ TEST_F(AndroidPaymentAppFactoryTest,
apps.back()->activities.back()->name = "com.twa.app.ActivityTwo";
apps.back()->activities.back()->default_payment_method =
"https://example.test";
support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
support_->ExpectQueryIsReadyToPayAndRespond(/*is_ready_to_pay=*/true);
factory_.Create(delegate_.GetWeakPtr());
}
......@@ -392,6 +506,7 @@ TEST_F(AndroidPaymentAppFactoryTest, ReturnErrorWhenMoreThanOneServiceInApp) {
apps.back()->activities.back()->default_payment_method =
"https://play.google.com/billing";
support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
support_->ExpectNoIsReadyToPayQuery();
factory_.Create(delegate_.GetWeakPtr());
}
......
......@@ -31,8 +31,9 @@ class AndroidPaymentAppTest : public testing::Test,
base::WeakPtr<AndroidAppCommunication> communication) {
std::set<std::string> payment_method_names;
payment_method_names.insert(methods::kGooglePlayBilling);
std::map<std::string, std::set<std::string>> stringified_method_data;
stringified_method_data[methods::kGooglePlayBilling].insert("{}");
auto stringified_method_data =
std::make_unique<std::map<std::string, std::set<std::string>>>();
stringified_method_data->insert({methods::kGooglePlayBilling, {"{}"}});
auto description = std::make_unique<AndroidAppDescription>();
description->package = "com.example.app";
description->service_names.push_back("com.example.app.Service");
......@@ -43,7 +44,7 @@ class AndroidPaymentAppTest : public testing::Test,
methods::kGooglePlayBilling;
return std::make_unique<AndroidPaymentApp>(
payment_method_names, stringified_method_data,
payment_method_names, std::move(stringified_method_data),
GURL("https://top-level-origin.com"),
GURL("https://payment-request-origin.com"), "payment-request-id",
std::move(description), communication);
......
......@@ -4,7 +4,7 @@
#include "components/payments/core/payment_request_data_util.h"
#include <memory>
#include <utility>
#include "base/stl_util.h"
#include "base/strings/string_split.h"
......@@ -166,13 +166,15 @@ base::string16 FormatCardNumberForDisplay(const base::string16& card_number) {
return number;
}
std::map<std::string, std::set<std::string>> FilterStringifiedMethodData(
std::unique_ptr<std::map<std::string, std::set<std::string>>>
FilterStringifiedMethodData(
const std::map<std::string, std::set<std::string>>& stringified_method_data,
const std::set<std::string>& supported_payment_method_names) {
std::map<std::string, std::set<std::string>> result;
auto result =
std::make_unique<std::map<std::string, std::set<std::string>>>();
for (const auto& pair : stringified_method_data) {
if (base::Contains(supported_payment_method_names, pair.first)) {
result[pair.first] = pair.second;
result->insert({pair.first, pair.second});
}
}
return result;
......
......@@ -5,6 +5,7 @@
#ifndef COMPONENTS_PAYMENTS_CORE_PAYMENT_REQUEST_DATA_UTIL_H_
#define COMPONENTS_PAYMENTS_CORE_PAYMENT_REQUEST_DATA_UTIL_H_
#include <memory>
#include <set>
#include <string>
#include <vector>
......@@ -77,7 +78,8 @@ base::string16 FormatCardNumberForDisplay(const base::string16& card_number);
// Value: The set of all payment method specific parameters for the given
// payment method identifier, each one serialized into a JSON string,
// e.g., '{"key": "value"}'.
std::map<std::string, std::set<std::string>> FilterStringifiedMethodData(
std::unique_ptr<std::map<std::string, std::set<std::string>>>
FilterStringifiedMethodData(
const std::map<std::string, std::set<std::string>>& stringified_method_data,
const std::set<std::string>& supported_payment_method_names);
......
......@@ -280,21 +280,21 @@ TEST(PaymentRequestDataUtil, ParseSupportedMethods_MultipleEntries) {
TEST(PaymentRequestDataUtil, FilterStringifiedMethodData) {
std::map<std::string, std::set<std::string>> requested;
std::set<std::string> supported;
EXPECT_TRUE(FilterStringifiedMethodData(requested, supported).empty());
EXPECT_TRUE(FilterStringifiedMethodData(requested, supported)->empty());
requested["a"].insert("{\"b\": \"c\"}");
EXPECT_TRUE(FilterStringifiedMethodData(requested, supported).empty());
EXPECT_TRUE(FilterStringifiedMethodData(requested, supported)->empty());
requested["x"].insert("{\"y\": \"z\"}");
EXPECT_TRUE(FilterStringifiedMethodData(requested, supported).empty());
EXPECT_TRUE(FilterStringifiedMethodData(requested, supported)->empty());
supported.insert("x");
std::map<std::string, std::set<std::string>> expected;
expected["x"].insert("{\"y\": \"z\"}");
EXPECT_EQ(expected, FilterStringifiedMethodData(requested, supported));
EXPECT_EQ(expected, *FilterStringifiedMethodData(requested, supported));
supported.insert("g");
EXPECT_EQ(expected, FilterStringifiedMethodData(requested, supported));
EXPECT_EQ(expected, *FilterStringifiedMethodData(requested, supported));
}
} // namespace data_util
......
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