chromium/components/payments/content/android_app_communication_lacros.cc

// 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