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 @@
#ifndef COMPONENTS_PAYMENTS_CONTENT_ANDROID_APP_COMMUNICATION_H_
#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_APP_COMMUNICATION_H_
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>
......@@ -15,6 +17,8 @@
#include "base/supports_user_data.h"
#include "components/payments/core/android_app_description.h"
class GURL;
namespace content {
class BrowserContext;
} // namespace content
......@@ -29,6 +33,10 @@ class AndroidAppCommunication : public base::SupportsUserData::Data {
const base::Optional<std::string>& error_message,
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
// owned by the given |context|, which should not be null.
static base::WeakPtr<AndroidAppCommunication> GetForBrowserContext(
......@@ -47,6 +55,17 @@ class AndroidAppCommunication : public base::SupportsUserData::Data {
virtual void GetAppDescriptions(const std::string& twa_package_name,
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.
virtual void SetForTesting() = 0;
......
......@@ -13,10 +13,13 @@
#include "components/payments/core/method_strings.h"
#include "components/payments/core/native_error_strings.h"
#include "content/public/browser/browser_thread.h"
#include "url/gurl.h"
namespace payments {
namespace {
static constexpr char kEmptyDictionaryJson[] = "{}";
void OnIsImplemented(
const std::string& twa_package_name,
AndroidAppCommunication::GetAppDescriptionsCallback callback,
......@@ -78,6 +81,64 @@ void OnIsImplemented(
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.
class AndroidAppCommunicationChromeOS : public AndroidAppCommunication {
public:
......@@ -119,6 +180,37 @@ class AndroidAppCommunicationChromeOS : public AndroidAppCommunication {
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:
void SetForTesting() override {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
......
......@@ -8,6 +8,7 @@
#include "base/callback.h"
#include "base/optional.h"
#include "components/payments/core/native_error_strings.h"
namespace payments {
namespace {
......@@ -26,6 +27,19 @@ class AndroidAppCommunicationStub : public AndroidAppCommunication {
/*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.
void SetForTesting() override {}
};
......
......@@ -15,6 +15,7 @@
#include "components/payments/content/android_app_communication_test_support.h"
#include "components/payments/core/android_app_description.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace payments {
namespace {
......@@ -59,9 +60,16 @@ class AndroidAppCommunicationTest : public testing::Test {
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_;
base::Optional<std::string> error_;
std::vector<std::unique_ptr<AndroidAppDescription>> apps_;
bool is_ready_to_pay_ = false;
};
TEST_F(AndroidAppCommunicationTest, OneInstancePerBrowserContext) {
......@@ -250,5 +258,174 @@ TEST_F(AndroidAppCommunicationTest, OutsideOfTwa) {
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 payments
......@@ -19,5 +19,8 @@ const char kMoreThanOneService[] =
"Found more than one IS_READY_TO_PAY service in the Trusted Web Activity, "
"but at most one service is supported.";
const char kMoreThanOneMethodData[] =
"At most one payment method specific data is supported.";
} // namespace errors
} // namespace payments
......@@ -23,6 +23,10 @@ extern const char kMoreThanOneActivity[];
// Used when the TWA declares more than one IS_READY_TO_PAY service.
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 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