Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "modules/payments/PaymentRequest.h" | 5 #include "modules/payments/PaymentRequest.h" |
| 6 | 6 |
| 7 #include "bindings/core/v8/ExceptionState.h" | 7 #include "bindings/core/v8/ExceptionState.h" |
| 8 #include "bindings/core/v8/JSONValuesForV8.h" | 8 #include "bindings/core/v8/JSONValuesForV8.h" |
| 9 #include "bindings/core/v8/ScriptPromiseResolver.h" | |
| 9 #include "bindings/core/v8/ScriptState.h" | 10 #include "bindings/core/v8/ScriptState.h" |
| 11 #include "core/EventTypeNames.h" | |
| 10 #include "core/dom/DOMException.h" | 12 #include "core/dom/DOMException.h" |
| 11 #include "core/dom/ExceptionCode.h" | 13 #include "core/dom/ExceptionCode.h" |
| 14 #include "core/events/Event.h" | |
| 15 #include "core/events/EventQueue.h" | |
| 12 #include "modules/EventTargetModulesNames.h" | 16 #include "modules/EventTargetModulesNames.h" |
| 17 #include "modules/payments/PaymentItem.h" | |
| 18 #include "modules/payments/PaymentResponse.h" | |
| 13 #include "modules/payments/ShippingAddress.h" | 19 #include "modules/payments/ShippingAddress.h" |
| 20 #include "modules/payments/ShippingOption.h" | |
| 21 #include "mojo/public/cpp/bindings/interface_request.h" | |
| 22 #include "public/platform/Platform.h" | |
| 23 #include <utility> | |
| 14 | 24 |
| 15 namespace blink { | 25 namespace blink { |
| 26 namespace { | |
| 27 | |
| 28 bool isValidCurrencyCode(const String& code, ExceptionState& exceptionState) | |
|
esprehn
2016/03/18 00:20:50
Is this all covered by tests? Seems like we should
please use gerrit instead
2016/03/23 00:14:51
Covered in PaymentRequestDetailsTest.
| |
| 29 { | |
| 30 if (code.length() != 3) { | |
| 31 exceptionState.throwTypeError("'" + code + "' is not a valid ISO 4217 cu rrency code, should be 3 letters"); | |
|
haraken
2016/03/18 09:19:44
Just to confirm: Maybe should this be a DOMExcepti
please use gerrit instead
2016/03/23 00:14:51
Spec does not touch on this yet. It's using a lot
| |
| 32 return false; | |
| 33 } | |
| 34 | |
| 35 for (size_t i = 0; i < code.length(); ++i) { | |
| 36 if (code[i] < 'A' || code[i] > 'Z') { | |
| 37 exceptionState.throwTypeError("'" + code + "' is not a valid ISO 421 7 currency code, should be upper case letters [A-Z]"); | |
| 38 return false; | |
| 39 } | |
| 40 } | |
| 41 | |
| 42 return true; | |
| 43 } | |
| 44 | |
| 45 bool isValidAmount(const String& amount, ExceptionState& exceptionState) | |
|
Marijn Kruisselbrink
2016/03/18 01:39:48
This doesn't seem to exactly match the (informativ
groby-ooo-7-16
2016/03/21 19:36:26
Hm. If we have a regexp, why not use ScriptRegex a
please use gerrit instead
2016/03/23 00:14:51
Using ScriptRegexp with the exact regular expressi
| |
| 46 { | |
| 47 if (amount.isEmpty()) { | |
| 48 exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 20022 2 CurrencyAnd30Amount currency amount, should be nonempty"); | |
|
haraken
2016/03/18 09:19:44
Ditto.
please use gerrit instead
2016/03/23 00:14:51
Acknowledged.
| |
| 49 return false; | |
| 50 } | |
| 51 | |
| 52 if (amount.length() > 32) { | |
| 53 exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 20022 2 CurrencyAnd30Amount currency amount, should be at most 32 characters"); | |
|
haraken
2016/03/18 09:19:44
Ditto.
please use gerrit instead
2016/03/23 00:14:51
Acknowledged.
| |
| 54 return false; | |
| 55 } | |
| 56 | |
| 57 int numberOfPeriods = 0; | |
| 58 int numberOfDigits = 0; | |
| 59 int numberOfFractionDigits = 0; | |
| 60 for (size_t i = 0; i < amount.length(); ++i) { | |
| 61 if (i == 0 && amount[i] == '-') | |
| 62 continue; | |
| 63 | |
| 64 bool isPeriod = amount[i] == '.'; | |
| 65 bool isDigit = amount[i] >= '0' && amount[i] <= '9'; | |
|
groby-ooo-7-16
2016/03/18 00:47:25
isdigit? (Well, OK, isASCIIDigit - I forgot we're
please use gerrit instead
2016/03/23 00:14:51
Using the regex from the spec instead.
| |
| 66 if (!isPeriod && !isDigit) { | |
| 67 exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 2 00222 CurrencyAnd30Amount currency amount, should contain only '-', '.', and [0- 9]"); | |
|
groby-ooo-7-16
2016/03/18 00:47:25
CurrencyAnd30Amount? (Here and elsewhere)
please use gerrit instead
2016/03/23 00:14:51
That's a newer replacement for ISO 4217 that our W
| |
| 68 return false; | |
| 69 } | |
| 70 | |
| 71 if (isPeriod) | |
| 72 ++numberOfPeriods; | |
| 73 | |
| 74 if (isDigit) { | |
| 75 ++numberOfDigits; | |
| 76 if (numberOfPeriods > 0) | |
| 77 ++numberOfFractionDigits; | |
| 78 } | |
| 79 } | |
| 80 | |
| 81 if (numberOfPeriods > 1) { | |
| 82 exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 20022 2 CurrencyAnd30Amount currency amount, should contain only one '.'"); | |
| 83 return false; | |
| 84 } | |
| 85 | |
| 86 if (numberOfDigits > 30) { | |
| 87 exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 20022 2 CurrencyAnd30Amount currency amount, should contain at most 30 digits"); | |
| 88 return false; | |
| 89 } | |
| 90 | |
| 91 if (numberOfFractionDigits > 10) { | |
| 92 exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 20022 2 CurrencyAnd30Amount currency amount, should contain at most 10 fraction digits "); | |
| 93 return false; | |
| 94 } | |
| 95 | |
| 96 return true; | |
| 97 } | |
| 98 | |
| 99 template <typename T> | |
| 100 bool areValidCurrencyAndAmounts(HeapVector<T> items, ExceptionState& exceptionSt ate) | |
| 101 { | |
| 102 for (const auto& item : items) { | |
| 103 if (item.hasAmount()) { | |
| 104 if (item.amount().hasCurrencyCode() && !isValidCurrencyCode(item.amo unt().currencyCode(), exceptionState)) | |
| 105 return false; | |
| 106 | |
| 107 if (item.amount().hasValue() && isValidAmount(item.amount().value(), exceptionState)) | |
|
Marijn Kruisselbrink
2016/03/18 01:39:48
Should this be !isValidAmount?
And some kind of te
please use gerrit instead
2016/03/23 00:14:51
Good catch. Tested and fixed.
| |
| 108 return false; | |
| 109 } | |
| 110 } | |
| 111 | |
| 112 return true; | |
| 113 } | |
| 114 | |
| 115 } // namespace | |
| 16 | 116 |
| 17 // static | 117 // static |
| 18 PaymentRequest* PaymentRequest::create(ScriptState* scriptState, const Vector<St ring>& supportedMethods, const PaymentDetails& details, ExceptionState& exceptio nState) | 118 PaymentRequest* PaymentRequest::create(ScriptState* scriptState, const Vector<St ring>& supportedMethods, const PaymentDetails& details, ExceptionState& exceptio nState) |
| 19 { | 119 { |
| 20 return new PaymentRequest(scriptState, supportedMethods, details, PaymentOpt ions(), ScriptValue(), exceptionState); | 120 return new PaymentRequest(scriptState, supportedMethods, details, PaymentOpt ions(), ScriptValue(), exceptionState); |
| 21 } | 121 } |
| 22 | 122 |
| 23 // static | 123 // static |
| 24 PaymentRequest* PaymentRequest::create(ScriptState* scriptState, const Vector<St ring>& supportedMethods, const PaymentDetails& details, const PaymentOptions& op tions, ExceptionState& exceptionState) | 124 PaymentRequest* PaymentRequest::create(ScriptState* scriptState, const Vector<St ring>& supportedMethods, const PaymentDetails& details, const PaymentOptions& op tions, ExceptionState& exceptionState) |
| 25 { | 125 { |
| 26 return new PaymentRequest(scriptState, supportedMethods, details, options, S criptValue(), exceptionState); | 126 return new PaymentRequest(scriptState, supportedMethods, details, options, S criptValue(), exceptionState); |
| 27 } | 127 } |
| 28 | 128 |
| 29 // static | 129 // static |
| 30 PaymentRequest* PaymentRequest::create(ScriptState* scriptState, const Vector<St ring>& supportedMethods, const PaymentDetails& details, const PaymentOptions& op tions, const ScriptValue& data, ExceptionState& exceptionState) | 130 PaymentRequest* PaymentRequest::create(ScriptState* scriptState, const Vector<St ring>& supportedMethods, const PaymentDetails& details, const PaymentOptions& op tions, const ScriptValue& data, ExceptionState& exceptionState) |
| 31 { | 131 { |
| 32 return new PaymentRequest(scriptState, supportedMethods, details, options, d ata, exceptionState); | 132 return new PaymentRequest(scriptState, supportedMethods, details, options, d ata, exceptionState); |
| 33 } | 133 } |
| 34 | 134 |
| 35 PaymentRequest::~PaymentRequest() | 135 PaymentRequest::~PaymentRequest() |
| 36 { | 136 { |
| 37 } | 137 } |
| 38 | 138 |
| 39 ScriptPromise PaymentRequest::show(ScriptState* scriptState) | 139 ScriptPromise PaymentRequest::show(ScriptState* scriptState) |
| 40 { | 140 { |
| 41 return ScriptPromise::rejectWithDOMException(scriptState, DOMException::crea te(NotSupportedError, "Not implemented.")); | 141 if (m_showResolver) |
| 142 return m_showResolver->promise().rejectWithDOMException(scriptState, DOM Exception::create(InvalidStateError, "Already called show() once")); | |
|
Marijn Kruisselbrink
2016/03/18 01:39:48
This seems weird. rejectWithDOMException is a stat
please use gerrit instead
2016/03/23 00:14:51
Did not realize it's static. Calling it properly n
| |
| 143 | |
| 144 ASSERT(!m_paymentProvider.is_bound()); | |
| 145 blink::Platform::current()->connectToRemoteService(mojo::GetProxy(&m_payment Provider)); | |
| 146 if (!m_paymentProvider) | |
| 147 return m_showResolver->promise().rejectWithDOMException(scriptState, DOM Exception::create(SyntaxError, "Not implemented on this platform")); | |
|
Marijn Kruisselbrink
2016/03/18 01:39:48
Same here, should just be ScriptPromise::rejectWit
please use gerrit instead
2016/03/23 00:14:51
Removed the check from this patch. Not writing con
| |
| 148 | |
| 149 m_paymentProvider->SetClient(m_clientBinding.CreateInterfacePtrAndBind()); | |
| 150 // TODO(rouslan): Call show() after WTF-Mojo serialization works. | |
| 151 | |
| 152 m_showResolver = ScriptPromiseResolver::create(scriptState); | |
| 153 return m_showResolver->promise(); | |
| 42 } | 154 } |
| 43 | 155 |
| 44 void PaymentRequest::abort() | 156 void PaymentRequest::abort(ExceptionState& exceptionState) |
| 45 { | 157 { |
| 158 if (!m_showResolver) { | |
| 159 exceptionState.throwDOMException(InvalidStateError, "Never called show() , so nothing to abort"); | |
| 160 return; | |
| 161 } | |
| 162 | |
| 163 m_paymentProvider->Abort(); | |
| 46 } | 164 } |
| 47 | 165 |
| 48 const AtomicString& PaymentRequest::interfaceName() const | 166 const AtomicString& PaymentRequest::interfaceName() const |
| 49 { | 167 { |
| 50 return EventTargetNames::PaymentRequest; | 168 return EventTargetNames::PaymentRequest; |
| 51 } | 169 } |
| 52 | 170 |
| 53 ExecutionContext* PaymentRequest::getExecutionContext() const | 171 ExecutionContext* PaymentRequest::getExecutionContext() const |
| 54 { | 172 { |
| 55 return m_scriptState->getExecutionContext(); | 173 return m_scriptState->getExecutionContext(); |
| 56 } | 174 } |
| 57 | 175 |
| 176 ScriptPromise PaymentRequest::complete(ScriptState* scriptState, bool success) | |
| 177 { | |
| 178 if (m_completeResolver) | |
| 179 return m_completeResolver->promise().rejectWithDOMException(scriptState, DOMException::create(InvalidStateError, "Already called complete() once")); | |
|
Marijn Kruisselbrink
2016/03/18 01:39:48
Same static method call thing.
please use gerrit instead
2016/03/23 00:14:51
Done.
| |
| 180 | |
| 181 m_completeResolver = ScriptPromiseResolver::create(scriptState); | |
| 182 m_paymentProvider->Complete(success); | |
| 183 | |
| 184 return m_completeResolver->promise(); | |
| 185 } | |
| 186 | |
| 58 DEFINE_TRACE(PaymentRequest) | 187 DEFINE_TRACE(PaymentRequest) |
| 59 { | 188 { |
| 60 visitor->trace(m_details); | 189 visitor->trace(m_details); |
| 61 visitor->trace(m_options); | 190 visitor->trace(m_options); |
| 62 visitor->trace(m_shippingAddress); | 191 visitor->trace(m_shippingAddress); |
| 192 visitor->trace(m_showResolver); | |
| 193 visitor->trace(m_completeResolver); | |
| 63 RefCountedGarbageCollectedEventTargetWithInlineData<PaymentRequest>::trace(v isitor); | 194 RefCountedGarbageCollectedEventTargetWithInlineData<PaymentRequest>::trace(v isitor); |
| 64 } | 195 } |
| 65 | 196 |
| 66 PaymentRequest::PaymentRequest(ScriptState* scriptState, const Vector<String>& s upportedMethods, const PaymentDetails& details, const PaymentOptions& options, c onst ScriptValue& data, ExceptionState& exceptionState) | 197 PaymentRequest::PaymentRequest(ScriptState* scriptState, const Vector<String>& s upportedMethods, const PaymentDetails& details, const PaymentOptions& options, c onst ScriptValue& data, ExceptionState& exceptionState) |
| 67 : m_scriptState(scriptState) | 198 : m_scriptState(scriptState) |
| 68 , m_supportedMethods(supportedMethods) | 199 , m_supportedMethods(supportedMethods) |
| 69 , m_details(details) | 200 , m_details(details) |
| 70 , m_options(options) | 201 , m_options(options) |
| 202 , m_clientBinding(this) | |
| 71 { | 203 { |
| 204 // TODO(rouslan): Also check for a top-level browsing context. | |
| 205 // https://github.com/w3c/browser-payment-api/issues/2 | |
| 206 if (!scriptState->getExecutionContext()->isSecureContext()) { | |
| 207 exceptionState.throwSecurityError("Must be in a secure context"); | |
| 208 return; | |
| 209 } | |
| 210 | |
| 211 if (supportedMethods.isEmpty()) { | |
| 212 exceptionState.throwTypeError("No payment methods identifiers provided") ; | |
| 213 return; | |
| 214 } | |
| 215 | |
| 216 if (details.hasItems() && !areValidCurrencyAndAmounts(details.items(), excep tionState)) | |
| 217 return; | |
| 218 | |
| 219 if (details.hasShippingOptions() && !areValidCurrencyAndAmounts(details.ship pingOptions(), exceptionState)) | |
| 220 return; | |
| 221 | |
| 72 if (!data.isEmpty()) { | 222 if (!data.isEmpty()) { |
| 73 RefPtr<JSONValue> value = toJSONValue(data.context(), data.v8Value()); | 223 RefPtr<JSONValue> value = toJSONValue(data.context(), data.v8Value()); |
| 74 if (value && value->getType() == JSONValue::TypeObject) | 224 if (value && !value->isNull()) { |
| 75 m_stringifiedData = JSONObject::cast(value)->toJSONString(); | 225 if (value->getType() != JSONValue::TypeObject) { |
| 226 exceptionState.throwTypeError("Payment method specific data shou ld be a JSON-serializable object"); | |
| 227 return; | |
| 228 } | |
| 229 | |
| 230 RefPtr<JSONObject> jsonData = JSONObject::cast(value); | |
| 231 for (const auto& paymentMethodSpecificKeyValue : *jsonData) { | |
| 232 if (!supportedMethods.contains(paymentMethodSpecificKeyValue.key )) { | |
| 233 exceptionState.throwTypeError("'" + paymentMethodSpecificKey Value.key + "' does not match one of the payment method identifiers"); | |
| 234 return; | |
| 235 } | |
| 236 if (!paymentMethodSpecificKeyValue.value || paymentMethodSpecifi cKeyValue.value->isNull() || paymentMethodSpecificKeyValue.value->getType() != J SONValue::TypeObject) { | |
| 237 exceptionState.throwTypeError("Data for '" + paymentMethodSp ecificKeyValue.key + "' should be a JSON-serializable object"); | |
| 238 return; | |
| 239 } | |
| 240 } | |
| 241 | |
| 242 m_stringifiedData = jsonData->toJSONString(); | |
| 243 } | |
| 76 } | 244 } |
| 245 | |
| 246 if (details.hasShippingOptions() && details.shippingOptions().size() == 1 && details.shippingOptions()[0].hasId()) | |
| 247 m_shippingOption = details.shippingOptions()[0].id(); | |
| 248 } | |
| 249 | |
| 250 void PaymentRequest::OnShippingAddressChange(mojom::blink::ShippingAddressPtr ad dress) | |
| 251 { | |
| 252 ASSERT(m_showResolver); | |
| 253 ASSERT(!m_completeResolver); | |
| 254 // TODO(rouslan): Should the merchant website be notified of invalid shippin g address | |
| 255 // from the browser or the payment app? | |
| 256 m_shippingAddress = new ShippingAddress(std::move(address)); | |
| 257 if (!m_shippingAddress->isValidRegionCode()) { | |
| 258 m_showResolver->reject(DOMException::create(SyntaxError, "Invalid ISO 31 66 country code")); | |
| 259 return; | |
| 260 } | |
| 261 if (!m_shippingAddress->isValidLanguageCode()) { | |
| 262 m_showResolver->reject(DOMException::create(SyntaxError, "Invalid ISO 63 9 language code")); | |
| 263 return; | |
| 264 } | |
| 265 if (!m_shippingAddress->isValidScriptCode()) { | |
| 266 m_showResolver->reject(DOMException::create(SyntaxError, "Invalid ISO 15 24 script code")); | |
| 267 return; | |
| 268 } | |
| 269 RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::shippingaddr esschange); | |
| 270 event->setTarget(this); | |
| 271 getExecutionContext()->getEventQueue()->enqueueEvent(event); | |
| 272 } | |
| 273 | |
| 274 void PaymentRequest::OnShippingOptionChange(const String& shippingOptionId) | |
| 275 { | |
| 276 ASSERT(m_showResolver); | |
| 277 ASSERT(!m_completeResolver); | |
| 278 m_shippingOption = shippingOptionId; | |
| 279 RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::shippingopti onchange); | |
| 280 event->setTarget(this); | |
| 281 getExecutionContext()->getEventQueue()->enqueueEvent(event); | |
| 282 } | |
| 283 | |
| 284 void PaymentRequest::OnPaymentResponse(mojom::blink::PaymentResponsePtr response ) | |
| 285 { | |
| 286 ASSERT(m_showResolver); | |
| 287 ASSERT(!m_completeResolver); | |
| 288 m_showResolver->resolve(new PaymentResponse(std::move(response), this)); | |
| 289 } | |
| 290 | |
| 291 void PaymentRequest::OnError() | |
| 292 { | |
| 293 if (m_completeResolver) { | |
| 294 m_completeResolver->reject(DOMException::create(SyntaxError, "Request ca ncelled")); | |
| 295 return; | |
| 296 } | |
| 297 ASSERT(m_showResolver); | |
| 298 m_showResolver->reject(DOMException::create(SyntaxError, "Request cancelled" )); | |
| 299 } | |
| 300 | |
| 301 void PaymentRequest::OnComplete() | |
| 302 { | |
| 303 ASSERT(m_completeResolver); | |
| 304 m_completeResolver->resolve(); | |
| 77 } | 305 } |
| 78 | 306 |
| 79 } // namespace blink | 307 } // namespace blink |
| OLD | NEW |