blob: 09561ebcd08508236aec144683ce081aaaf1c672 [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/secure_payment_confirmation_controller.h"
#include "base/check.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "components/payments/content/content_payment_request_delegate.h"
#include "components/payments/content/payment_request.h"
#include "components/payments/content/secure_payment_confirmation_app.h"
#include "components/payments/content/secure_payment_confirmation_transaction_mode.h"
#include "components/payments/core/currency_formatter.h"
#include "components/payments/core/features.h"
#include "components/payments/core/method_strings.h"
#include "components/strings/grit/components_strings.h"
#include "components/url_formatter/elide_url.h"
#include "third_party/blink/public/common/features_generated.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/url_constants.h"
namespace payments {
SecurePaymentConfirmationController::SecurePaymentConfirmationController(
base::WeakPtr<PaymentRequest> request)
: request_(request) {
DCHECK(request_);
}
SecurePaymentConfirmationController::~SecurePaymentConfirmationController() =
default;
void SecurePaymentConfirmationController::ShowDialog() {
#if BUILDFLAG(IS_ANDROID)
NOTREACHED();
#else
if (!request_ || !request_->spec())
return;
if (!request_->state()->IsInitialized()) {
number_of_initialization_tasks_++;
request_->state()->AddInitializationObserver(this);
}
if (!request_->spec()->IsInitialized()) {
number_of_initialization_tasks_++;
request_->spec()->AddInitializationObserver(this);
}
if (number_of_initialization_tasks_ == 0)
SetupModelAndShowDialogIfApplicable();
#endif // BUILDFLAG(IS_ANDROID)
}
void SecurePaymentConfirmationController::
SetupModelAndShowDialogIfApplicable() {
DCHECK(!view_);
// If no apps are available then don't show any UI. The payment_request.cc
// code will reject the PaymentRequest.show() call with appropriate error
// message on its own.
if (!request_ || !request_->state() || !request_->spec() ||
request_->state()->available_apps().empty()) {
return;
}
if (!request_->web_contents() || !request_->state()->selected_app() ||
request_->state()->selected_app()->type() != PaymentApp::Type::INTERNAL ||
request_->state()->selected_app()->GetAppMethodNames().size() != 1 ||
*request_->state()->selected_app()->GetAppMethodNames().begin() !=
methods::kSecurePaymentConfirmation ||
request_->state()->available_apps().size() != 1 || !request_->spec() ||
!request_->spec()->IsSecurePaymentConfirmationRequested() ||
request_->spec()->request_shipping() ||
request_->spec()->request_payer_name() ||
request_->spec()->request_payer_email() ||
request_->spec()->request_payer_phone() ||
request_->spec()->method_data().size() != 1 ||
!request_->spec()->method_data().front() ||
request_->spec()->method_data().front()->supported_method !=
methods::kSecurePaymentConfirmation ||
!request_->spec()->method_data().front()->secure_payment_confirmation ||
(request_->spec()
->method_data()
.front()
->secure_payment_confirmation->payee_origin.has_value() &&
request_->spec()
->method_data()
.front()
->secure_payment_confirmation->payee_origin->scheme() !=
url::kHttpsScheme)) {
OnCancel();
return;
}
model_.set_verify_button_label(l10n_util::GetStringUTF16(
IDS_SECURE_PAYMENT_CONFIRMATION_VERIFY_BUTTON_LABEL));
model_.set_cancel_button_label(l10n_util::GetStringUTF16(IDS_CANCEL));
model_.set_progress_bar_visible(false);
model_.set_title(l10n_util::GetStringUTF16(
IDS_SECURE_PAYMENT_CONFIRMATION_VERIFY_PURCHASE));
model_.set_merchant_label(
l10n_util::GetStringUTF16(IDS_SECURE_PAYMENT_CONFIRMATION_STORE_LABEL));
std::optional<std::string>& payee_name =
request_->spec()
->method_data()
.front()
->secure_payment_confirmation->payee_name;
if (payee_name.has_value()) {
model_.set_merchant_name(
std::optional<std::u16string>(base::UTF8ToUTF16(payee_name.value())));
}
std::optional<url::Origin>& origin =
request_->spec()
->method_data()
.front()
->secure_payment_confirmation->payee_origin;
if (origin.has_value()) {
model_.set_merchant_origin(std::optional<std::u16string>(
url_formatter::FormatUrlForSecurityDisplay(
origin.value().GetURL(),
url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC)));
}
SecurePaymentConfirmationApp* app =
static_cast<SecurePaymentConfirmationApp*>(
request_->state()->selected_app());
model_.set_instrument_label(l10n_util::GetStringUTF16(
IDS_PAYMENT_REQUEST_PAYMENT_METHOD_SECTION_NAME));
model_.set_instrument_value(app->GetLabel());
model_.set_instrument_icon(app->icon_bitmap());
model_.set_total_label(
l10n_util::GetStringUTF16(IDS_SECURE_PAYMENT_CONFIRMATION_TOTAL_LABEL));
const mojom::PaymentItemPtr& total = request_->spec()->GetTotal(app);
std::u16string total_value = base::UTF8ToUTF16(total->amount->currency);
model_.set_total_value(
base::StrCat({base::UTF8ToUTF16(total->amount->currency), u" ",
CurrencyFormatter(total->amount->currency,
request_->state()->GetApplicationLocale())
.Format(total->amount->value)}));
model_.set_opt_out_visible(request_->spec()
->method_data()
.front()
->secure_payment_confirmation->show_opt_out);
model_.set_opt_out_label(
l10n_util::GetStringUTF16(IDS_SECURE_PAYMENT_CONFIRMATION_OPT_OUT_LABEL));
model_.set_opt_out_link_label(l10n_util::GetStringUTF16(
IDS_SECURE_PAYMENT_CONFIRMATION_OPT_OUT_LINK_LABEL));
model_.set_relying_party_id(
base::UTF8ToUTF16(request_->spec()
->method_data()
.front()
->secure_payment_confirmation->rp_id));
view_ = SecurePaymentConfirmationView::Create(
request_->state()->GetPaymentRequestDelegate()->GetPaymentUIObserver());
view_->ShowDialog(
request_->web_contents(), model_.GetWeakPtr(),
base::BindOnce(&SecurePaymentConfirmationController::OnConfirm,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&SecurePaymentConfirmationController::OnCancel,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&SecurePaymentConfirmationController::OnOptOut,
weak_ptr_factory_.GetWeakPtr()));
// For automated testing, SPC can be placed in an 'autoaccept' or
// 'autoreject' mode, where the dialog should immediately be
// accepted/rejected without user interaction. We deliberately wait until
// after the dialog is created and shown to handle this, in order to keep the
// automation codepath as close to the 'real' one as possible.
if (request_->spc_transaction_mode() != SPCTransactionMode::kNone) {
// TODO(crbug.com/417426346): Once the desktop SPC controller supports the
// new fallback flow, it should handle SPCTransactionMode::
// kAutoAuthAnotherWay here.
if (request_->spc_transaction_mode() == SPCTransactionMode::kAutoAccept) {
OnConfirm();
} else if (request_->spc_transaction_mode() ==
SPCTransactionMode::kAutoOptOut) {
OnOptOut();
} else {
OnCancel();
}
}
}
void SecurePaymentConfirmationController::RetryDialog() {
// Retry is not supported.
OnCancel();
}
void SecurePaymentConfirmationController::CloseDialog() {
if (view_)
view_->HideDialog();
}
void SecurePaymentConfirmationController::ShowErrorMessage() {
// Error message is not supported.
OnCancel();
}
void SecurePaymentConfirmationController::ShowProcessingSpinner() {
if (!view_)
return;
model_.set_progress_bar_visible(true);
model_.set_verify_button_enabled(false);
model_.set_cancel_button_enabled(false);
view_->OnModelUpdated();
}
bool SecurePaymentConfirmationController::IsInteractive() const {
return view_ && !model_.progress_bar_visible();
}
void SecurePaymentConfirmationController::ShowPaymentHandlerScreen(
const GURL& url,
PaymentHandlerOpenWindowCallback callback) {
// Payment handler screen is not supported.
NOTREACHED();
}
void SecurePaymentConfirmationController::ConfirmPaymentForTesting() {
OnConfirm();
}
bool SecurePaymentConfirmationController::ClickOptOutForTesting() {
// This should only be called when the view is showing.
DCHECK(view_);
return view_->ClickOptOutForTesting();
}
void SecurePaymentConfirmationController::OnInitialized(
InitializationTask* initialization_task) {
if (--number_of_initialization_tasks_ == 0)
SetupModelAndShowDialogIfApplicable();
}
void SecurePaymentConfirmationController::OnCancel() {
base::UmaHistogramEnumeration("SecurePaymentRequest.Transaction.Outcome",
SecurePaymentRequestOutcome::kAnotherWay);
CloseDialog();
if (!request_)
return;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&PaymentRequest::OnUserCancelled, request_));
}
void SecurePaymentConfirmationController::OnOptOut() {
base::UmaHistogramEnumeration("SecurePaymentRequest.Transaction.Outcome",
SecurePaymentRequestOutcome::kOptOut);
// Set the opt out clicked state on the model so that the view knows not to
// call back to OnCancel when the dialog is closed.
model_.set_opt_out_clicked(true);
CloseDialog();
if (!request_)
return;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&PaymentRequest::OnUserOptedOut, request_));
}
void SecurePaymentConfirmationController::OnConfirm() {
base::UmaHistogramEnumeration("SecurePaymentRequest.Transaction.Outcome",
SecurePaymentRequestOutcome::kAccept);
if (!request_)
return;
ShowProcessingSpinner();
// This will trigger WebAuthn with OS-level UI (if any) on top of the |view_|
// with its animated processing spinner. For example, on Linux, there's no
// OS-level UI, while on MacOS, there's an OS-level prompt for the Touch ID
// that shows on top of Chrome.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&PaymentRequest::Pay, request_));
}
base::WeakPtr<SecurePaymentConfirmationController>
SecurePaymentConfirmationController::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
} // namespace payments