// Copyright 2023 The Chromium Authors
// 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_app_communication.h"
#include "chromeos/components/payments/mojom/payment_app.mojom.h"
#include "chromeos/components/payments/mojom/payment_app_types.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "components/payments/content/android_app_communication_helpers.h"
#include "components/payments/core/chrome_os_error_strings.h"
#include "components/payments/core/native_error_strings.h"
#include "content/public/browser/browser_thread.h"
#include "mojo/public/cpp/bindings/remote.h"
namespace payments {
namespace {
using chromeos::payments::mojom::PaymentAppInstance;
PaymentAppInstance* GetPaymentAppInstance() {
auto* service = chromeos::LacrosService::Get();
if (!service || !service->IsAvailable<PaymentAppInstance>()) {
LOG(ERROR) << "Lacros service not available.";
return nullptr;
}
return service->GetRemote<PaymentAppInstance>().get();
}
// Invokes the TWA Android app via Ash using Crosapi.
class AndroidAppCommunicationLacros : public AndroidAppCommunication {
public:
explicit AndroidAppCommunicationLacros(content::BrowserContext* context)
: AndroidAppCommunication(context) {}
~AndroidAppCommunicationLacros() override = default;
// AndroidAppCommunication:
void GetAppDescriptions(const std::string& twa_package_name,
GetAppDescriptionsCallback callback) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (twa_package_name.empty()) {
// Chrome OS supports Android app payment only through a TWA. An empty
// `twa_package_name` indicates that Chrome was not launched from a TWA,
// so there're no payment apps available.
std::move(callback).Run(/*error_message=*/std::nullopt,
/*app_descriptions=*/{});
return;
}
if (!package_name_for_testing_.empty()) {
std::move(callback).Run(
/*error_message=*/std::nullopt,
CreateAppForTesting(package_name_for_testing_, method_for_testing_));
return;
}
PaymentAppInstance* payment_app_instance = GetPaymentAppInstance();
if (!payment_app_instance) {
std::move(callback).Run(errors::kUnableToConnectToAsh,
/*app_descriptions=*/{});
return;
}
payment_app_instance->IsPaymentImplemented(
twa_package_name, base::BindOnce(&OnIsImplemented, twa_package_name,
std::move(callback)));
}
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);
PaymentAppInstance* payment_app_instance = GetPaymentAppInstance();
if (!payment_app_instance) {
std::move(callback).Run(errors::kUnableToConnectToAsh,
/*is_ready_to_pay=*/false);
return;
}
std::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_instance->IsReadyToPay(
std::move(parameters),
base::BindOnce(&OnIsReadyToPay, std::move(callback)));
}
void InvokePaymentApp(
const std::string& package_name,
const std::string& activity_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,
const base::UnguessableToken& request_token,
content::WebContents* web_contents,
const std::optional<base::UnguessableToken>& twa_instance_identifier,
InvokePaymentAppCallback callback) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// TODO(crbug.com/40247053): Ensure the Android Play Billing interface is
// overlaid on top of the browser window.
std::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;
}
PaymentAppInstance* payment_app_instance = GetPaymentAppInstance();
if (!payment_app_instance) {
std::move(callback).Run(errors::kUnableToConnectToAsh,
/*is_activity_result_ok=*/false,
/*payment_method_identifier=*/"",
/*stringified_details=*/kEmptyDictionaryJson);
return;
}
auto parameters = CreatePaymentParameters(
package_name, activity_name, stringified_method_data, top_level_origin,
payment_request_origin, payment_request_id, &error_message);
if (!parameters) {
std::move(callback).Run(error_message,
/*is_activity_result_ok=*/false,
/*payment_method_identifier=*/"",
/*stringified_details=*/kEmptyDictionaryJson);
return;
}
parameters->request_token = request_token.ToString();
parameters->twa_instance_identifier = twa_instance_identifier;
payment_app_instance->InvokePaymentApp(
std::move(parameters),
base::BindOnce(&OnPaymentAppResponse, std::move(callback),
base::ScopedClosureRunner()));
}
void AbortPaymentApp(const base::UnguessableToken& request_token,
AbortPaymentAppCallback callback) override {
PaymentAppInstance* payment_app_instance = GetPaymentAppInstance();
if (!payment_app_instance) {
std::move(callback).Run(false);
return;
}
payment_app_instance->AbortPaymentApp(request_token.ToString(),
std::move(callback));
}
void SetForTesting() override {}
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:
std::string package_name_for_testing_;
std::string method_for_testing_;
std::string response_for_testing_;
};
} // namespace
// Declared in cross-platform header file. See:
// //components/payments/content/android_app_communication.h
// static
std::unique_ptr<AndroidAppCommunication> AndroidAppCommunication::Create(
content::BrowserContext* context) {
return std::make_unique<AndroidAppCommunicationLacros>(context);
}
} // namespace payments