| Index: third_party/WebKit/Source/modules/payments/PaymentRequest.cpp
|
| diff --git a/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp b/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp
|
| index fe3ab4642012884480a3a1fe8edd66b0e6bdd892..39a517938eb402d54454bb7f544e1b74380b7bea 100644
|
| --- a/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp
|
| +++ b/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp
|
| @@ -6,13 +6,150 @@
|
|
|
| #include "bindings/core/v8/ExceptionState.h"
|
| #include "bindings/core/v8/JSONValuesForV8.h"
|
| +#include "bindings/core/v8/ScriptPromiseResolver.h"
|
| #include "bindings/core/v8/ScriptState.h"
|
| +#include "core/EventTypeNames.h"
|
| #include "core/dom/DOMException.h"
|
| #include "core/dom/ExceptionCode.h"
|
| +#include "core/events/Event.h"
|
| +#include "core/events/EventQueue.h"
|
| #include "modules/EventTargetModulesNames.h"
|
| +#include "modules/payments/PaymentItem.h"
|
| +#include "modules/payments/PaymentResponse.h"
|
| #include "modules/payments/ShippingAddress.h"
|
| +#include "modules/payments/ShippingOption.h"
|
| +#include "mojo/public/cpp/bindings/interface_request.h"
|
| +#include "public/platform/Platform.h"
|
| +#include <utility>
|
|
|
| namespace blink {
|
| +namespace {
|
| +
|
| +template <typename OutputPtr, typename OutputType, typename Input>
|
| +OutputPtr toMojoItem(const Input& input)
|
| +{
|
| + OutputPtr output = OutputType::New();
|
| + output->id = input.id();
|
| + output->label = input.label();
|
| + output->amount = mojom::CurrencyAmount::New();
|
| + if (input.hasAmount()) {
|
| + output->amount->currency_code = input.amount().currencyCode();
|
| + output->amount->value = input.amount().value();
|
| + }
|
| + return output;
|
| +}
|
| +
|
| +mojom::PaymentDetailsPtr toMojoPaymentDetails(const PaymentDetails& input)
|
| +{
|
| + mojom::PaymentDetailsPtr output = mojom::PaymentDetails::New();
|
| + output->items = mojo::WTFArray<mojom::PaymentItemPtr>::New(input.hasItems() ? input.items().size() : 0);
|
| + if (input.hasItems()) {
|
| + for (size_t i = 0; i < input.items().size(); ++i)
|
| + output->items[i] = toMojoItem<mojom::PaymentItemPtr, mojom::PaymentItem, PaymentItem>(input.items()[i]);
|
| + }
|
| + output->shipping_options = mojo::WTFArray<mojom::ShippingOptionPtr>::New(input.hasShippingOptions() ? input.shippingOptions().size() : 0);
|
| + if (input.hasShippingOptions()) {
|
| + for (size_t i = 0; i < input.shippingOptions().size(); ++i)
|
| + output->shipping_options[i] = toMojoItem<mojom::ShippingOptionPtr, mojom::ShippingOption, ShippingOption>(input.shippingOptions()[i]);
|
| + }
|
| + return output;
|
| +}
|
| +
|
| +mojom::PaymentOptionsPtr toMojoPaymentOptions(const PaymentOptions& input)
|
| +{
|
| + mojom::PaymentOptionsPtr output = mojom::PaymentOptions::New();
|
| + output->request_shipping = input.requestShipping();
|
| + return output;
|
| +}
|
| +
|
| +bool isValidCurrencyCode(const String& code, ExceptionState& exceptionState)
|
| +{
|
| + if (code.length() != 3) {
|
| + exceptionState.throwTypeError("'" + code + "' is not a valid ISO 4217 currency code, should be 3 letters");
|
| + return false;
|
| + }
|
| +
|
| + for (size_t i = 0; i < code.length(); ++i) {
|
| + if (code[i] < 'A' || code[i] > 'Z') {
|
| + exceptionState.throwTypeError("'" + code + "' is not a valid ISO 4217 currency code, should be upper case letters [A-Z]");
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool isValidAmount(const String& amount, ExceptionState& exceptionState)
|
| +{
|
| + if (amount.isEmpty()) {
|
| + exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 200222 CurrencyAnd30Amount currency amount, should be nonempty");
|
| + return false;
|
| + }
|
| +
|
| + if (amount.length() > 32) {
|
| + exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 200222 CurrencyAnd30Amount currency amount, should be at most 32 characters");
|
| + return false;
|
| + }
|
| +
|
| + int numberOfPeriods = 0;
|
| + int numberOfDigits = 0;
|
| + int numberOfFractionDigits = 0;
|
| + for (size_t i = 0; i < amount.length(); ++i) {
|
| + if (i == 0 && amount[i] == '-')
|
| + continue;
|
| +
|
| + bool isPeriod = amount[i] == '.';
|
| + bool isDigit = amount[i] >= '0' && amount[i] <= '9';
|
| + if (!isPeriod && !isDigit) {
|
| + exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 200222 CurrencyAnd30Amount currency amount, should contain only '-', '.', and [0-9]");
|
| + return false;
|
| + }
|
| +
|
| + if (isPeriod)
|
| + ++numberOfPeriods;
|
| +
|
| + if (isDigit) {
|
| + ++numberOfDigits;
|
| + if (numberOfPeriods > 0)
|
| + ++numberOfFractionDigits;
|
| + }
|
| + }
|
| +
|
| + if (numberOfPeriods > 1) {
|
| + exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 200222 CurrencyAnd30Amount currency amount, should contain only one '.'");
|
| + return false;
|
| + }
|
| +
|
| + if (numberOfDigits > 30) {
|
| + exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 200222 CurrencyAnd30Amount currency amount, should contain at most 30 digits");
|
| + return false;
|
| + }
|
| +
|
| + if (numberOfFractionDigits > 10) {
|
| + exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 200222 CurrencyAnd30Amount currency amount, should contain at most 10 fraction digits");
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +template <typename T>
|
| +bool areValidCurrencyAndAmounts(HeapVector<T> items, ExceptionState& exceptionState)
|
| +{
|
| + for (const auto& item : items) {
|
| + if (item.hasAmount()) {
|
| + if (item.amount().hasCurrencyCode() && !isValidCurrencyCode(item.amount().currencyCode(), exceptionState))
|
| + return false;
|
| +
|
| + if (item.amount().hasValue() && isValidAmount(item.amount().value(), exceptionState))
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +} // namespace
|
|
|
| // static
|
| PaymentRequest* PaymentRequest::create(ScriptState* scriptState, const Vector<String>& supportedMethods, const PaymentDetails& details, ExceptionState& exceptionState)
|
| @@ -38,11 +175,29 @@ PaymentRequest::~PaymentRequest()
|
|
|
| ScriptPromise PaymentRequest::show(ScriptState* scriptState)
|
| {
|
| - return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(NotSupportedError, "Not implemented."));
|
| + if (m_showResolver)
|
| + return m_showResolver->promise().rejectWithDOMException(scriptState, DOMException::create(InvalidStateError, "Already called show() once"));
|
| +
|
| + ASSERT(!m_paymentProvider.is_bound());
|
| + blink::Platform::current()->connectToRemoteService(mojo::GetProxy(&m_paymentProvider));
|
| + if (!m_paymentProvider)
|
| + return m_showResolver->promise().rejectWithDOMException(scriptState, DOMException::create(SyntaxError, "Not implemented on this platform"));
|
| +
|
| + m_paymentProvider->SetClient(m_clientBinding.CreateInterfacePtrAndBind());
|
| + m_paymentProvider->Show(std::move(m_supportedMethods), toMojoPaymentDetails(m_details), toMojoPaymentOptions(m_options), m_stringifiedData.isNull() ? "" : m_stringifiedData);
|
| +
|
| + m_showResolver = ScriptPromiseResolver::create(scriptState);
|
| + return m_showResolver->promise();
|
| }
|
|
|
| -void PaymentRequest::abort()
|
| +void PaymentRequest::abort(ExceptionState& exceptionState)
|
| {
|
| + if (!m_showResolver) {
|
| + exceptionState.throwDOMException(InvalidStateError, "Never called show(), so nothing to abort");
|
| + return;
|
| + }
|
| +
|
| + m_paymentProvider->Abort();
|
| }
|
|
|
| const AtomicString& PaymentRequest::interfaceName() const
|
| @@ -55,11 +210,24 @@ ExecutionContext* PaymentRequest::getExecutionContext() const
|
| return m_scriptState->getExecutionContext();
|
| }
|
|
|
| +ScriptPromise PaymentRequest::complete(ScriptState* scriptState, bool success)
|
| +{
|
| + if (m_completeResolver)
|
| + return m_completeResolver->promise().rejectWithDOMException(scriptState, DOMException::create(InvalidStateError, "Already called complete() once"));
|
| +
|
| + m_completeResolver = ScriptPromiseResolver::create(scriptState);
|
| + m_paymentProvider->Complete(success);
|
| +
|
| + return m_completeResolver->promise();
|
| +}
|
| +
|
| DEFINE_TRACE(PaymentRequest)
|
| {
|
| visitor->trace(m_details);
|
| visitor->trace(m_options);
|
| visitor->trace(m_shippingAddress);
|
| + visitor->trace(m_showResolver);
|
| + visitor->trace(m_completeResolver);
|
| RefCountedGarbageCollectedEventTargetWithInlineData<PaymentRequest>::trace(visitor);
|
| }
|
|
|
| @@ -68,12 +236,109 @@ PaymentRequest::PaymentRequest(ScriptState* scriptState, const Vector<String>& s
|
| , m_supportedMethods(supportedMethods)
|
| , m_details(details)
|
| , m_options(options)
|
| + , m_clientBinding(this)
|
| {
|
| + // TODO(rouslan): Also check for a top-level browsing context.
|
| + // https://github.com/w3c/browser-payment-api/issues/2
|
| + if (!scriptState->getExecutionContext()->isSecureContext()) {
|
| + exceptionState.throwSecurityError("Must be in a secure context");
|
| + return;
|
| + }
|
| +
|
| + if (supportedMethods.isEmpty()) {
|
| + exceptionState.throwTypeError("No payment methods identifiers provided");
|
| + return;
|
| + }
|
| +
|
| + if (details.hasItems() && !areValidCurrencyAndAmounts(details.items(), exceptionState))
|
| + return;
|
| +
|
| + if (details.hasShippingOptions() && !areValidCurrencyAndAmounts(details.shippingOptions(), exceptionState))
|
| + return;
|
| +
|
| if (!data.isEmpty()) {
|
| RefPtr<JSONValue> value = toJSONValue(data.context(), data.v8Value());
|
| - if (value && value->getType() == JSONValue::TypeObject)
|
| - m_stringifiedData = JSONObject::cast(value)->toJSONString();
|
| + if (value && !value->isNull()) {
|
| + if (value->getType() != JSONValue::TypeObject) {
|
| + exceptionState.throwTypeError("Payment method specific data should be a JSON-serializable object");
|
| + return;
|
| + }
|
| +
|
| + RefPtr<JSONObject> jsonData = JSONObject::cast(value);
|
| + for (const auto& paymentMethodSpecificKeyValue : *jsonData) {
|
| + if (!supportedMethods.contains(paymentMethodSpecificKeyValue.key)) {
|
| + exceptionState.throwTypeError("'" + paymentMethodSpecificKeyValue.key + "' does not match one of the payment method identifiers");
|
| + return;
|
| + }
|
| + if (!paymentMethodSpecificKeyValue.value || paymentMethodSpecificKeyValue.value->isNull() || paymentMethodSpecificKeyValue.value->getType() != JSONValue::TypeObject) {
|
| + exceptionState.throwTypeError("Data for '" + paymentMethodSpecificKeyValue.key + "' should be a JSON-serializable object");
|
| + return;
|
| + }
|
| + }
|
| +
|
| + m_stringifiedData = jsonData->toJSONString();
|
| + }
|
| }
|
| +
|
| + if (details.hasShippingOptions() && details.shippingOptions().size() == 1 && details.shippingOptions()[0].hasId())
|
| + m_shippingOption = details.shippingOptions()[0].id();
|
| +}
|
| +
|
| +void PaymentRequest::OnShippingAddressChange(mojom::ShippingAddressPtr address)
|
| +{
|
| + ASSERT(m_showResolver);
|
| + ASSERT(!m_completeResolver);
|
| + // TODO(rouslan): Should the merchant website be notified of invalid shipping address
|
| + // from the browser or the payment app?
|
| + m_shippingAddress = new ShippingAddress(std::move(address));
|
| + if (!m_shippingAddress->isValidRegionCode()) {
|
| + m_showResolver->reject(DOMException::create(SyntaxError, "Invalid ISO 3166 country code"));
|
| + return;
|
| + }
|
| + if (!m_shippingAddress->isValidLanguageCode()) {
|
| + m_showResolver->reject(DOMException::create(SyntaxError, "Invalid ISO 639 language code"));
|
| + return;
|
| + }
|
| + if (!m_shippingAddress->isValidScriptCode()) {
|
| + m_showResolver->reject(DOMException::create(SyntaxError, "Invalid ISO 1524 script code"));
|
| + return;
|
| + }
|
| + RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::shippingaddresschange);
|
| + event->setTarget(this);
|
| + getExecutionContext()->getEventQueue()->enqueueEvent(event);
|
| +}
|
| +
|
| +void PaymentRequest::OnShippingOptionChange(const String& shippingOptionId)
|
| +{
|
| + ASSERT(m_showResolver);
|
| + ASSERT(!m_completeResolver);
|
| + m_shippingOption = shippingOptionId;
|
| + RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::shippingoptionchange);
|
| + event->setTarget(this);
|
| + getExecutionContext()->getEventQueue()->enqueueEvent(event);
|
| +}
|
| +
|
| +void PaymentRequest::OnPaymentResponse(mojom::PaymentResponsePtr response)
|
| +{
|
| + ASSERT(m_showResolver);
|
| + ASSERT(!m_completeResolver);
|
| + m_showResolver->resolve(new PaymentResponse(std::move(response), this));
|
| +}
|
| +
|
| +void PaymentRequest::OnError()
|
| +{
|
| + if (m_completeResolver) {
|
| + m_completeResolver->reject(DOMException::create(SyntaxError, "Request cancelled"));
|
| + return;
|
| + }
|
| + ASSERT(m_showResolver);
|
| + m_showResolver->reject(DOMException::create(SyntaxError, "Request cancelled"));
|
| +}
|
| +
|
| +void PaymentRequest::OnComplete()
|
| +{
|
| + ASSERT(m_completeResolver);
|
| + m_completeResolver->resolve();
|
| }
|
|
|
| } // namespace blink
|
|
|