Commit 1043cf8e authored by Rouslan Solomakhin's avatar Rouslan Solomakhin Committed by Commit Bot

[Web Payment][Chrome OS] Android app communication - IsReadyToPay.

This patch adds a cross-platform way for Web Payment feature to query
the IS_READY_TO_PAY intent of an Android app. As a first step, only the
Android apps installed on the Android subsystem of Chrome OS are
supported.

Bug: 1061503
Change-Id: I3055874e96e046a233c5bb223c9e4422d7e8e966
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2248427
Commit-Queue: Rouslan Solomakhin <rouslan@chromium.org>
Reviewed-by: default avatarLiquan (Max) Gu <maxlg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#797954}
parent f34b0c09
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
#ifndef COMPONENTS_PAYMENTS_CONTENT_ANDROID_APP_COMMUNICATION_H_ #ifndef COMPONENTS_PAYMENTS_CONTENT_ANDROID_APP_COMMUNICATION_H_
#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_APP_COMMUNICATION_H_ #define COMPONENTS_PAYMENTS_CONTENT_ANDROID_APP_COMMUNICATION_H_
#include <map>
#include <memory> #include <memory>
#include <set>
#include <string> #include <string>
#include <vector> #include <vector>
...@@ -15,6 +17,8 @@ ...@@ -15,6 +17,8 @@
#include "base/supports_user_data.h" #include "base/supports_user_data.h"
#include "components/payments/core/android_app_description.h" #include "components/payments/core/android_app_description.h"
class GURL;
namespace content { namespace content {
class BrowserContext; class BrowserContext;
} // namespace content } // namespace content
...@@ -29,6 +33,10 @@ class AndroidAppCommunication : public base::SupportsUserData::Data { ...@@ -29,6 +33,10 @@ class AndroidAppCommunication : public base::SupportsUserData::Data {
const base::Optional<std::string>& error_message, const base::Optional<std::string>& error_message,
std::vector<std::unique_ptr<AndroidAppDescription>> app_descriptions)>; std::vector<std::unique_ptr<AndroidAppDescription>> app_descriptions)>;
using IsReadyToPayCallback =
base::OnceCallback<void(const base::Optional<std::string>& error_message,
bool is_ready_to_pay)>;
// Returns a weak pointer to the instance of AndroidAppCommunication that is // Returns a weak pointer to the instance of AndroidAppCommunication that is
// owned by the given |context|, which should not be null. // owned by the given |context|, which should not be null.
static base::WeakPtr<AndroidAppCommunication> GetForBrowserContext( static base::WeakPtr<AndroidAppCommunication> GetForBrowserContext(
...@@ -47,6 +55,17 @@ class AndroidAppCommunication : public base::SupportsUserData::Data { ...@@ -47,6 +55,17 @@ class AndroidAppCommunication : public base::SupportsUserData::Data {
virtual void GetAppDescriptions(const std::string& twa_package_name, virtual void GetAppDescriptions(const std::string& twa_package_name,
GetAppDescriptionsCallback callback) = 0; GetAppDescriptionsCallback callback) = 0;
// Queries the IS_READY_TO_PAY service to check whether the payment app can
// perform payments.
virtual void IsReadyToPay(const std::string& package_name,
const std::string& service_name,
const 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,
IsReadyToPayCallback callback) = 0;
// Enables the testing mode. // Enables the testing mode.
virtual void SetForTesting() = 0; virtual void SetForTesting() = 0;
......
...@@ -13,10 +13,13 @@ ...@@ -13,10 +13,13 @@
#include "components/payments/core/method_strings.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 "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "url/gurl.h"
namespace payments { namespace payments {
namespace { namespace {
static constexpr char kEmptyDictionaryJson[] = "{}";
void OnIsImplemented( void OnIsImplemented(
const std::string& twa_package_name, const std::string& twa_package_name,
AndroidAppCommunication::GetAppDescriptionsCallback callback, AndroidAppCommunication::GetAppDescriptionsCallback callback,
...@@ -78,6 +81,64 @@ void OnIsImplemented( ...@@ -78,6 +81,64 @@ void OnIsImplemented(
std::move(app_descriptions)); std::move(app_descriptions));
} }
void OnIsReadyToPay(AndroidAppCommunication::IsReadyToPayCallback callback,
arc::mojom::IsReadyToPayResultPtr response) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (response.is_null()) {
std::move(callback).Run(errors::kEmptyResponse, /*is_ready_to_pay=*/false);
return;
}
if (response->is_error()) {
std::move(callback).Run(response->get_error(), /*is_ready_to_pay=*/false);
return;
}
if (!response->is_response()) {
std::move(callback).Run(errors::kInvalidResponse,
/*is_ready_to_pay=*/false);
return;
}
std::move(callback).Run(/*error_message=*/base::nullopt,
response->get_response());
}
arc::mojom::PaymentParametersPtr CreatePaymentParameters(
const std::string& package_name,
const std::string& activity_or_service_name,
const 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,
base::Optional<std::string>* error_message) {
// Chrome OS TWA supports only kGooglePlayBilling payment method identifier
// at this time.
auto supported_method_iterator =
stringified_method_data.find(methods::kGooglePlayBilling);
if (supported_method_iterator == stringified_method_data.end())
return nullptr;
// Chrome OS TWA supports only one set of payment method specific data.
if (supported_method_iterator->second.size() > 1) {
*error_message = errors::kMoreThanOneMethodData;
return nullptr;
}
auto parameters = arc::mojom::PaymentParameters::New();
parameters->stringified_method_data =
supported_method_iterator->second.empty()
? kEmptyDictionaryJson
: *supported_method_iterator->second.begin();
parameters->package_name = package_name;
parameters->activity_or_service_name = activity_or_service_name;
parameters->top_level_origin = top_level_origin.spec();
parameters->payment_request_origin = payment_request_origin.spec();
parameters->payment_request_id = payment_request_id;
return parameters;
}
// 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:
...@@ -119,6 +180,37 @@ class AndroidAppCommunicationChromeOS : public AndroidAppCommunication { ...@@ -119,6 +180,37 @@ class AndroidAppCommunicationChromeOS : public AndroidAppCommunication {
std::move(callback))); std::move(callback)));
} }
// AndroidAppCommunication implementation.
void IsReadyToPay(const std::string& package_name,
const std::string& service_name,
const 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,
IsReadyToPayCallback callback) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto* payment_app_service = get_app_service_.Run(context());
if (!payment_app_service) {
std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps,
/*is_ready_to_pay=*/false);
return;
}
base::Optional<std::string> error_message;
auto parameters = CreatePaymentParameters(
package_name, service_name, stringified_method_data, top_level_origin,
payment_request_origin, payment_request_id, &error_message);
if (!parameters) {
std::move(callback).Run(error_message, /*is_ready_to_pay=*/false);
return;
}
payment_app_service->IsReadyToPay(
std::move(parameters),
base::BindOnce(&OnIsReadyToPay, 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);
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/optional.h" #include "base/optional.h"
#include "components/payments/core/native_error_strings.h"
namespace payments { namespace payments {
namespace { namespace {
...@@ -26,6 +27,19 @@ class AndroidAppCommunicationStub : public AndroidAppCommunication { ...@@ -26,6 +27,19 @@ class AndroidAppCommunicationStub : public AndroidAppCommunication {
/*app_descriptions=*/{}); /*app_descriptions=*/{});
} }
// AndroidAppCommunication implementation.
void IsReadyToPay(const std::string& package_name,
const std::string& service_name,
const 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,
IsReadyToPayCallback callback) override {
std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps,
/*is_ready_to_pay=*/false);
}
// AndroidAppCommunication implementation. // AndroidAppCommunication implementation.
void SetForTesting() override {} void SetForTesting() override {}
}; };
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "components/payments/content/android_app_communication_test_support.h" #include "components/payments/content/android_app_communication_test_support.h"
#include "components/payments/core/android_app_description.h" #include "components/payments/core/android_app_description.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace payments { namespace payments {
namespace { namespace {
...@@ -59,9 +60,16 @@ class AndroidAppCommunicationTest : public testing::Test { ...@@ -59,9 +60,16 @@ class AndroidAppCommunicationTest : public testing::Test {
apps_ = std::move(apps); apps_ = std::move(apps);
} }
void OnIsReadyToPayResponse(const base::Optional<std::string>& error,
bool is_ready_to_pay) {
error_ = error;
is_ready_to_pay_ = is_ready_to_pay;
}
std::unique_ptr<AndroidAppCommunicationTestSupport> support_; std::unique_ptr<AndroidAppCommunicationTestSupport> support_;
base::Optional<std::string> error_; base::Optional<std::string> error_;
std::vector<std::unique_ptr<AndroidAppDescription>> apps_; std::vector<std::unique_ptr<AndroidAppDescription>> apps_;
bool is_ready_to_pay_ = false;
}; };
TEST_F(AndroidAppCommunicationTest, OneInstancePerBrowserContext) { TEST_F(AndroidAppCommunicationTest, OneInstancePerBrowserContext) {
...@@ -250,5 +258,174 @@ TEST_F(AndroidAppCommunicationTest, OutsideOfTwa) { ...@@ -250,5 +258,174 @@ TEST_F(AndroidAppCommunicationTest, OutsideOfTwa) {
EXPECT_TRUE(apps_.empty()); EXPECT_TRUE(apps_.empty());
} }
TEST_F(AndroidAppCommunicationTest, NoArcForIsReadyToPay) {
// Intentionally do not set an instance.
support_->ExpectNoIsReadyToPayQuery();
auto communication =
AndroidAppCommunication::GetForBrowserContext(support_->context());
communication->SetForTesting();
std::map<std::string, std::set<std::string>> stringified_method_data;
stringified_method_data["https://play.google.com/billing"].insert("{}");
communication->IsReadyToPay(
"com.example.app", "com.example.app.Service", stringified_method_data,
GURL("https://top-level-origin.com"),
GURL("https://payment-request-origin.com"), "payment-request-id",
base::BindOnce(&AndroidAppCommunicationTest::OnIsReadyToPayResponse,
base::Unretained(this)));
ASSERT_TRUE(error_.has_value());
EXPECT_EQ("Unable to invoke Android apps.", error_.value());
EXPECT_FALSE(is_ready_to_pay_);
}
TEST_F(AndroidAppCommunicationTest, TwaIsReadyToPayOnlyWithPlayBilling) {
auto scoped_initialization = support_->CreateScopedInitialization();
support_->ExpectNoIsReadyToPayQuery();
auto communication =
AndroidAppCommunication::GetForBrowserContext(support_->context());
communication->SetForTesting();
std::map<std::string, std::set<std::string>> stringified_method_data;
stringified_method_data["https://example.com"].insert("{}");
communication->IsReadyToPay(
"com.example.app", "com.example.app.Service", stringified_method_data,
GURL("https://top-level-origin.com"),
GURL("https://payment-request-origin.com"), "payment-request-id",
base::BindOnce(&AndroidAppCommunicationTest::OnIsReadyToPayResponse,
base::Unretained(this)));
if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
EXPECT_FALSE(error_.has_value());
} else {
ASSERT_TRUE(error_.has_value());
EXPECT_EQ("Unable to invoke Android apps.", error_.value());
}
EXPECT_FALSE(is_ready_to_pay_);
}
TEST_F(AndroidAppCommunicationTest, MoreThanOnePaymentMethodDataNotReadyToPay) {
auto scoped_initialization = support_->CreateScopedInitialization();
support_->ExpectNoIsReadyToPayQuery();
auto communication =
AndroidAppCommunication::GetForBrowserContext(support_->context());
communication->SetForTesting();
std::map<std::string, std::set<std::string>> stringified_method_data;
stringified_method_data["https://play.google.com/billing"].insert(
"{\"product_id\": \"1\"}");
stringified_method_data["https://play.google.com/billing"].insert(
"{\"product_id\": \"2\"}");
communication->IsReadyToPay(
"com.example.app", "com.example.app.Service", stringified_method_data,
GURL("https://top-level-origin.com"),
GURL("https://payment-request-origin.com"), "payment-request-id",
base::BindOnce(&AndroidAppCommunicationTest::OnIsReadyToPayResponse,
base::Unretained(this)));
ASSERT_TRUE(error_.has_value());
if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
EXPECT_EQ("At most one payment method specific data is supported.",
error_.value());
} else {
EXPECT_EQ("Unable to invoke Android apps.", error_.value());
}
EXPECT_FALSE(is_ready_to_pay_);
}
TEST_F(AndroidAppCommunicationTest, EmptyMethodDataIsReadyToPay) {
auto scoped_initialization = support_->CreateScopedInitialization();
support_->ExpectQueryIsReadyToPayAndRespond(true);
auto communication =
AndroidAppCommunication::GetForBrowserContext(support_->context());
communication->SetForTesting();
std::map<std::string, std::set<std::string>> stringified_method_data;
stringified_method_data.insert(std::make_pair(
"https://play.google.com/billing", std::set<std::string>()));
communication->IsReadyToPay(
"com.example.app", "com.example.app.Service", stringified_method_data,
GURL("https://top-level-origin.com"),
GURL("https://payment-request-origin.com"), "payment-request-id",
base::BindOnce(&AndroidAppCommunicationTest::OnIsReadyToPayResponse,
base::Unretained(this)));
if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
EXPECT_FALSE(error_.has_value());
EXPECT_TRUE(is_ready_to_pay_);
} else {
ASSERT_TRUE(error_.has_value());
EXPECT_EQ("Unable to invoke Android apps.", error_.value());
EXPECT_FALSE(is_ready_to_pay_);
}
}
TEST_F(AndroidAppCommunicationTest, NotReadyToPay) {
auto scoped_initialization = support_->CreateScopedInitialization();
support_->ExpectQueryIsReadyToPayAndRespond(false);
auto communication =
AndroidAppCommunication::GetForBrowserContext(support_->context());
communication->SetForTesting();
std::map<std::string, std::set<std::string>> stringified_method_data;
stringified_method_data["https://play.google.com/billing"].insert("{}");
communication->IsReadyToPay(
"com.example.app", "com.example.app.Service", stringified_method_data,
GURL("https://top-level-origin.com"),
GURL("https://payment-request-origin.com"), "payment-request-id",
base::BindOnce(&AndroidAppCommunicationTest::OnIsReadyToPayResponse,
base::Unretained(this)));
if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
EXPECT_FALSE(error_.has_value());
} else {
ASSERT_TRUE(error_.has_value());
EXPECT_EQ("Unable to invoke Android apps.", error_.value());
}
EXPECT_FALSE(is_ready_to_pay_);
}
TEST_F(AndroidAppCommunicationTest, ReadyToPay) {
auto scoped_initialization = support_->CreateScopedInitialization();
support_->ExpectQueryIsReadyToPayAndRespond(true);
auto communication =
AndroidAppCommunication::GetForBrowserContext(support_->context());
communication->SetForTesting();
std::map<std::string, std::set<std::string>> stringified_method_data;
stringified_method_data["https://play.google.com/billing"].insert("{}");
communication->IsReadyToPay(
"com.example.app", "com.example.app.Service", stringified_method_data,
GURL("https://top-level-origin.com"),
GURL("https://payment-request-origin.com"), "payment-request-id",
base::BindOnce(&AndroidAppCommunicationTest::OnIsReadyToPayResponse,
base::Unretained(this)));
if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
EXPECT_FALSE(error_.has_value());
EXPECT_TRUE(is_ready_to_pay_);
} else {
ASSERT_TRUE(error_.has_value());
EXPECT_EQ("Unable to invoke Android apps.", error_.value());
EXPECT_FALSE(is_ready_to_pay_);
}
}
} // namespace } // namespace
} // namespace payments } // namespace payments
...@@ -19,5 +19,8 @@ const char kMoreThanOneService[] = ...@@ -19,5 +19,8 @@ const char kMoreThanOneService[] =
"Found more than one IS_READY_TO_PAY service in the Trusted Web Activity, " "Found more than one IS_READY_TO_PAY service in the Trusted Web Activity, "
"but at most one service is supported."; "but at most one service is supported.";
const char kMoreThanOneMethodData[] =
"At most one payment method specific data is supported.";
} // namespace errors } // namespace errors
} // namespace payments } // namespace payments
...@@ -23,6 +23,10 @@ extern const char kMoreThanOneActivity[]; ...@@ -23,6 +23,10 @@ extern const char kMoreThanOneActivity[];
// Used when the TWA declares more than one IS_READY_TO_PAY service. // Used when the TWA declares more than one IS_READY_TO_PAY service.
extern const char kMoreThanOneService[]; extern const char kMoreThanOneService[];
// Used when the merchant invokes the Trusted Web Activity with more than set of
// payment method specific data.
extern const char kMoreThanOneMethodData[];
} // namespace errors } // namespace errors
} // namespace payments } // namespace payments
......
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