blob: cee4aaf1f718c63dc76bb739c353befa80c11220 [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 "modules/payments/PaymentInstruments.h"
#include <utility>
#include "base/location.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ScriptPromise.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "bindings/core/v8/V8BindingForCore.h"
#include "core/dom/DOMException.h"
#include "core/dom/Document.h"
#include "core/frame/Frame.h"
#include "core/frame/LocalFrame.h"
#include "core/inspector/ConsoleMessage.h"
#include "modules/payments/BasicCardHelper.h"
#include "modules/payments/PaymentInstrument.h"
#include "modules/payments/PaymentManager.h"
#include "modules/permissions/PermissionUtils.h"
#include "platform/wtf/Vector.h"
#include "public/platform/TaskType.h"
#include "public/platform/WebIconSizesParser.h"
#include "public/platform/modules/manifest/manifest.mojom-blink.h"
namespace blink {
namespace {
static const char kPaymentManagerUnavailable[] = "Payment manager unavailable";
bool rejectError(ScriptPromiseResolver* resolver,
payments::mojom::blink::PaymentHandlerStatus status) {
switch (status) {
case payments::mojom::blink::PaymentHandlerStatus::SUCCESS:
return false;
case payments::mojom::blink::PaymentHandlerStatus::NOT_IMPLEMENTED:
resolver->Reject(
DOMException::Create(kNotSupportedError, "Not implemented yet"));
return true;
case payments::mojom::blink::PaymentHandlerStatus::NOT_FOUND:
resolver->Resolve();
return true;
case payments::mojom::blink::PaymentHandlerStatus::NO_ACTIVE_WORKER:
resolver->Reject(
DOMException::Create(kInvalidStateError, "No active service worker"));
return true;
case payments::mojom::blink::PaymentHandlerStatus::STORAGE_OPERATION_FAILED:
resolver->Reject(DOMException::Create(kInvalidStateError,
"Storage operation is failed"));
return true;
case payments::mojom::blink::PaymentHandlerStatus::
FETCH_INSTRUMENT_ICON_FAILED: {
ScriptState::Scope scope(resolver->GetScriptState());
resolver->Reject(V8ThrowException::CreateTypeError(
resolver->GetScriptState()->GetIsolate(),
"Fetch or decode instrument icon failed"));
return true;
}
case payments::mojom::blink::PaymentHandlerStatus::
FETCH_PAYMENT_APP_INFO_FAILED:
// FETCH_PAYMENT_APP_INFO_FAILED indicates everything works well except
// fetching payment handler's name and/or icon from its web app manifest.
// The origin or name will be used to label this payment handler in
// UI in this case, so only show warnning message instead of reject the
// promise. The warning message was printed by
// payment_app_info_fetcher.cc.
return false;
}
NOTREACHED();
return false;
}
} // namespace
PaymentInstruments::PaymentInstruments(
const payments::mojom::blink::PaymentManagerPtr& manager)
: manager_(manager) {}
ScriptPromise PaymentInstruments::deleteInstrument(
ScriptState* script_state,
const String& instrument_key) {
if (!manager_.is_bound()) {
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(kInvalidStateError, kPaymentManagerUnavailable));
}
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
manager_->DeletePaymentInstrument(
instrument_key,
WTF::Bind(&PaymentInstruments::onDeletePaymentInstrument,
WrapPersistent(this), WrapPersistent(resolver)));
return promise;
}
ScriptPromise PaymentInstruments::get(ScriptState* script_state,
const String& instrument_key) {
if (!manager_.is_bound()) {
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(kInvalidStateError, kPaymentManagerUnavailable));
}
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
manager_->GetPaymentInstrument(
instrument_key,
WTF::Bind(&PaymentInstruments::onGetPaymentInstrument,
WrapPersistent(this), WrapPersistent(resolver)));
return promise;
}
ScriptPromise PaymentInstruments::keys(ScriptState* script_state) {
if (!manager_.is_bound()) {
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(kInvalidStateError, kPaymentManagerUnavailable));
}
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
manager_->KeysOfPaymentInstruments(
WTF::Bind(&PaymentInstruments::onKeysOfPaymentInstruments,
WrapPersistent(this), WrapPersistent(resolver)));
return promise;
}
ScriptPromise PaymentInstruments::has(ScriptState* script_state,
const String& instrument_key) {
if (!manager_.is_bound()) {
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(kInvalidStateError, kPaymentManagerUnavailable));
}
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
manager_->HasPaymentInstrument(
instrument_key,
WTF::Bind(&PaymentInstruments::onHasPaymentInstrument,
WrapPersistent(this), WrapPersistent(resolver)));
return promise;
}
ScriptPromise PaymentInstruments::set(ScriptState* script_state,
const String& instrument_key,
const PaymentInstrument& details,
ExceptionState& exception_state) {
if (!manager_.is_bound()) {
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(kInvalidStateError, kPaymentManagerUnavailable));
}
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ExecutionContext* context = ExecutionContext::From(script_state);
Document* doc = ToDocumentOrNull(context);
// Should move this permission check to browser process.
// Please see http://crbug.com/795929
GetPermissionService(script_state)
->RequestPermission(
CreatePermissionDescriptor(
mojom::blink::PermissionName::PAYMENT_HANDLER),
Frame::HasTransientUserActivation(doc ? doc->GetFrame() : nullptr),
WTF::Bind(&PaymentInstruments::OnRequestPermission,
WrapPersistent(this), WrapPersistent(resolver),
instrument_key, details));
return resolver->Promise();
}
ScriptPromise PaymentInstruments::clear(ScriptState* script_state) {
if (!manager_.is_bound()) {
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(kInvalidStateError, kPaymentManagerUnavailable));
}
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
manager_->ClearPaymentInstruments(
WTF::Bind(&PaymentInstruments::onClearPaymentInstruments,
WrapPersistent(this), WrapPersistent(resolver)));
return promise;
}
mojom::blink::PermissionService* PaymentInstruments::GetPermissionService(
ScriptState* script_state) {
if (!permission_service_) {
ConnectToPermissionService(ExecutionContext::From(script_state),
mojo::MakeRequest(&permission_service_));
}
return permission_service_.get();
}
void PaymentInstruments::OnRequestPermission(
ScriptPromiseResolver* resolver,
const String& instrument_key,
const PaymentInstrument& details,
mojom::blink::PermissionStatus status) {
DCHECK(resolver);
if (!resolver->GetExecutionContext() ||
resolver->GetExecutionContext()->IsContextDestroyed())
return;
ScriptState::Scope scope(resolver->GetScriptState());
if (status != mojom::blink::PermissionStatus::GRANTED) {
resolver->Reject(DOMException::Create(
kNotAllowedError, "Not allowed to install this payment handler"));
return;
}
payments::mojom::blink::PaymentInstrumentPtr instrument =
payments::mojom::blink::PaymentInstrument::New();
instrument->name = details.hasName() ? details.name() : WTF::g_empty_string;
if (details.hasIcons()) {
ExecutionContext* context =
ExecutionContext::From(resolver->GetScriptState());
for (const ImageObject image_object : details.icons()) {
KURL parsed_url = context->CompleteURL(image_object.src());
if (!parsed_url.IsValid() || !parsed_url.ProtocolIsInHTTPFamily()) {
resolver->Reject(V8ThrowException::CreateTypeError(
resolver->GetScriptState()->GetIsolate(),
"'" + image_object.src() + "' is not a valid URL."));
return;
}
mojom::blink::ManifestIconPtr icon = mojom::blink::ManifestIcon::New();
icon->src = parsed_url;
icon->type = image_object.type();
icon->purpose.push_back(blink::mojom::ManifestIcon_Purpose::ANY);
WebVector<WebSize> web_sizes =
WebIconSizesParser::ParseIconSizes(image_object.sizes());
for (const auto& web_size : web_sizes) {
icon->sizes.push_back(web_size);
}
instrument->icons.push_back(std::move(icon));
}
}
if (details.hasEnabledMethods()) {
instrument->enabled_methods = details.enabledMethods();
}
if (details.hasCapabilities()) {
v8::Local<v8::String> value;
if (!v8::JSON::Stringify(resolver->GetScriptState()->GetContext(),
details.capabilities().V8Value().As<v8::Object>())
.ToLocal(&value)) {
resolver->Reject(V8ThrowException::CreateTypeError(
resolver->GetScriptState()->GetIsolate(),
"Capabilities should be a JSON-serializable object"));
return;
}
instrument->stringified_capabilities = ToCoreString(value);
if (instrument->enabled_methods.Contains("basic-card")) {
ExceptionState exception_state(resolver->GetScriptState()->GetIsolate(),
ExceptionState::kSetterContext,
"PaymentInstruments", "set");
BasicCardHelper::ParseBasiccardData(
details.capabilities(), instrument->supported_networks,
instrument->supported_types, exception_state);
if (exception_state.HadException()) {
exception_state.Reject(resolver);
return;
}
}
} else {
instrument->stringified_capabilities = WTF::g_empty_string;
}
manager_->SetPaymentInstrument(
instrument_key, std::move(instrument),
WTF::Bind(&PaymentInstruments::onSetPaymentInstrument,
WrapPersistent(this), WrapPersistent(resolver)));
}
void PaymentInstruments::onDeletePaymentInstrument(
ScriptPromiseResolver* resolver,
payments::mojom::blink::PaymentHandlerStatus status) {
DCHECK(resolver);
resolver->Resolve(status ==
payments::mojom::blink::PaymentHandlerStatus::SUCCESS);
}
void PaymentInstruments::onGetPaymentInstrument(
ScriptPromiseResolver* resolver,
payments::mojom::blink::PaymentInstrumentPtr stored_instrument,
payments::mojom::blink::PaymentHandlerStatus status) {
DCHECK(resolver);
if (!resolver->GetExecutionContext() ||
resolver->GetExecutionContext()->IsContextDestroyed())
return;
ScriptState::Scope scope(resolver->GetScriptState());
if (rejectError(resolver, status))
return;
PaymentInstrument instrument;
instrument.setName(stored_instrument->name);
HeapVector<ImageObject> icons;
for (const auto& icon : stored_instrument->icons) {
ImageObject image_object;
image_object.setSrc(icon->src.GetString());
image_object.setType(icon->type);
String sizes = WTF::g_empty_string;
for (const auto& size : icon->sizes) {
sizes = sizes + String::Format("%dx%d ", size.width, size.height);
}
image_object.setSizes(sizes.StripWhiteSpace());
icons.emplace_back(image_object);
}
instrument.setIcons(icons);
Vector<String> enabled_methods;
for (const auto& method : stored_instrument->enabled_methods) {
enabled_methods.push_back(method);
}
instrument.setEnabledMethods(enabled_methods);
if (!stored_instrument->stringified_capabilities.IsEmpty()) {
ExceptionState exception_state(resolver->GetScriptState()->GetIsolate(),
ExceptionState::kGetterContext,
"PaymentInstruments", "get");
instrument.setCapabilities(
ScriptValue(resolver->GetScriptState(),
FromJSONString(resolver->GetScriptState()->GetIsolate(),
resolver->GetScriptState()->GetContext(),
stored_instrument->stringified_capabilities,
exception_state)));
if (exception_state.HadException()) {
exception_state.Reject(resolver);
return;
}
}
resolver->Resolve(instrument);
}
void PaymentInstruments::onKeysOfPaymentInstruments(
ScriptPromiseResolver* resolver,
const Vector<String>& keys,
payments::mojom::blink::PaymentHandlerStatus status) {
DCHECK(resolver);
if (rejectError(resolver, status))
return;
resolver->Resolve(keys);
}
void PaymentInstruments::onHasPaymentInstrument(
ScriptPromiseResolver* resolver,
payments::mojom::blink::PaymentHandlerStatus status) {
DCHECK(resolver);
resolver->Resolve(status ==
payments::mojom::blink::PaymentHandlerStatus::SUCCESS);
}
void PaymentInstruments::onSetPaymentInstrument(
ScriptPromiseResolver* resolver,
payments::mojom::blink::PaymentHandlerStatus status) {
DCHECK(resolver);
if (rejectError(resolver, status))
return;
resolver->Resolve();
}
void PaymentInstruments::onClearPaymentInstruments(
ScriptPromiseResolver* resolver,
payments::mojom::blink::PaymentHandlerStatus status) {
DCHECK(resolver);
if (rejectError(resolver, status))
return;
resolver->Resolve();
}
} // namespace blink