blob: 912187ab5692d7c36cdb7de720fbc5cdc779e04d [file] [log] [blame]
// Copyright 2020 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_payment_app_factory.h"
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/stl_util.h"
#include "base/supports_user_data.h"
#include "components/payments/content/android_app_communication.h"
#include "components/payments/content/android_payment_app.h"
#include "components/payments/content/content_payment_request_delegate.h"
#include "components/payments/content/payment_request_spec.h"
#include "components/payments/core/android_app_description.h"
#include "components/payments/core/android_app_description_tools.h"
#include "components/payments/core/method_strings.h"
#include "components/payments/core/native_error_strings.h"
#include "components/payments/core/payment_request_data_util.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/document_user_data.h"
#include "content/public/browser/web_contents.h"
namespace payments {
namespace {
class AppFinder : public base::SupportsUserData::Data {
public:
static base::WeakPtr<AppFinder> CreateAndSetOwnedBy(
base::SupportsUserData* owner) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(owner);
auto owned = std::make_unique<AppFinder>(owner);
auto weak_ptr = owned->weak_ptr_factory_.GetWeakPtr();
const void* key = owned.get();
owner->SetUserData(key, std::move(owned));
return weak_ptr;
}
explicit AppFinder(base::SupportsUserData* owner) : owner_(owner) {}
~AppFinder() override = default;
AppFinder(const AppFinder& other) = delete;
AppFinder& operator=(const AppFinder& other) = delete;
void FindApps(base::WeakPtr<AndroidAppCommunication> communication,
base::WeakPtr<PaymentAppFactory::Delegate> delegate) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(nullptr, delegate_.get());
DCHECK_NE(nullptr, delegate.get());
DCHECK_EQ(0U, number_of_pending_is_ready_to_pay_queries_);
DCHECK_EQ(nullptr, communication_.get());
DCHECK_NE(nullptr, communication.get());
DCHECK(delegate->GetSpec());
DCHECK(delegate->GetSpec()->details().id.has_value());
delegate_ = delegate;
communication_ = communication;
std::set<std::string> twa_payment_method_names = {
methods::kGooglePlayBilling,
};
if (base::STLSetIntersection<std::set<std::string>>(
delegate_->GetSpec()->payment_method_identifiers_set(),
twa_payment_method_names)
.empty()) {
OnDoneCreatingPaymentApps();
return;
}
delegate_->GetTwaPackageName(base::BindOnce(
&AppFinder::OnGetTwaPackageName, weak_ptr_factory_.GetWeakPtr()));
}
void OnGetTwaPackageName(const std::string& twa_package_name) {
if (twa_package_name.empty()) {
OnDoneCreatingPaymentApps();
return;
}
communication_->GetAppDescriptions(
twa_package_name, base::BindOnce(&AppFinder::OnGetAppDescriptions,
weak_ptr_factory_.GetWeakPtr()));
}
private:
// Check that our required dependencies are still valid, i.e. that the page
// isn't currently being torn down.
bool PageIsValid() {
return communication_ && delegate_ && delegate_->GetSpec() &&
delegate_->GetInitiatorRenderFrameHost();
}
void OnGetAppDescriptions(
const std::optional<std::string>& error_message,
std::vector<std::unique_ptr<AndroidAppDescription>> app_descriptions) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// The browser could be shutting down.
if (!PageIsValid()) {
return;
}
if (error_message.has_value()) {
delegate_->OnPaymentAppCreationError(error_message.value());
OnDoneCreatingPaymentApps();
return;
}
std::vector<std::unique_ptr<AndroidAppDescription>> single_activity_apps;
for (size_t i = 0; i < app_descriptions.size(); ++i) {
auto app = std::move(app_descriptions[i]);
if (app->service_names.size() > 1U) {
delegate_->OnPaymentAppCreationError(errors::kMoreThanOneService);
continue;
}
// Move each activity in the given |app| to its own AndroidAppDescription
// in |single_activity_apps|, so the code can treat each PAY intent as its
// own payment app. This allows Android apps to implement PAY intent in
// multiple activities with different names and icons for different use
// cases.
SplitPotentiallyMultipleActivities(std::move(app), &single_activity_apps);
}
number_of_pending_is_ready_to_pay_queries_ = single_activity_apps.size();
if (number_of_pending_is_ready_to_pay_queries_ == 0U) {
OnDoneCreatingPaymentApps();
return;
}
for (size_t i = 0; i < single_activity_apps.size(); ++i) {
std::unique_ptr<AndroidAppDescription> single_activity_app =
std::move(single_activity_apps[i]);
const std::string& default_method =
single_activity_app->activities.front()->default_payment_method;
DCHECK_EQ(methods::kGooglePlayBilling, default_method);
std::set<std::string> supported_payment_methods = {default_method};
std::set<std::string> payment_method_names =
base::STLSetIntersection<std::set<std::string>>(
delegate_->GetSpec()->payment_method_identifiers_set(),
supported_payment_methods);
std::unique_ptr<std::map<std::string, std::set<std::string>>>
stringified_method_data = data_util::FilterStringifiedMethodData(
delegate_->GetSpec()->stringified_method_data(),
supported_payment_methods);
// TODO(crbug.com/40106647): Download the web app manifest for
// |default_payment_method_name| to verify Android app signature.
// Skip querying IS_READY_TO_PAY service when Chrome is off-the-record or
// when the app does not implement the IS_READY_TO_PAY service.
if (delegate_->IsOffTheRecord() ||
single_activity_app->service_names.empty()) {
OnIsReadyToPay(std::move(single_activity_app), payment_method_names,
std::move(stringified_method_data),
/*error_message=*/std::nullopt,
/*is_ready_to_pay=*/true);
continue;
}
const std::string package = single_activity_app->package;
const std::string service_name =
single_activity_app->service_names.front();
std::map<std::string, std::set<std::string>>
stringified_method_data_copy = *stringified_method_data;
communication_->IsReadyToPay(
package, service_name, stringified_method_data_copy,
delegate_->GetTopOrigin(), delegate_->GetFrameOrigin(),
delegate_->GetSpec()->details().id.value(),
base::BindOnce(&AppFinder::OnIsReadyToPay,
weak_ptr_factory_.GetWeakPtr(),
std::move(single_activity_app), payment_method_names,
std::move(stringified_method_data)));
}
}
void OnIsReadyToPay(
std::unique_ptr<AndroidAppDescription> app_description,
const std::set<std::string>& payment_method_names,
std::unique_ptr<std::map<std::string, std::set<std::string>>>
stringified_method_data,
const std::optional<std::string>& error_message,
bool is_ready_to_pay) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_LT(0U, number_of_pending_is_ready_to_pay_queries_);
// The browser could be shutting down.
if (!PageIsValid()) {
OnDoneCreatingPaymentApps();
return;
}
if (error_message.has_value()) {
delegate_->OnPaymentAppCreationError(error_message.value());
} else if (is_ready_to_pay) {
delegate_->OnPaymentAppCreated(std::make_unique<AndroidPaymentApp>(
payment_method_names, std::move(stringified_method_data),
delegate_->GetTopOrigin(), delegate_->GetFrameOrigin(),
delegate_->GetSpec()->details().id.value(),
std::move(app_description), communication_,
delegate_->GetInitiatorRenderFrameHost()->GetGlobalId(),
delegate_->GetChromeOSTWAInstanceId()));
}
if (--number_of_pending_is_ready_to_pay_queries_ == 0)
OnDoneCreatingPaymentApps();
}
void OnDoneCreatingPaymentApps() {
if (delegate_)
delegate_->OnDoneCreatingPaymentApps();
owner_->RemoveUserData(this);
}
raw_ptr<base::SupportsUserData> owner_;
base::WeakPtr<PaymentAppFactory::Delegate> delegate_;
size_t number_of_pending_is_ready_to_pay_queries_ = 0;
base::WeakPtr<AndroidAppCommunication> communication_;
base::WeakPtrFactory<AppFinder> weak_ptr_factory_{this};
};
} // namespace
AndroidPaymentAppFactory::AndroidPaymentAppFactory(
base::WeakPtr<AndroidAppCommunication> communication)
: PaymentAppFactory(PaymentApp::Type::NATIVE_MOBILE_APP),
communication_(communication) {
DCHECK(communication_);
}
AndroidPaymentAppFactory::~AndroidPaymentAppFactory() = default;
void AndroidPaymentAppFactory::Create(base::WeakPtr<Delegate> delegate) {
content::WebContents* web_contents = delegate->GetWebContents();
if (web_contents) {
auto app_finder = AppFinder::CreateAndSetOwnedBy(web_contents);
app_finder->FindApps(communication_, delegate);
}
}
} // namespace payments