| // Copyright 2017 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/service_worker_payment_app.h" |
| |
| #include <limits> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/contains.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/stl_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "components/payments/content/payment_event_response_util.h" |
| #include "components/payments/content/payment_handler_host.h" |
| #include "components/payments/content/payment_request_converter.h" |
| #include "components/payments/core/features.h" |
| #include "components/payments/core/method_strings.h" |
| #include "components/payments/core/pre_purchase_query.h" |
| #include "content/public/browser/payment_app_provider.h" |
| #include "content/public/browser/payment_app_provider_util.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_features.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "url/origin.h" |
| |
| namespace payments { |
| |
| // Service worker payment app provides icon through bitmap, so set 0 as invalid |
| // resource Id. |
| ServiceWorkerPaymentApp::ServiceWorkerPaymentApp( |
| content::WebContents* web_contents, |
| const GURL& top_origin, |
| const GURL& frame_origin, |
| base::WeakPtr<PaymentRequestSpec> spec, |
| std::unique_ptr<content::StoredPaymentApp> stored_payment_app_info, |
| bool is_incognito, |
| const base::RepeatingClosure& show_processing_spinner) |
| : PaymentApp(0, PaymentApp::Type::SERVICE_WORKER_APP), |
| top_origin_(top_origin), |
| frame_origin_(frame_origin), |
| spec_(spec), |
| stored_payment_app_info_(std::move(stored_payment_app_info)), |
| delegate_(nullptr), |
| is_incognito_(is_incognito), |
| show_processing_spinner_(show_processing_spinner), |
| can_make_payment_result_(false), |
| has_enrolled_instrument_result_(false), |
| needs_installation_(false), |
| web_contents_(web_contents->GetWeakPtr()) { |
| DCHECK(web_contents); |
| DCHECK(top_origin_.is_valid()); |
| DCHECK(frame_origin_.is_valid()); |
| |
| app_method_names_.insert(stored_payment_app_info_->enabled_methods.begin(), |
| stored_payment_app_info_->enabled_methods.end()); |
| } |
| |
| // Service worker payment app provides icon through bitmap, so set 0 as invalid |
| // resource Id. |
| ServiceWorkerPaymentApp::ServiceWorkerPaymentApp( |
| content::WebContents* web_contents, |
| const GURL& top_origin, |
| const GURL& frame_origin, |
| base::WeakPtr<PaymentRequestSpec> spec, |
| std::unique_ptr<WebAppInstallationInfo> installable_payment_app_info, |
| const std::string& enabled_method, |
| bool is_incognito, |
| const base::RepeatingClosure& show_processing_spinner) |
| : PaymentApp(0, PaymentApp::Type::SERVICE_WORKER_APP), |
| top_origin_(top_origin), |
| frame_origin_(frame_origin), |
| spec_(spec), |
| delegate_(nullptr), |
| is_incognito_(is_incognito), |
| show_processing_spinner_(show_processing_spinner), |
| can_make_payment_result_(false), |
| has_enrolled_instrument_result_(false), |
| needs_installation_(true), |
| installable_web_app_info_(std::move(installable_payment_app_info)), |
| installable_enabled_method_(enabled_method), |
| web_contents_(web_contents->GetWeakPtr()) { |
| DCHECK(web_contents); |
| DCHECK(top_origin_.is_valid()); |
| DCHECK(frame_origin_.is_valid()); |
| |
| app_method_names_.insert(installable_enabled_method_); |
| } |
| |
| ServiceWorkerPaymentApp::~ServiceWorkerPaymentApp() { |
| if (delegate_) { |
| // If there's a payment in progress, abort it before destroying this so that |
| // it can update its internal state. Since the PaymentRequest will be |
| // destroyed, pass an empty callback to the payment app. |
| AbortPaymentApp(/*abort_callback=*/base::DoNothing()); |
| } |
| } |
| |
| void ServiceWorkerPaymentApp::ValidateCanMakePayment( |
| ValidateCanMakePaymentCallback callback) { |
| if (!spec_) |
| return; |
| |
| // Returns true for payment app that needs installation. |
| if (needs_installation_) { |
| OnCanMakePaymentEventSkipped(std::move(callback)); |
| return; |
| } |
| |
| // Returns true if we are in incognito (avoiding sending the event to the |
| // payment handler). |
| if (is_incognito_) { |
| OnCanMakePaymentEventSkipped(std::move(callback)); |
| return; |
| } |
| |
| // Do not send CanMakePayment event to payment apps that have not been |
| // explicitly verified. |
| if (!stored_payment_app_info_->has_explicitly_verified_methods) { |
| OnCanMakePaymentEventSkipped(std::move(callback)); |
| return; |
| } |
| |
| mojom::CanMakePaymentEventDataPtr event_data = |
| CreateCanMakePaymentEventData(); |
| if (event_data.is_null()) { |
| // This could only happen if this app only supports non-url based payment |
| // methods of the payment request, then return true and do not send |
| // CanMakePaymentEvent to the payment app. |
| OnCanMakePaymentEventSkipped(std::move(callback)); |
| return; |
| } |
| |
| auto* payment_app_provider = GetPaymentAppProvider(); |
| if (!payment_app_provider) |
| return; |
| |
| base::UmaHistogramEnumeration("PaymentRequest.PrePurchaseQuery", |
| PrePurchaseQuery::kServiceWorkerEvent); |
| payment_app_provider->CanMakePayment( |
| stored_payment_app_info_->registration_id, |
| url::Origin::Create(stored_payment_app_info_->scope), |
| *spec_->details().id, std::move(event_data), |
| base::BindOnce(&ServiceWorkerPaymentApp::OnCanMakePaymentEventResponded, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| mojom::CanMakePaymentEventDataPtr |
| ServiceWorkerPaymentApp::CreateCanMakePaymentEventData() { |
| if (!spec_) |
| return nullptr; |
| |
| std::set<std::string> requested_url_methods; |
| for (const auto& method : spec_->payment_method_identifiers_set()) { |
| GURL url_method(method); |
| if (url_method.is_valid()) { |
| requested_url_methods.insert(method); |
| } |
| } |
| std::set<std::string> supported_url_methods = |
| base::STLSetIntersection<std::set<std::string>>(requested_url_methods, |
| GetAppMethodNames()); |
| // Only fire CanMakePaymentEvent if this app supports non-url based payment |
| // methods of the payment request. |
| if (supported_url_methods.empty()) |
| return nullptr; |
| |
| mojom::CanMakePaymentEventDataPtr event_data = |
| mojom::CanMakePaymentEventData::New(); |
| |
| event_data->top_origin = top_origin_; |
| event_data->payment_request_origin = frame_origin_; |
| |
| DCHECK(spec_->details().modifiers); |
| for (const auto& modifier : *spec_->details().modifiers) { |
| if (base::Contains(supported_url_methods, |
| modifier->method_data->supported_method)) { |
| event_data->modifiers.emplace_back(modifier.Clone()); |
| } |
| } |
| |
| for (const auto& data : spec_->method_data()) { |
| if (base::Contains(supported_url_methods, data->supported_method)) { |
| event_data->method_data.push_back(data.Clone()); |
| } |
| } |
| |
| return event_data; |
| } |
| |
| void ServiceWorkerPaymentApp::OnCanMakePaymentEventSkipped( |
| ValidateCanMakePaymentCallback callback) { |
| // |can_make_payment| is true as long as there is a matching payment handler. |
| can_make_payment_result_ = true; |
| has_enrolled_instrument_result_ = false; |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &ServiceWorkerPaymentApp::CallValidateCanMakePaymentCallback, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void ServiceWorkerPaymentApp::OnCanMakePaymentEventResponded( |
| ValidateCanMakePaymentCallback callback, |
| mojom::CanMakePaymentResponsePtr response) { |
| // |can_make_payment| is true as long as there is a matching payment handler. |
| can_make_payment_result_ = true; |
| has_enrolled_instrument_result_ = response->can_make_payment; |
| base::UmaHistogramBoolean("PaymentRequest.EventResponse.CanMakePayment", |
| response->can_make_payment); |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &ServiceWorkerPaymentApp::CallValidateCanMakePaymentCallback, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void ServiceWorkerPaymentApp::CallValidateCanMakePaymentCallback( |
| ValidateCanMakePaymentCallback callback) { |
| std::move(callback).Run(weak_ptr_factory_.GetWeakPtr(), true); |
| } |
| |
| void ServiceWorkerPaymentApp::InvokePaymentApp( |
| base::WeakPtr<Delegate> delegate) { |
| delegate_ = delegate; |
| auto* payment_app_provider = GetPaymentAppProvider(); |
| if (!payment_app_provider) |
| return; |
| |
| if (needs_installation_) { |
| payment_app_provider->InstallAndInvokePaymentApp( |
| CreatePaymentRequestEventData(), installable_web_app_info_->name, |
| installable_web_app_info_->icon == nullptr |
| ? SkBitmap() |
| : *(installable_web_app_info_->icon), |
| GURL(installable_web_app_info_->sw_js_url), |
| GURL(installable_web_app_info_->sw_scope), |
| installable_web_app_info_->sw_use_cache, installable_enabled_method_, |
| installable_web_app_info_->supported_delegations, |
| base::BindOnce( |
| &ServiceWorkerPaymentApp::OnPaymentAppIdentity, |
| weak_ptr_factory_.GetWeakPtr(), |
| url::Origin::Create(GURL(installable_web_app_info_->sw_scope))), |
| base::BindOnce(&ServiceWorkerPaymentApp::OnPaymentAppResponse, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| url::Origin sw_origin = |
| url::Origin::Create(stored_payment_app_info_->scope); |
| OnPaymentAppIdentity(sw_origin, stored_payment_app_info_->registration_id); |
| payment_app_provider->InvokePaymentApp( |
| stored_payment_app_info_->registration_id, sw_origin, |
| CreatePaymentRequestEventData(), |
| base::BindOnce(&ServiceWorkerPaymentApp::OnPaymentAppResponse, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| show_processing_spinner_.Run(); |
| } |
| |
| void ServiceWorkerPaymentApp::OnPaymentAppWindowClosed() { |
| delegate_ = nullptr; |
| auto* payment_app_provider = GetPaymentAppProvider(); |
| if (!payment_app_provider) |
| return; |
| payment_app_provider->OnClosingOpenedWindow( |
| mojom::PaymentEventResponseType::PAYMENT_HANDLER_WINDOW_CLOSING); |
| } |
| |
| mojom::PaymentRequestEventDataPtr |
| ServiceWorkerPaymentApp::CreatePaymentRequestEventData() { |
| mojom::PaymentRequestEventDataPtr event_data = |
| mojom::PaymentRequestEventData::New(); |
| |
| if (!spec_) |
| return event_data; |
| |
| event_data->top_origin = top_origin_; |
| event_data->payment_request_origin = frame_origin_; |
| |
| if (spec_->details().id.has_value()) |
| event_data->payment_request_id = spec_->details().id.value(); |
| |
| event_data->total = spec_->details().total->amount.Clone(); |
| |
| DCHECK(spec_->details().modifiers); |
| for (const auto& modifier : *spec_->details().modifiers) { |
| if (base::Contains(GetAppMethodNames(), |
| modifier->method_data->supported_method)) { |
| event_data->modifiers.emplace_back(modifier.Clone()); |
| } |
| } |
| |
| for (const auto& data : spec_->method_data()) { |
| if (base::Contains(GetAppMethodNames(), data->supported_method)) { |
| event_data->method_data.push_back(data.Clone()); |
| } |
| } |
| |
| if (HandlesPayerName()) { |
| DCHECK(spec_->request_payer_name()); |
| |
| if (!event_data->payment_options) |
| event_data->payment_options = mojom::PaymentOptions::New(); |
| |
| event_data->payment_options->request_payer_name = true; |
| } |
| |
| if (HandlesPayerEmail()) { |
| DCHECK(spec_->request_payer_email()); |
| |
| if (!event_data->payment_options) |
| event_data->payment_options = mojom::PaymentOptions::New(); |
| |
| event_data->payment_options->request_payer_email = true; |
| } |
| |
| if (HandlesPayerPhone()) { |
| DCHECK(spec_->request_payer_phone()); |
| |
| if (!event_data->payment_options) |
| event_data->payment_options = mojom::PaymentOptions::New(); |
| |
| event_data->payment_options->request_payer_phone = true; |
| } |
| |
| if (HandlesShippingAddress()) { |
| DCHECK(spec_->request_shipping()); |
| |
| if (!event_data->payment_options) |
| event_data->payment_options = mojom::PaymentOptions::New(); |
| |
| event_data->payment_options->request_shipping = true; |
| event_data->payment_options->shipping_type = |
| mojom::PaymentShippingType::SHIPPING; |
| if (spec_->shipping_type() == PaymentShippingType::DELIVERY) { |
| event_data->payment_options->shipping_type = |
| mojom::PaymentShippingType::DELIVERY; |
| } else if (spec_->shipping_type() == PaymentShippingType::PICKUP) { |
| event_data->payment_options->shipping_type = |
| mojom::PaymentShippingType::PICKUP; |
| } |
| |
| event_data->shipping_options = |
| std::vector<mojom::PaymentShippingOptionPtr>(); |
| for (const auto& option : spec_->GetShippingOptions()) { |
| event_data->shipping_options->push_back(option.Clone()); |
| } |
| } |
| |
| event_data->payment_handler_host = std::move(payment_handler_host_remote_); |
| |
| return event_data; |
| } |
| |
| void ServiceWorkerPaymentApp::OnPaymentAppResponse( |
| mojom::PaymentHandlerResponsePtr response) { |
| if (!delegate_) |
| return; |
| |
| if (response->response_type == |
| mojom::PaymentEventResponseType::PAYMENT_EVENT_SUCCESS) { |
| DCHECK(!response->method_name.empty()); |
| DCHECK(!response->stringified_details.empty()); |
| delegate_->OnInstrumentDetailsReady( |
| response->method_name, response->stringified_details, |
| PayerData(response->payer_name.value_or(""), |
| response->payer_email.value_or(""), |
| response->payer_phone.value_or(""), |
| std::move(response->shipping_address), |
| response->shipping_option.value_or(""))); |
| } else { |
| DCHECK(response->method_name.empty()); |
| DCHECK(response->stringified_details.empty()); |
| DCHECK(response->payer_name.value_or("").empty()); |
| DCHECK(response->payer_email.value_or("").empty()); |
| DCHECK(response->payer_phone.value_or("").empty()); |
| DCHECK(!response->shipping_address); |
| DCHECK(response->shipping_option.value_or("").empty()); |
| delegate_->OnInstrumentDetailsError(std::string( |
| ConvertPaymentEventResponseTypeToErrorString(response->response_type))); |
| } |
| |
| delegate_ = nullptr; |
| } |
| |
| bool ServiceWorkerPaymentApp::IsCompleteForPayment() const { |
| return true; |
| } |
| |
| bool ServiceWorkerPaymentApp::CanPreselect() const { |
| // Do not preselect the payment app when the name and/or icon is missing. |
| return !GetLabel().empty() && icon_bitmap() && !icon_bitmap()->drawsNothing(); |
| } |
| |
| std::u16string ServiceWorkerPaymentApp::GetMissingInfoLabel() const { |
| NOTREACHED(); |
| return std::u16string(); |
| } |
| |
| bool ServiceWorkerPaymentApp::HasEnrolledInstrument() const { |
| // This app should not be used when can_make_payment_result_ is false, so this |
| // interface should not be invoked. |
| DCHECK(can_make_payment_result_); |
| return has_enrolled_instrument_result_; |
| } |
| |
| void ServiceWorkerPaymentApp::RecordUse() { |
| NOTIMPLEMENTED(); |
| } |
| |
| bool ServiceWorkerPaymentApp::NeedsInstallation() const { |
| return needs_installation_; |
| } |
| |
| std::string ServiceWorkerPaymentApp::GetId() const { |
| return needs_installation_ ? installable_web_app_info_->sw_scope |
| : stored_payment_app_info_->scope.spec(); |
| } |
| |
| std::u16string ServiceWorkerPaymentApp::GetLabel() const { |
| return base::UTF8ToUTF16(needs_installation_ |
| ? installable_web_app_info_->name |
| : stored_payment_app_info_->name); |
| } |
| |
| std::u16string ServiceWorkerPaymentApp::GetSublabel() const { |
| if (needs_installation_) { |
| DCHECK(GURL(installable_web_app_info_->sw_scope).is_valid()); |
| return base::UTF8ToUTF16( |
| url::Origin::Create(GURL(installable_web_app_info_->sw_scope)).host()); |
| } |
| return base::UTF8ToUTF16( |
| url::Origin::Create(stored_payment_app_info_->scope).host()); |
| } |
| |
| bool ServiceWorkerPaymentApp::IsValidForModifier( |
| const std::string& method) const { |
| // Payment app that needs installation only supports url based payment |
| // methods. |
| if (needs_installation_) |
| return installable_enabled_method_ == method; |
| |
| bool is_valid = false; |
| IsValidForPaymentMethodIdentifier(method, &is_valid); |
| return is_valid; |
| } |
| |
| base::WeakPtr<PaymentApp> ServiceWorkerPaymentApp::AsWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| const SkBitmap* ServiceWorkerPaymentApp::icon_bitmap() const { |
| return needs_installation_ ? installable_web_app_info_->icon.get() |
| : stored_payment_app_info_->icon.get(); |
| } |
| |
| std::set<std::string> |
| ServiceWorkerPaymentApp::GetApplicationIdentifiersThatHideThisApp() const { |
| if (needs_installation_) { |
| return std::set<std::string>( |
| installable_web_app_info_->preferred_app_ids.begin(), |
| installable_web_app_info_->preferred_app_ids.end()); |
| } |
| |
| std::set<std::string> result; |
| if (!stored_payment_app_info_->prefer_related_applications) |
| return result; |
| |
| for (const auto& related : stored_payment_app_info_->related_applications) { |
| result.insert(related.id); |
| } |
| |
| return result; |
| } |
| |
| bool ServiceWorkerPaymentApp::HandlesShippingAddress() const { |
| if (!spec_ || !spec_->request_shipping()) |
| return false; |
| |
| return needs_installation_ |
| ? installable_web_app_info_->supported_delegations.shipping_address |
| : stored_payment_app_info_->supported_delegations.shipping_address; |
| } |
| |
| bool ServiceWorkerPaymentApp::HandlesPayerName() const { |
| if (!spec_ || !spec_->request_payer_name()) |
| return false; |
| |
| return needs_installation_ |
| ? installable_web_app_info_->supported_delegations.payer_name |
| : stored_payment_app_info_->supported_delegations.payer_name; |
| } |
| |
| bool ServiceWorkerPaymentApp::HandlesPayerEmail() const { |
| if (!spec_ || !spec_->request_payer_email()) |
| return false; |
| |
| return needs_installation_ |
| ? installable_web_app_info_->supported_delegations.payer_email |
| : stored_payment_app_info_->supported_delegations.payer_email; |
| } |
| |
| bool ServiceWorkerPaymentApp::HandlesPayerPhone() const { |
| if (!spec_ || !spec_->request_payer_phone()) |
| return false; |
| |
| return needs_installation_ |
| ? installable_web_app_info_->supported_delegations.payer_phone |
| : stored_payment_app_info_->supported_delegations.payer_phone; |
| } |
| |
| void ServiceWorkerPaymentApp::OnPaymentAppIdentity(const url::Origin& origin, |
| int64_t registration_id) { |
| registration_id_ = registration_id; |
| if (payment_handler_host_) { |
| payment_handler_host_->set_sw_origin_for_logs(origin); |
| payment_handler_host_->set_registration_id_for_logs(registration_id_); |
| } |
| } |
| |
| ukm::SourceId ServiceWorkerPaymentApp::UkmSourceId() { |
| if (ukm_source_id_ == ukm::kInvalidSourceId) { |
| GURL sw_scope = needs_installation_ |
| ? GURL(installable_web_app_info_->sw_scope) |
| : stored_payment_app_info_->scope; |
| // At this point we know that the payment handler window is open for this |
| // app since this getter is called for the invoked app inside the |
| // PaymentRequest::OnPaymentHandlerOpenWindowCalled function. |
| ukm_source_id_ = |
| content::PaymentAppProviderUtil::GetSourceIdForPaymentAppFromScope( |
| sw_scope.DeprecatedGetOriginAsURL()); |
| } |
| return ukm_source_id_; |
| } |
| |
| void ServiceWorkerPaymentApp::SetPaymentHandlerHost( |
| base::WeakPtr<PaymentHandlerHost> payment_handler_host) { |
| payment_handler_host_ = payment_handler_host; |
| payment_handler_host_remote_ = payment_handler_host_->Bind(); |
| } |
| |
| bool ServiceWorkerPaymentApp::IsWaitingForPaymentDetailsUpdate() const { |
| return payment_handler_host_ && |
| payment_handler_host_->is_waiting_for_payment_details_update(); |
| } |
| |
| void ServiceWorkerPaymentApp::UpdateWith( |
| mojom::PaymentRequestDetailsUpdatePtr details_update) { |
| if (payment_handler_host_) |
| payment_handler_host_->UpdateWith(std::move(details_update)); |
| } |
| |
| void ServiceWorkerPaymentApp::OnPaymentDetailsNotUpdated() { |
| if (payment_handler_host_) |
| payment_handler_host_->OnPaymentDetailsNotUpdated(); |
| } |
| |
| void ServiceWorkerPaymentApp::AbortPaymentApp( |
| base::OnceCallback<void(bool)> abort_callback) { |
| auto* payment_app_provider = GetPaymentAppProvider(); |
| if (!spec_ || !payment_app_provider) |
| return; |
| |
| payment_app_provider->AbortPayment( |
| registration_id_, |
| stored_payment_app_info_ |
| ? url::Origin::Create(stored_payment_app_info_->scope) |
| : url::Origin::Create(GURL(installable_web_app_info_->sw_scope)), |
| *spec_->details().id, std::move(abort_callback)); |
| } |
| |
| content::PaymentAppProvider* ServiceWorkerPaymentApp::GetPaymentAppProvider() { |
| return (!web_contents_) |
| ? nullptr |
| : content::PaymentAppProvider::GetOrCreateForWebContents( |
| web_contents_.get()); |
| } |
| |
| } // namespace payments |