blob: c3ad7af3378dd2da4d962cb1ff718c5dd9f2fe76 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// 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_instrument.h"
#include <limits>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/payments/content/payment_event_response_util.h"
#include "components/payments/content/payment_request_converter.h"
#include "components/payments/core/payment_request_delegate.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/payment_app_provider.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.
ServiceWorkerPaymentInstrument::ServiceWorkerPaymentInstrument(
content::BrowserContext* browser_context,
const GURL& top_origin,
const GURL& frame_origin,
const PaymentRequestSpec* spec,
std::unique_ptr<content::StoredPaymentApp> stored_payment_app_info,
PaymentRequestDelegate* payment_request_delegate,
base::WeakPtr<IdentityObserver> identity_observer)
: PaymentInstrument(0, PaymentInstrument::Type::SERVICE_WORKER_APP),
browser_context_(browser_context),
top_origin_(top_origin),
frame_origin_(frame_origin),
spec_(spec),
stored_payment_app_info_(std::move(stored_payment_app_info)),
delegate_(nullptr),
payment_request_delegate_(payment_request_delegate),
identity_observer_(identity_observer),
can_make_payment_result_(false),
has_enrolled_instrument_result_(false),
needs_installation_(false) {
DCHECK(browser_context_);
DCHECK(top_origin_.is_valid());
DCHECK(frame_origin_.is_valid());
DCHECK(spec_);
DCHECK(identity_observer_);
if (stored_payment_app_info_->icon) {
icon_image_ =
gfx::ImageSkia::CreateFrom1xBitmap(*(stored_payment_app_info_->icon))
.DeepCopy();
} else {
// Create an empty icon image to avoid using invalid icon resource id.
icon_image_ = gfx::ImageSkia::CreateFrom1xBitmap(SkBitmap()).DeepCopy();
}
}
// Service worker payment app provides icon through bitmap, so set 0 as invalid
// resource Id.
ServiceWorkerPaymentInstrument::ServiceWorkerPaymentInstrument(
content::WebContents* web_contents,
const GURL& top_origin,
const GURL& frame_origin,
const PaymentRequestSpec* spec,
std::unique_ptr<WebAppInstallationInfo> installable_payment_app_info,
const std::string& enabled_method,
PaymentRequestDelegate* payment_request_delegate,
base::WeakPtr<IdentityObserver> identity_observer)
: PaymentInstrument(0, PaymentInstrument::Type::SERVICE_WORKER_APP),
top_origin_(top_origin),
frame_origin_(frame_origin),
spec_(spec),
delegate_(nullptr),
payment_request_delegate_(payment_request_delegate),
identity_observer_(identity_observer),
can_make_payment_result_(false),
has_enrolled_instrument_result_(false),
needs_installation_(true),
web_contents_(web_contents),
installable_web_app_info_(std::move(installable_payment_app_info)),
installable_enabled_method_(enabled_method) {
DCHECK(web_contents_);
DCHECK(top_origin_.is_valid());
DCHECK(frame_origin_.is_valid());
DCHECK(spec_);
DCHECK(identity_observer_);
if (installable_web_app_info_->icon) {
icon_image_ =
gfx::ImageSkia::CreateFrom1xBitmap(*(installable_web_app_info_->icon))
.DeepCopy();
} else {
// Create an empty icon image to avoid using invalid icon resource id.
icon_image_ = gfx::ImageSkia::CreateFrom1xBitmap(SkBitmap()).DeepCopy();
}
}
ServiceWorkerPaymentInstrument::~ServiceWorkerPaymentInstrument() {
if (delegate_ && !needs_installation_) {
// 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.
content::PaymentAppProvider::GetInstance()->AbortPayment(
browser_context_, stored_payment_app_info_->registration_id,
url::Origin::Create(stored_payment_app_info_->scope),
*spec_->details().id, base::DoNothing());
}
}
void ServiceWorkerPaymentInstrument::ValidateCanMakePayment(
ValidateCanMakePaymentCallback callback) {
// 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 (payment_request_delegate_->IsIncognito()) {
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 instrument 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;
}
content::PaymentAppProvider::GetInstance()->CanMakePayment(
browser_context_, stored_payment_app_info_->registration_id,
url::Origin::Create(stored_payment_app_info_->scope),
*spec_->details().id, std::move(event_data),
base::BindOnce(
&ServiceWorkerPaymentInstrument::OnCanMakePaymentEventResponded,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
mojom::CanMakePaymentEventDataPtr
ServiceWorkerPaymentInstrument::CreateCanMakePaymentEventData() {
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_methods;
supported_methods.insert(stored_payment_app_info_->enabled_methods.begin(),
stored_payment_app_info_->enabled_methods.end());
std::set<std::string> supported_url_methods =
base::STLSetIntersection<std::set<std::string>>(requested_url_methods,
supported_methods);
// Only fire CanMakePaymentEvent if this instrument 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 ServiceWorkerPaymentInstrument::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::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), this, can_make_payment_result_));
}
void ServiceWorkerPaymentInstrument::OnCanMakePaymentEventResponded(
ValidateCanMakePaymentCallback callback,
bool result) {
// |can_make_payment| is true as long as there is a matching payment handler.
can_make_payment_result_ = true;
has_enrolled_instrument_result_ = result;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), this, can_make_payment_result_));
}
void ServiceWorkerPaymentInstrument::InvokePaymentApp(Delegate* delegate) {
delegate_ = delegate;
if (needs_installation_) {
content::PaymentAppProvider::GetInstance()->InstallAndInvokePaymentApp(
web_contents_, CreatePaymentRequestEventData(),
installable_web_app_info_->name,
installable_web_app_info_->icon == nullptr
? SkBitmap()
: *(installable_web_app_info_->icon),
installable_web_app_info_->sw_js_url,
installable_web_app_info_->sw_scope,
installable_web_app_info_->sw_use_cache, installable_enabled_method_,
installable_web_app_info_->supported_delegations,
base::BindOnce(
&IdentityObserver::SetInvokedServiceWorkerIdentity,
identity_observer_,
url::Origin::Create(GURL(installable_web_app_info_->sw_scope))),
base::BindOnce(&ServiceWorkerPaymentInstrument::OnPaymentAppInvoked,
weak_ptr_factory_.GetWeakPtr()));
} else {
url::Origin sw_origin =
url::Origin::Create(stored_payment_app_info_->scope);
identity_observer_->SetInvokedServiceWorkerIdentity(
sw_origin, stored_payment_app_info_->registration_id);
content::PaymentAppProvider::GetInstance()->InvokePaymentApp(
browser_context_, stored_payment_app_info_->registration_id, sw_origin,
CreatePaymentRequestEventData(),
base::BindOnce(&ServiceWorkerPaymentInstrument::OnPaymentAppInvoked,
weak_ptr_factory_.GetWeakPtr()));
}
payment_request_delegate_->ShowProcessingSpinner();
}
void ServiceWorkerPaymentInstrument::OnPaymentAppWindowClosed() {
delegate_ = nullptr;
content::PaymentAppProvider::GetInstance()->OnClosingOpenedWindow(
browser_context_,
mojom::PaymentEventResponseType::PAYMENT_HANDLER_WINDOW_CLOSING);
}
mojom::PaymentRequestEventDataPtr
ServiceWorkerPaymentInstrument::CreatePaymentRequestEventData() {
mojom::PaymentRequestEventDataPtr event_data =
mojom::PaymentRequestEventData::New();
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();
std::unordered_set<std::string> supported_methods;
if (needs_installation_) {
supported_methods.insert(installable_enabled_method_);
} else {
supported_methods.insert(stored_payment_app_info_->enabled_methods.begin(),
stored_payment_app_info_->enabled_methods.end());
}
DCHECK(spec_->details().modifiers);
for (const auto& modifier : *spec_->details().modifiers) {
if (base::Contains(supported_methods,
modifier->method_data->supported_method)) {
event_data->modifiers.emplace_back(modifier.Clone());
}
}
for (const auto& data : spec_->method_data()) {
if (base::Contains(supported_methods, 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_);
return event_data;
}
void ServiceWorkerPaymentInstrument::OnPaymentAppInvoked(
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 ServiceWorkerPaymentInstrument::IsCompleteForPayment() const {
return true;
}
uint32_t ServiceWorkerPaymentInstrument::GetCompletenessScore() const {
// Return max value to ensure that SW instruments always score higher than
// autofill.
return std::numeric_limits<uint32_t>::max();
}
bool ServiceWorkerPaymentInstrument::CanPreselect() const {
// Do not preselect the payment instrument when the name and/or icon is
// missing.
return !GetLabel().empty() && !icon_image_.size().IsEmpty();
}
bool ServiceWorkerPaymentInstrument::IsExactlyMatchingMerchantRequest() const {
return true;
}
base::string16 ServiceWorkerPaymentInstrument::GetMissingInfoLabel() const {
NOTREACHED();
return base::string16();
}
bool ServiceWorkerPaymentInstrument::IsValidForCanMakePayment() const {
// This instrument 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 ServiceWorkerPaymentInstrument::RecordUse() {
NOTIMPLEMENTED();
}
base::string16 ServiceWorkerPaymentInstrument::GetLabel() const {
return base::UTF8ToUTF16(needs_installation_
? installable_web_app_info_->name
: stored_payment_app_info_->name);
}
base::string16 ServiceWorkerPaymentInstrument::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 ServiceWorkerPaymentInstrument::IsValidForModifier(
const std::string& method,
bool supported_networks_specified,
const std::set<std::string>& supported_networks,
bool supported_types_specified,
const std::set<autofill::CreditCard::CardType>& supported_types) 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);
if (!is_valid)
return false;
// Return true if 'basic-card' is not the only matched payment method. This
// assumes that there is no duplicated payment methods.
if (method != "basic-card")
return true;
// Checking the capabilities of this instrument against the modifier.
// Return true if both card networks and types are not specified in the
// modifier.
if (!supported_networks_specified && !supported_types_specified)
return true;
// Return false if no capabilities for this instrument.
if (stored_payment_app_info_->capabilities.empty())
return false;
uint32_t i = 0;
for (; i < stored_payment_app_info_->capabilities.size(); i++) {
if (supported_networks_specified) {
std::set<std::string> app_supported_networks;
for (const auto& network :
stored_payment_app_info_->capabilities[i].supported_card_networks) {
app_supported_networks.insert(GetBasicCardNetworkName(
static_cast<mojom::BasicCardNetwork>(network)));
}
if (base::STLSetIntersection<std::set<std::string>>(
app_supported_networks, supported_networks)
.empty()) {
continue;
}
}
if (supported_types_specified) {
std::set<autofill::CreditCard::CardType> app_supported_types;
for (const auto& type :
stored_payment_app_info_->capabilities[i].supported_card_types) {
app_supported_types.insert(
GetBasicCardType(static_cast<mojom::BasicCardType>(type)));
}
if (base::STLSetIntersection<std::set<autofill::CreditCard::CardType>>(
app_supported_types, supported_types)
.empty()) {
continue;
}
}
break;
}
// i >= stored_payment_app_info_->capabilities.size() indicates no matched
// capabilities.
return i < stored_payment_app_info_->capabilities.size();
}
void ServiceWorkerPaymentInstrument::IsValidForPaymentMethodIdentifier(
const std::string& payment_method_identifier,
bool* is_valid) const {
DCHECK(!needs_installation_);
*is_valid = base::Contains(stored_payment_app_info_->enabled_methods,
payment_method_identifier);
}
base::WeakPtr<PaymentInstrument> ServiceWorkerPaymentInstrument::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
gfx::ImageSkia ServiceWorkerPaymentInstrument::icon_image_skia() const {
return icon_image_;
}
bool ServiceWorkerPaymentInstrument::HandlesShippingAddress() const {
if (!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 ServiceWorkerPaymentInstrument::HandlesPayerName() const {
if (!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 ServiceWorkerPaymentInstrument::HandlesPayerEmail() const {
if (!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 ServiceWorkerPaymentInstrument::HandlesPayerPhone() const {
if (!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;
}
} // namespace payments