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..011a4101f62a2c7eaaa867cd8ade62fb69225369 100644 |
--- a/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp |
+++ b/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp |
@@ -6,13 +6,135 @@ |
#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/PaymentsValidators.h" |
#include "modules/payments/ShippingAddress.h" |
+#include "modules/payments/ShippingOption.h" |
+#include "mojo/public/cpp/bindings/interface_request.h" |
+#include "mojo/public/cpp/bindings/wtf_array.h" |
+#include "platform/threading/BindForMojo.h" |
+#include "public/platform/Platform.h" |
+#include <utility> |
+ |
+namespace mojo { |
+ |
+template <> |
+struct TypeConverter<mojom::wtf::CurrencyAmountPtr, blink::CurrencyAmount> { |
+ static mojom::wtf::CurrencyAmountPtr Convert(const blink::CurrencyAmount& input) |
+ { |
+ mojom::wtf::CurrencyAmountPtr output = mojom::wtf::CurrencyAmount::New(); |
+ output->currency_code = input.currencyCode(); |
+ output->value = input.value(); |
+ return output; |
+ } |
+}; |
+ |
+template <> |
+struct TypeConverter<mojom::wtf::PaymentItemPtr, blink::PaymentItem> { |
+ static mojom::wtf::PaymentItemPtr Convert(const blink::PaymentItem& input) |
+ { |
+ mojom::wtf::PaymentItemPtr output = mojom::wtf::PaymentItem::New(); |
+ output->id = input.id(); |
+ output->label = input.label(); |
+ output->amount = mojom::wtf::CurrencyAmount::From(input.amount()); |
+ return output; |
+ } |
+}; |
+ |
+template <> |
+struct TypeConverter<mojom::wtf::ShippingOptionPtr, blink::ShippingOption> { |
+ static mojom::wtf::ShippingOptionPtr Convert(const blink::ShippingOption& input) |
+ { |
+ mojom::wtf::ShippingOptionPtr output = mojom::wtf::ShippingOption::New(); |
+ output->id = input.id(); |
+ output->label = input.label(); |
+ output->amount = mojom::wtf::CurrencyAmount::From(input.amount()); |
+ return output; |
+ } |
+}; |
+ |
+template <> |
+struct TypeConverter<mojom::wtf::PaymentDetailsPtr, blink::PaymentDetails> { |
+ static mojom::wtf::PaymentDetailsPtr Convert(const blink::PaymentDetails& input) |
+ { |
+ mojom::wtf::PaymentDetailsPtr output = mojom::wtf::PaymentDetails::New(); |
+ output->items = mojo::WTFArray<mojom::wtf::PaymentItemPtr>::From(static_cast<const WTF::Vector<blink::PaymentItem, 0, blink::HeapAllocator>>(input.items())); |
+ if (input.hasShippingOptions()) |
+ output->shipping_options = mojo::WTFArray<mojom::wtf::ShippingOptionPtr>::From(static_cast<const WTF::Vector<blink::ShippingOption, 0, blink::HeapAllocator>>(input.shippingOptions())); |
+ else |
+ output->shipping_options = mojo::WTFArray<mojom::wtf::ShippingOptionPtr>::New(0); |
+ return output; |
+ } |
+}; |
+ |
+template <> |
+struct TypeConverter<mojom::wtf::PaymentOptionsPtr, blink::PaymentOptions> { |
+ static mojom::wtf::PaymentOptionsPtr Convert(const blink::PaymentOptions& input) |
+ { |
+ mojom::wtf::PaymentOptionsPtr output = mojom::wtf::PaymentOptions::New(); |
+ output->request_shipping = input.requestShipping(); |
+ return output; |
+ } |
+}; |
+ |
+} // namespace mojo |
namespace blink { |
+namespace { |
+ |
+// Validates ShippingOption and PaymentItem dictionaries, which happen to have identical fields. |
+template <typename T> |
+void validateShippingOptionsOrPaymentItems(HeapVector<T> items, ExceptionState& exceptionState) |
+{ |
+ String errorMessage; |
+ for (const auto& item : items) { |
+ if (!item.hasId()) { |
+ exceptionState.throwTypeError("Item id required"); |
+ return; |
+ } |
+ |
+ if (!item.hasLabel()) { |
+ exceptionState.throwTypeError("Item label required"); |
+ return; |
+ } |
+ |
+ if (!item.hasAmount()) { |
+ exceptionState.throwTypeError("Currency amount required"); |
+ return; |
+ } |
+ |
+ if (!item.amount().hasCurrencyCode()) { |
+ exceptionState.throwTypeError("Currency code required"); |
+ return; |
+ } |
+ |
+ if (!item.amount().hasValue()) { |
+ exceptionState.throwTypeError("Currency value required"); |
+ return; |
+ } |
+ |
+ if (!isValidCurrencyCodeFormat(item.amount().currencyCode(), &errorMessage)) { |
+ exceptionState.throwTypeError(errorMessage); |
+ return; |
+ } |
+ |
+ if (!isValidAmountFormat(item.amount().value(), &errorMessage)) { |
+ exceptionState.throwTypeError(errorMessage); |
+ return; |
+ } |
+ } |
+} |
+ |
+} // namespace |
// static |
PaymentRequest* PaymentRequest::create(ScriptState* scriptState, const Vector<String>& supportedMethods, const PaymentDetails& details, ExceptionState& exceptionState) |
@@ -38,11 +160,27 @@ PaymentRequest::~PaymentRequest() |
ScriptPromise PaymentRequest::show(ScriptState* scriptState) |
{ |
- return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(NotSupportedError, "Not implemented.")); |
+ if (m_showResolver) |
+ return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(InvalidStateError, "Already called show() once")); |
+ |
+ DCHECK(!m_paymentProvider.is_bound()); |
+ blink::Platform::current()->connectToRemoteService(mojo::GetProxy(&m_paymentProvider)); |
+ m_paymentProvider.set_connection_error_handler(sameThreadBindForMojo(&PaymentRequest::OnError, this)); |
+ m_paymentProvider->SetClient(m_clientBinding.CreateInterfacePtrAndBind()); |
+ m_paymentProvider->Show(std::move(m_supportedMethods), mojom::wtf::PaymentDetails::From(m_details), mojom::wtf::PaymentOptions::From(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 +193,24 @@ ExecutionContext* PaymentRequest::getExecutionContext() const |
return m_scriptState->getExecutionContext(); |
} |
+ScriptPromise PaymentRequest::complete(ScriptState* scriptState, bool success) |
+{ |
+ if (m_completeResolver) |
+ return ScriptPromise::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 +219,132 @@ 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("Must specify at least one payment method identifier"); |
+ return; |
+ } |
+ |
+ if (!details.hasItems()) { |
+ exceptionState.throwTypeError("Must specify items"); |
+ return; |
+ } |
+ |
+ if (details.items().isEmpty()) { |
+ exceptionState.throwTypeError("Must specify at least one item"); |
+ return; |
+ } |
+ |
+ validateShippingOptionsOrPaymentItems(details.items(), exceptionState); |
+ if (exceptionState.hadException()) |
+ return; |
+ |
+ if (details.hasShippingOptions()) { |
+ validateShippingOptionsOrPaymentItems(details.shippingOptions(), exceptionState); |
+ if (exceptionState.hadException()) |
+ 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) { |
+ exceptionState.throwTypeError("Unable to parse payment method specific data"); |
+ return; |
+ } |
+ if (!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 + "' should match one of the payment method identifiers"); |
+ return; |
+ } |
+ if (paymentMethodSpecificKeyValue.value->getType() != JSONValue::TypeObject) { |
+ exceptionState.throwTypeError("Data for '" + paymentMethodSpecificKeyValue.key + "' should be a JSON-serializable object"); |
+ return; |
+ } |
+ } |
+ |
+ m_stringifiedData = jsonData->toJSONString(); |
+ } |
} |
+ |
+ // Set the currently selected option if only one option was passed. |
+ if (details.hasShippingOptions() && details.shippingOptions().size() == 1) |
+ m_shippingOption = details.shippingOptions().begin()->id(); |
+} |
+ |
+void PaymentRequest::OnShippingAddressChange(mojom::wtf::ShippingAddressPtr address) |
+{ |
+ DCHECK(m_showResolver); |
+ DCHECK(!m_completeResolver); |
+ |
+ // TODO(rouslan): Should the merchant website be notified of invalid shipping address |
+ // from the browser or the payment app? |
+ String errorMessage; |
+ if (!isValidRegionCodeFormat(address->region_code, &errorMessage) |
+ || !isValidLanguageCodeFormat(address->language_code, &errorMessage) |
+ || !isValidScriptCodeFormat(address->script_code, &errorMessage)) { |
+ m_showResolver->reject(DOMException::create(SyntaxError, errorMessage)); |
+ return; |
+ } |
+ |
+ if (address->language_code.isEmpty() && !address->script_code.isEmpty()) { |
+ m_showResolver->reject(DOMException::create(SyntaxError, "If language code is empty, then script code should also be empty")); |
+ return; |
+ } |
+ |
+ m_shippingAddress = new ShippingAddress(std::move(address)); |
+ RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::shippingaddresschange); |
+ event->setTarget(this); |
+ getExecutionContext()->getEventQueue()->enqueueEvent(event); |
+} |
+ |
+void PaymentRequest::OnShippingOptionChange(const String& shippingOptionId) |
+{ |
+ DCHECK(m_showResolver); |
+ DCHECK(!m_completeResolver); |
+ m_shippingOption = shippingOptionId; |
+ RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::shippingoptionchange); |
+ event->setTarget(this); |
+ getExecutionContext()->getEventQueue()->enqueueEvent(event); |
+} |
+ |
+void PaymentRequest::OnPaymentResponse(mojom::wtf::PaymentResponsePtr response) |
+{ |
+ DCHECK(m_showResolver); |
+ DCHECK(!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")); |
+ else if (m_showResolver) |
+ m_showResolver->reject(DOMException::create(SyntaxError, "Request cancelled")); |
+ m_clientBinding.Close(); |
+ m_paymentProvider.reset(); |
+} |
+ |
+void PaymentRequest::OnComplete() |
+{ |
+ DCHECK(m_completeResolver); |
+ m_completeResolver->resolve(); |
+ m_clientBinding.Close(); |
+ m_paymentProvider.reset(); |
} |
} // namespace blink |