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 |