Commit a714a88a authored by Rouslan Solomakhin's avatar Rouslan Solomakhin Committed by Commit Bot

[Web Payment][Chrome OS] Cross-platform Android payment app.

This patch adds a C++ payment app object that communicates to Android
payment apps. At this time, only Chrome OS has concrete implementation
that connects to a Trusted Web Activity in the Android subsystem.

Design: https://bit.ly/cross-platform-pay-app-factory

Bug: 1061503
Change-Id: Ice5918967493e34cfa5ad4a4ccc25ce2602d204a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2255112
Commit-Queue: Rouslan Solomakhin <rouslan@chromium.org>
Reviewed-by: default avatarDanyao Wang <danyao@chromium.org>
Cr-Commit-Position: refs/heads/master@{#798238}
parent fd142c07
......@@ -6,6 +6,8 @@ static_library("content") {
sources = [
"android_app_communication.cc",
"android_app_communication.h",
"android_payment_app.cc",
"android_payment_app.h",
"autofill_payment_app.cc",
"autofill_payment_app.h",
"autofill_payment_app_factory.cc",
......@@ -153,6 +155,7 @@ source_set("unit_tests") {
sources = [
"android_app_communication_test_support.h",
"android_app_communication_unittest.cc",
"android_payment_app_unittest.cc",
"payment_method_manifest_table_unittest.cc",
"service_worker_payment_app_finder_unittest.cc",
"test_content_payment_request_delegate.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.
#include "components/payments/content/android_payment_app.h"
#include <utility>
#include "components/payments/core/native_error_strings.h"
#include "components/payments/core/payer_data.h"
namespace payments {
AndroidPaymentApp::AndroidPaymentApp(
const std::set<std::string>& payment_method_names,
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,
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),
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.begin(),
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());
app_method_names_ = payment_method_names;
}
AndroidPaymentApp::~AndroidPaymentApp() = default;
void AndroidPaymentApp::InvokePaymentApp(Delegate* delegate) {
// Browser is closing, so no need to invoke a callback.
if (!communication_)
return;
communication_->InvokePaymentApp(
description_->package, description_->activities.front()->name,
stringified_method_data_, top_level_origin_, payment_request_origin_,
payment_request_id_,
base::BindOnce(&AndroidPaymentApp::OnPaymentAppResponse,
weak_ptr_factory_.GetWeakPtr(), delegate));
}
bool AndroidPaymentApp::IsCompleteForPayment() const {
return true;
}
uint32_t AndroidPaymentApp::GetCompletenessScore() const {
return 0;
}
bool AndroidPaymentApp::CanPreselect() const {
return true;
}
base::string16 AndroidPaymentApp::GetMissingInfoLabel() const {
NOTREACHED();
return base::string16();
}
bool AndroidPaymentApp::HasEnrolledInstrument() const {
return true;
}
void AndroidPaymentApp::RecordUse() {
NOTIMPLEMENTED();
}
bool AndroidPaymentApp::NeedsInstallation() const {
return false;
}
std::string AndroidPaymentApp::GetId() const {
return description_->package;
}
base::string16 AndroidPaymentApp::GetLabel() const {
return base::string16();
}
base::string16 AndroidPaymentApp::GetSublabel() const {
return base::string16();
}
const SkBitmap* AndroidPaymentApp::icon_bitmap() const {
return nullptr;
}
bool AndroidPaymentApp::IsValidForModifier(
const std::string& method,
bool supported_networks_specified,
const std::set<std::string>& supported_networks) const {
bool is_valid = false;
IsValidForPaymentMethodIdentifier(method, &is_valid);
return is_valid;
}
base::WeakPtr<PaymentApp> AndroidPaymentApp::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
bool AndroidPaymentApp::HandlesShippingAddress() const {
return false;
}
bool AndroidPaymentApp::HandlesPayerName() const {
return false;
}
bool AndroidPaymentApp::HandlesPayerEmail() const {
return false;
}
bool AndroidPaymentApp::HandlesPayerPhone() const {
return false;
}
bool AndroidPaymentApp::IsWaitingForPaymentDetailsUpdate() const {
return false;
}
void AndroidPaymentApp::UpdateWith(
mojom::PaymentRequestDetailsUpdatePtr details_update) {
// TODO(crbug.com/1022512): Support payment method, shipping address, and
// shipping option change events.
}
void AndroidPaymentApp::OnPaymentDetailsNotUpdated() {}
void AndroidPaymentApp::OnPaymentAppResponse(
Delegate* delegate,
const base::Optional<std::string>& error_message,
bool is_activity_result_ok,
const std::string& payment_method_identifier,
const std::string& stringified_details) {
if (error_message.has_value()) {
delegate->OnInstrumentDetailsError(error_message.value());
return;
}
if (!is_activity_result_ok) {
delegate->OnInstrumentDetailsError(errors::kUserClosedPaymentApp);
return;
}
delegate->OnInstrumentDetailsReady(payment_method_identifier,
stringified_details, PayerData());
}
} // namespace payments
// 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.
#ifndef COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_APP_H_
#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_APP_H_
#include <memory>
#include <set>
#include <string>
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "components/payments/content/android_app_communication.h"
#include "components/payments/content/payment_app.h"
#include "components/payments/core/android_app_description.h"
#include "url/gurl.h"
namespace payments {
// A cross-platform way to invoke an Android payment app.
class AndroidPaymentApp : public PaymentApp {
public:
// Creates an instance of AndroidPaymentApp.
//
// The |payment_method_names| is the set of payment method identifiers
// supported by this app, e.g., ["https://example1.com",
// "https://example2.com"]. This set should not be empty.
//
// The |stringified_method_data| is a mapping from payment method identifiers
// that this app can handle to the method-specific data provided by the
// 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>>&
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);
~AndroidPaymentApp() override;
AndroidPaymentApp(const AndroidPaymentApp& other) = delete;
AndroidPaymentApp& operator=(const AndroidPaymentApp& other) = delete;
// PaymentApp implementation.
void InvokePaymentApp(Delegate* delegate) override;
bool IsCompleteForPayment() const override;
uint32_t GetCompletenessScore() const override;
bool CanPreselect() const override;
base::string16 GetMissingInfoLabel() const override;
bool HasEnrolledInstrument() const override;
void RecordUse() override;
bool NeedsInstallation() const override;
std::string GetId() const override;
base::string16 GetLabel() const override;
base::string16 GetSublabel() const override;
const SkBitmap* icon_bitmap() const override;
bool IsValidForModifier(
const std::string& method,
bool supported_networks_specified,
const std::set<std::string>& supported_networks) const override;
base::WeakPtr<PaymentApp> AsWeakPtr() override;
bool HandlesShippingAddress() const override;
bool HandlesPayerName() const override;
bool HandlesPayerEmail() const override;
bool HandlesPayerPhone() const override;
bool IsWaitingForPaymentDetailsUpdate() const override;
void UpdateWith(
mojom::PaymentRequestDetailsUpdatePtr details_update) override;
void OnPaymentDetailsNotUpdated() override;
private:
void OnPaymentAppResponse(Delegate* delegate,
const base::Optional<std::string>& error_message,
bool is_activity_result_ok,
const std::string& payment_method_identifier,
const std::string& stringified_details);
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_;
const std::unique_ptr<AndroidAppDescription> description_;
base::WeakPtr<AndroidAppCommunication> communication_;
base::WeakPtrFactory<AndroidPaymentApp> weak_ptr_factory_{this};
};
} // namespace payments
#endif // COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_APP_H_
// 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 "components/payments/content/android_payment_app.h"
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/optional.h"
#include "components/payments/content/android_app_communication.h"
#include "components/payments/content/android_app_communication_test_support.h"
#include "components/payments/core/android_app_description.h"
#include "components/payments/core/method_strings.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace payments {
namespace {
class AndroidPaymentAppTest : public testing::Test,
public PaymentApp::Delegate {
public:
static std::unique_ptr<AndroidPaymentApp> CreateAndroidPaymentApp(
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 description = std::make_unique<AndroidAppDescription>();
description->package = "com.example.app";
description->service_names.push_back("com.example.app.Service");
description->activities.emplace_back(
std::make_unique<AndroidActivityDescription>());
description->activities.back()->name = "com.example.app.Activity";
description->activities.back()->default_payment_method =
methods::kGooglePlayBilling;
return std::make_unique<AndroidPaymentApp>(
payment_method_names, stringified_method_data,
GURL("https://top-level-origin.com"),
GURL("https://payment-request-origin.com"), "payment-request-id",
std::move(description), communication);
}
AndroidPaymentAppTest()
: support_(AndroidAppCommunicationTestSupport::Create()) {}
~AndroidPaymentAppTest() override = default;
AndroidPaymentAppTest(const AndroidPaymentAppTest& other) = delete;
AndroidPaymentAppTest& operator=(const AndroidPaymentAppTest& other) = delete;
// PaymentApp::Delegate implementation.
void OnInstrumentDetailsReady(const std::string& method_name,
const std::string& stringified_details,
const PayerData& payer_data) override {
method_name_ = method_name;
stringified_details_ = stringified_details;
}
// PaymentApp::Delegate implementation.
void OnInstrumentDetailsError(const std::string& error_message) override {
error_message_ = error_message;
}
std::unique_ptr<AndroidAppCommunicationTestSupport> support_;
std::unique_ptr<AndroidAppCommunicationTestSupport::ScopedInitialization>
scoped_initialization_;
base::WeakPtr<AndroidAppCommunication> communication_;
std::string method_name_;
std::string stringified_details_;
std::string error_message_;
};
TEST_F(AndroidPaymentAppTest, BrowserShutdown) {
// Explicitly do not initialize AndroidAppCommunication. This can happen
// during browser shutdown.
scoped_initialization_ = support_->CreateScopedInitialization();
support_->ExpectNoPaymentAppInvoke();
auto app = CreateAndroidPaymentApp(communication_);
app->InvokePaymentApp(/*delegate=*/this);
EXPECT_TRUE(error_message_.empty());
EXPECT_TRUE(method_name_.empty());
EXPECT_TRUE(stringified_details_.empty());
}
TEST_F(AndroidPaymentAppTest, UnableToCommunicateToAndroidApps) {
communication_ =
AndroidAppCommunication::GetForBrowserContext(support_->context());
communication_->SetForTesting();
// Explicitly do not create ScopedInitialization.
support_->ExpectNoPaymentAppInvoke();
auto app = CreateAndroidPaymentApp(communication_);
app->InvokePaymentApp(/*delegate=*/this);
EXPECT_EQ("Unable to invoke Android apps.", error_message_);
EXPECT_TRUE(method_name_.empty());
EXPECT_TRUE(stringified_details_.empty());
}
TEST_F(AndroidPaymentAppTest, OnInstrumentDetailsError) {
communication_ =
AndroidAppCommunication::GetForBrowserContext(support_->context());
communication_->SetForTesting();
scoped_initialization_ = support_->CreateScopedInitialization();
support_->ExpectInvokePaymentAppAndRespond(
/*is_activity_result_ok=*/false,
/*payment_method_identifier=*/methods::kGooglePlayBilling,
/*stringified_details=*/"{}");
auto app = CreateAndroidPaymentApp(communication_);
app->InvokePaymentApp(/*delegate=*/this);
if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
EXPECT_EQ("User closed the payment app.", error_message_);
} else {
EXPECT_EQ("Unable to invoke Android apps.", error_message_);
}
EXPECT_TRUE(method_name_.empty());
EXPECT_TRUE(stringified_details_.empty());
}
TEST_F(AndroidPaymentAppTest, OnInstrumentDetailsReady) {
communication_ =
AndroidAppCommunication::GetForBrowserContext(support_->context());
communication_->SetForTesting();
scoped_initialization_ = support_->CreateScopedInitialization();
support_->ExpectInvokePaymentAppAndRespond(
/*is_activity_result_ok=*/true,
/*payment_method_identifier=*/methods::kGooglePlayBilling,
/*stringified_details=*/"{\"status\": \"ok\"}");
auto app = CreateAndroidPaymentApp(communication_);
app->InvokePaymentApp(/*delegate=*/this);
if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
EXPECT_TRUE(error_message_.empty());
EXPECT_EQ(methods::kGooglePlayBilling, method_name_);
EXPECT_EQ("{\"status\": \"ok\"}", stringified_details_);
} else {
EXPECT_EQ("Unable to invoke Android apps.", error_message_);
EXPECT_TRUE(method_name_.empty());
EXPECT_TRUE(stringified_details_.empty());
}
}
} // namespace
} // namespace payments
......@@ -204,5 +204,7 @@ const char kNoContentInPaymentManifest[] =
const char kUnableToInvokeAndroidPaymentApps[] =
"Unable to invoke Android apps.";
const char kUserClosedPaymentApp[] = "User closed the payment app.";
} // namespace errors
} // namespace payments
......@@ -230,6 +230,10 @@ extern const char kNoContentInPaymentManifest[];
// disabled on Chrome OS.
extern const char kUnableToInvokeAndroidPaymentApps[];
// Used when the user has closed the payment app. For example, An Android app
// indicates this by returning Activity.RESULT_CANCELED.
extern const char kUserClosedPaymentApp[];
} // 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