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