chromium/components/payments/content/android_app_communication_helpers.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_helpers.h"

#include "chromeos/components/payments/mojom/payment_app.mojom.h"
#include "chromeos/components/payments/mojom/payment_app_types.mojom.h"
#include "components/payments/core/chrome_os_error_strings.h"
#include "components/payments/core/method_strings.h"
#include "content/public/browser/browser_thread.h"
#include "url/gurl.h"

namespace payments {

const char kEmptyDictionaryJson[] = "{}";

void OnIsImplemented(
    const std::string& twa_package_name,
    AndroidAppCommunication::GetAppDescriptionsCallback callback,
    chromeos::payments::mojom::IsPaymentImplementedResultPtr response) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DCHECK(!twa_package_name.empty());

  if (response.is_null()) {
    std::move(callback).Run(errors::kEmptyResponse, /*app_descriptions=*/{});
    return;
  }

  if (response->is_error()) {
    std::move(callback).Run(response->get_error(), /*app_descriptions=*/{});
    return;
  }

  if (!response->is_valid()) {
    std::move(callback).Run(errors::kInvalidResponse, /*app_descriptions=*/{});
    return;
  }

  if (response->get_valid()->activity_names.empty()) {
    // If a TWA does not implement PAY intent in any of its activities, then
    // |activity_names| is empty, which is not an error.
    std::move(callback).Run(/*error_message=*/std::nullopt,
                            /*app_descriptions=*/{});
    return;
  }

  if (response->get_valid()->activity_names.size() != 1U) {
    std::move(callback).Run(errors::kMoreThanOneActivity,
                            /*app_descriptions=*/{});
    return;
  }

  auto activity = std::make_unique<AndroidActivityDescription>();
  activity->name = response->get_valid()->activity_names.front();

  // The only available payment method identifier in a Chrome OS TWA at this
  // time.
  activity->default_payment_method = methods::kGooglePlayBilling;

  auto app = std::make_unique<AndroidAppDescription>();
  app->package = twa_package_name;
  app->activities.emplace_back(std::move(activity));
  app->service_names = response->get_valid()->service_names;

  std::vector<std::unique_ptr<AndroidAppDescription>> app_descriptions;
  app_descriptions.emplace_back(std::move(app));

  std::move(callback).Run(/*error_message=*/std::nullopt,
                          std::move(app_descriptions));
}

void OnIsReadyToPay(AndroidAppCommunication::IsReadyToPayCallback callback,
                    chromeos::payments::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=*/std::nullopt,
                          response->get_response());
}

void OnPaymentAppResponse(
    AndroidAppCommunication::InvokePaymentAppCallback callback,
    base::ScopedClosureRunner overlay_state,
    chromeos::payments::mojom::InvokePaymentAppResultPtr response) {
  if (overlay_state) {
    // Dismiss and prevent any further overlays
    overlay_state.RunAndReset();
  }

  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (response.is_null()) {
    std::move(callback).Run(errors::kEmptyResponse,
                            /*is_activity_result_ok=*/false,
                            /*payment_method_identifier=*/"",
                            /*stringified_details=*/kEmptyDictionaryJson);
    return;
  }

  if (response->is_error()) {
    std::move(callback).Run(response->get_error(),
                            /*is_activity_result_ok=*/false,
                            /*payment_method_identifier=*/"",
                            /*stringified_details=*/kEmptyDictionaryJson);
    return;
  }

  if (!response->is_valid()) {
    std::move(callback).Run(errors::kInvalidResponse,
                            /*is_activity_result_ok=*/false,
                            /*payment_method_identifier=*/"",
                            /*stringified_details=*/kEmptyDictionaryJson);
    return;
  }

  // Chrome OS TWA currently supports only methods::kGooglePlayBilling payment
  // method identifier.
  std::move(callback).Run(
      /*error_message=*/std::nullopt,
      response->get_valid()->is_activity_result_ok,
      /*payment_method_identifier=*/methods::kGooglePlayBilling,
      response->get_valid()->stringified_details);
}

chromeos::payments::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,
    std::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 = chromeos::payments::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;
}

std::vector<std::unique_ptr<AndroidAppDescription>> CreateAppForTesting(
    const std::string& package_name,
    const std::string& method_name) {
  auto activity = std::make_unique<AndroidActivityDescription>();
  activity->name = package_name + ".Activity";
  activity->default_payment_method = method_name;
  auto app = std::make_unique<AndroidAppDescription>();
  app->package = package_name;
  app->activities.emplace_back(std::move(activity));
  std::vector<std::unique_ptr<AndroidAppDescription>> app_descriptions;
  app_descriptions.emplace_back(std::move(app));
  return app_descriptions;
}

}  // namespace payments