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
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/modules/payments/payment_request_event.h"
6
7 #include <utility>
8
9 #include "third_party/blink/public/mojom/payments/payment_request.mojom-blink.h"
10 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
11 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
12 #include "third_party/blink/renderer/bindings/modules/v8/v8_address_errors.h"
13 #include "third_party/blink/renderer/bindings/modules/v8/v8_payment_currency_amount.h"
14 #include "third_party/blink/renderer/bindings/modules/v8/v8_payment_details_modifier.h"
15 #include "third_party/blink/renderer/bindings/modules/v8/v8_payment_item.h"
16 #include "third_party/blink/renderer/bindings/modules/v8/v8_payment_method_data.h"
17 #include "third_party/blink/renderer/bindings/modules/v8/v8_payment_options.h"
18 #include "third_party/blink/renderer/bindings/modules/v8/v8_payment_request_details_update.h"
19 #include "third_party/blink/renderer/bindings/modules/v8/v8_payment_shipping_option.h"
20 #include "third_party/blink/renderer/core/dom/dom_exception.h"
21 #include "third_party/blink/renderer/core/workers/worker_global_scope.h"
22 #include "third_party/blink/renderer/core/workers/worker_location.h"
23 #include "third_party/blink/renderer/modules/payments/address_init_type_converter.h"
24 #include "third_party/blink/renderer/modules/payments/payments_validators.h"
25 #include "third_party/blink/renderer/modules/service_worker/respond_with_observer.h"
26 #include "third_party/blink/renderer/modules/service_worker/service_worker_global_scope.h"
27 #include "third_party/blink/renderer/modules/service_worker/service_worker_window_client.h"
28 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
29 #include "third_party/blink/renderer/platform/bindings/script_state.h"
30 #include "third_party/blink/renderer/platform/heap/heap.h"
31 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
32
33 namespace blink {
34
Create(const AtomicString & type,const PaymentRequestEventInit * initializer)35 PaymentRequestEvent* PaymentRequestEvent::Create(
36 const AtomicString& type,
37 const PaymentRequestEventInit* initializer) {
38 return MakeGarbageCollected<PaymentRequestEvent>(
39 type, initializer, mojo::NullRemote(), nullptr, nullptr);
40 }
41
Create(const AtomicString & type,const PaymentRequestEventInit * initializer,mojo::PendingRemote<payments::mojom::blink::PaymentHandlerHost> host,RespondWithObserver * respond_with_observer,WaitUntilObserver * wait_until_observer)42 PaymentRequestEvent* PaymentRequestEvent::Create(
43 const AtomicString& type,
44 const PaymentRequestEventInit* initializer,
45 mojo::PendingRemote<payments::mojom::blink::PaymentHandlerHost> host,
46 RespondWithObserver* respond_with_observer,
47 WaitUntilObserver* wait_until_observer) {
48 return MakeGarbageCollected<PaymentRequestEvent>(
49 type, initializer, std::move(host), respond_with_observer,
50 wait_until_observer);
51 }
52
PaymentRequestEvent(const AtomicString & type,const PaymentRequestEventInit * initializer,mojo::PendingRemote<payments::mojom::blink::PaymentHandlerHost> host,RespondWithObserver * respond_with_observer,WaitUntilObserver * wait_until_observer)53 PaymentRequestEvent::PaymentRequestEvent(
54 const AtomicString& type,
55 const PaymentRequestEventInit* initializer,
56 mojo::PendingRemote<payments::mojom::blink::PaymentHandlerHost> host,
57 RespondWithObserver* respond_with_observer,
58 WaitUntilObserver* wait_until_observer)
59 : ExtendableEvent(type, initializer, wait_until_observer),
60 top_origin_(initializer->topOrigin()),
61 payment_request_origin_(initializer->paymentRequestOrigin()),
62 payment_request_id_(initializer->paymentRequestId()),
63 method_data_(initializer->hasMethodData()
64 ? initializer->methodData()
65 : HeapVector<Member<PaymentMethodData>>()),
66 total_(initializer->hasTotal() ? initializer->total()
67 : PaymentCurrencyAmount::Create()),
68 modifiers_(initializer->hasModifiers()
69 ? initializer->modifiers()
70 : HeapVector<Member<PaymentDetailsModifier>>()),
71 instrument_key_(initializer->instrumentKey()),
72 payment_options_(initializer->hasPaymentOptions()
73 ? initializer->paymentOptions()
74 : PaymentOptions::Create()),
75 shipping_options_(initializer->hasShippingOptions()
76 ? initializer->shippingOptions()
77 : HeapVector<Member<PaymentShippingOption>>()),
78 observer_(respond_with_observer) {
79 if (!host.is_valid())
80 return;
81
82 payment_handler_host_.Bind(std::move(host));
83 payment_handler_host_.set_disconnect_handler(WTF::Bind(
84 &PaymentRequestEvent::OnHostConnectionError, WrapWeakPersistent(this)));
85 }
86
87 PaymentRequestEvent::~PaymentRequestEvent() = default;
88
InterfaceName() const89 const AtomicString& PaymentRequestEvent::InterfaceName() const {
90 return event_interface_names::kPaymentRequestEvent;
91 }
92
topOrigin() const93 const String& PaymentRequestEvent::topOrigin() const {
94 return top_origin_;
95 }
96
paymentRequestOrigin() const97 const String& PaymentRequestEvent::paymentRequestOrigin() const {
98 return payment_request_origin_;
99 }
100
paymentRequestId() const101 const String& PaymentRequestEvent::paymentRequestId() const {
102 return payment_request_id_;
103 }
104
methodData() const105 const HeapVector<Member<PaymentMethodData>>& PaymentRequestEvent::methodData()
106 const {
107 return method_data_;
108 }
109
total(ScriptState * script_state) const110 const ScriptValue PaymentRequestEvent::total(ScriptState* script_state) const {
111 return ScriptValue::From(script_state, total_);
112 }
113
114 const HeapVector<Member<PaymentDetailsModifier>>&
modifiers() const115 PaymentRequestEvent::modifiers() const {
116 return modifiers_;
117 }
118
instrumentKey() const119 const String& PaymentRequestEvent::instrumentKey() const {
120 return instrument_key_;
121 }
122
paymentOptions(ScriptState * script_state) const123 const ScriptValue PaymentRequestEvent::paymentOptions(
124 ScriptState* script_state) const {
125 if (!payment_options_)
126 return ScriptValue::CreateNull(script_state->GetIsolate());
127 return ScriptValue::From(script_state, payment_options_);
128 }
129
130 base::Optional<HeapVector<Member<PaymentShippingOption>>>
shippingOptions() const131 PaymentRequestEvent::shippingOptions() const {
132 if (shipping_options_.IsEmpty())
133 return base::nullopt;
134 return shipping_options_;
135 }
136
137 const HeapVector<Member<PaymentShippingOption>>&
shippingOptions(bool & is_null) const138 PaymentRequestEvent::shippingOptions(bool& is_null) const {
139 is_null = shipping_options_.IsEmpty();
140 return shipping_options_;
141 }
142
openWindow(ScriptState * script_state,const String & url)143 ScriptPromise PaymentRequestEvent::openWindow(ScriptState* script_state,
144 const String& url) {
145 auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
146 ScriptPromise promise = resolver->Promise();
147 ExecutionContext* context = ExecutionContext::From(script_state);
148
149 if (!isTrusted()) {
150 resolver->Reject(MakeGarbageCollected<DOMException>(
151 DOMExceptionCode::kInvalidStateError,
152 "Cannot open a window when the event is not trusted"));
153 return promise;
154 }
155
156 KURL parsed_url_to_open = context->CompleteURL(url);
157 if (!parsed_url_to_open.IsValid()) {
158 resolver->Reject(V8ThrowException::CreateTypeError(
159 script_state->GetIsolate(), "'" + url + "' is not a valid URL."));
160 return promise;
161 }
162
163 if (!context->GetSecurityOrigin()->IsSameOriginWith(
164 SecurityOrigin::Create(parsed_url_to_open).get())) {
165 resolver->Resolve(v8::Null(script_state->GetIsolate()));
166 return promise;
167 }
168
169 if (!context->IsWindowInteractionAllowed()) {
170 resolver->Reject(MakeGarbageCollected<DOMException>(
171 DOMExceptionCode::kNotAllowedError,
172 "Not allowed to open a window without user activation"));
173 return promise;
174 }
175 context->ConsumeWindowInteraction();
176
177 To<ServiceWorkerGlobalScope>(context)
178 ->GetServiceWorkerHost()
179 ->OpenPaymentHandlerWindow(
180 parsed_url_to_open,
181 ServiceWorkerWindowClient::CreateResolveWindowClientCallback(
182 resolver));
183 return promise;
184 }
185
changePaymentMethod(ScriptState * script_state,const String & method_name,ExceptionState & exception_state)186 ScriptPromise PaymentRequestEvent::changePaymentMethod(
187 ScriptState* script_state,
188 const String& method_name,
189 ExceptionState& exception_state) {
190 return changePaymentMethod(script_state, method_name, ScriptValue(),
191 exception_state);
192 }
193
changePaymentMethod(ScriptState * script_state,const String & method_name,const ScriptValue & method_details,ExceptionState & exception_state)194 ScriptPromise PaymentRequestEvent::changePaymentMethod(
195 ScriptState* script_state,
196 const String& method_name,
197 const ScriptValue& method_details,
198 ExceptionState& exception_state) {
199 if (change_payment_request_details_resolver_) {
200 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
201 "Waiting for response to the previous "
202 "payment request details change");
203 return ScriptPromise();
204 }
205
206 if (!payment_handler_host_.is_bound()) {
207 exception_state.ThrowDOMException(
208 DOMExceptionCode::kInvalidStateError,
209 "No corresponding PaymentRequest object found");
210 return ScriptPromise();
211 }
212
213 auto method_data = payments::mojom::blink::PaymentHandlerMethodData::New();
214 if (!method_details.IsEmpty()) {
215 PaymentsValidators::ValidateAndStringifyObject(
216 script_state->GetIsolate(), "Method details", method_details,
217 method_data->stringified_data, exception_state);
218 if (exception_state.HadException())
219 return ScriptPromise();
220 }
221
222 method_data->method_name = method_name;
223 payment_handler_host_->ChangePaymentMethod(
224 std::move(method_data),
225 WTF::Bind(&PaymentRequestEvent::OnChangePaymentRequestDetailsResponse,
226 WrapWeakPersistent(this)));
227 change_payment_request_details_resolver_ =
228 MakeGarbageCollected<ScriptPromiseResolver>(script_state);
229 return change_payment_request_details_resolver_->Promise();
230 }
231
changeShippingAddress(ScriptState * script_state,AddressInit * shipping_address,ExceptionState & exception_state)232 ScriptPromise PaymentRequestEvent::changeShippingAddress(
233 ScriptState* script_state,
234 AddressInit* shipping_address,
235 ExceptionState& exception_state) {
236 if (change_payment_request_details_resolver_) {
237 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
238 "Waiting for response to the previous "
239 "payment request details change");
240 return ScriptPromise();
241 }
242
243 if (!payment_handler_host_.is_bound()) {
244 exception_state.ThrowDOMException(
245 DOMExceptionCode::kInvalidStateError,
246 "No corresponding PaymentRequest object found");
247 return ScriptPromise();
248 }
249 if (!shipping_address) {
250 exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError,
251 "Shipping address cannot be null");
252 return ScriptPromise();
253 }
254
255 auto shipping_address_ptr =
256 payments::mojom::blink::PaymentAddress::From(shipping_address);
257 String shipping_address_error;
258 if (!PaymentsValidators::IsValidShippingAddress(shipping_address_ptr,
259 &shipping_address_error)) {
260 exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError,
261 shipping_address_error);
262 return ScriptPromise();
263 }
264
265 payment_handler_host_->ChangeShippingAddress(
266 std::move(shipping_address_ptr),
267 WTF::Bind(&PaymentRequestEvent::OnChangePaymentRequestDetailsResponse,
268 WrapWeakPersistent(this)));
269 change_payment_request_details_resolver_ =
270 MakeGarbageCollected<ScriptPromiseResolver>(script_state);
271 return change_payment_request_details_resolver_->Promise();
272 }
273
changeShippingOption(ScriptState * script_state,const String & shipping_option_id,ExceptionState & exception_state)274 ScriptPromise PaymentRequestEvent::changeShippingOption(
275 ScriptState* script_state,
276 const String& shipping_option_id,
277 ExceptionState& exception_state) {
278 if (change_payment_request_details_resolver_) {
279 exception_state.ThrowDOMException(
280 DOMExceptionCode::kInvalidStateError,
281 "Waiting for response to the previous payment request details change");
282 return ScriptPromise();
283 }
284
285 if (!payment_handler_host_.is_bound()) {
286 exception_state.ThrowDOMException(
287 DOMExceptionCode::kInvalidStateError,
288 "No corresponding PaymentRequest object found");
289 return ScriptPromise();
290 }
291
292 bool shipping_option_id_is_valid = false;
293 for (const auto& option : shipping_options_) {
294 if (option->id() == shipping_option_id) {
295 shipping_option_id_is_valid = true;
296 break;
297 }
298 }
299 if (!shipping_option_id_is_valid) {
300 exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError,
301 "Shipping option identifier is invalid");
302 return ScriptPromise();
303 }
304
305 payment_handler_host_->ChangeShippingOption(
306 shipping_option_id,
307 WTF::Bind(&PaymentRequestEvent::OnChangePaymentRequestDetailsResponse,
308 WrapWeakPersistent(this)));
309 change_payment_request_details_resolver_ =
310 MakeGarbageCollected<ScriptPromiseResolver>(script_state);
311 return change_payment_request_details_resolver_->Promise();
312 }
313
respondWith(ScriptState * script_state,ScriptPromise script_promise,ExceptionState & exception_state)314 void PaymentRequestEvent::respondWith(ScriptState* script_state,
315 ScriptPromise script_promise,
316 ExceptionState& exception_state) {
317 if (!isTrusted()) {
318 exception_state.ThrowDOMException(
319 DOMExceptionCode::kInvalidStateError,
320 "Cannot respond with data when the event is not trusted");
321 return;
322 }
323
324 stopImmediatePropagation();
325 if (observer_) {
326 observer_->RespondWith(script_state, script_promise, exception_state);
327 }
328 }
329
Trace(Visitor * visitor)330 void PaymentRequestEvent::Trace(Visitor* visitor) {
331 visitor->Trace(method_data_);
332 visitor->Trace(total_);
333 visitor->Trace(modifiers_);
334 visitor->Trace(payment_options_);
335 visitor->Trace(shipping_options_);
336 visitor->Trace(change_payment_request_details_resolver_);
337 visitor->Trace(observer_);
338 ExtendableEvent::Trace(visitor);
339 }
340
OnChangePaymentRequestDetailsResponse(payments::mojom::blink::PaymentRequestDetailsUpdatePtr response)341 void PaymentRequestEvent::OnChangePaymentRequestDetailsResponse(
342 payments::mojom::blink::PaymentRequestDetailsUpdatePtr response) {
343 if (!change_payment_request_details_resolver_)
344 return;
345
346 auto* dictionary = MakeGarbageCollected<PaymentRequestDetailsUpdate>();
347 if (!response->error.IsNull() && !response->error.IsEmpty()) {
348 dictionary->setError(response->error);
349 }
350
351 if (response->total) {
352 auto* total = MakeGarbageCollected<PaymentCurrencyAmount>();
353 total->setCurrency(response->total->currency);
354 total->setValue(response->total->value);
355 dictionary->setTotal(total);
356 }
357
358 ScriptState* script_state =
359 change_payment_request_details_resolver_->GetScriptState();
360 ScriptState::Scope scope(script_state);
361 ExceptionState exception_state(script_state->GetIsolate(),
362 ExceptionState::kConstructionContext,
363 "PaymentDetailsModifier");
364
365 if (response->modifiers) {
366 auto* modifiers =
367 MakeGarbageCollected<HeapVector<Member<PaymentDetailsModifier>>>();
368 for (const auto& response_modifier : *response->modifiers) {
369 if (!response_modifier)
370 continue;
371
372 auto* mod = MakeGarbageCollected<PaymentDetailsModifier>();
373 mod->setSupportedMethod(response_modifier->method_data->method_name);
374
375 if (response_modifier->total) {
376 auto* amount = MakeGarbageCollected<PaymentCurrencyAmount>();
377 amount->setCurrency(response_modifier->total->currency);
378 amount->setValue(response_modifier->total->value);
379 auto* total = MakeGarbageCollected<PaymentItem>();
380 total->setAmount(amount);
381 total->setLabel("");
382 mod->setTotal(total);
383 }
384
385 if (!response_modifier->method_data->stringified_data.IsEmpty()) {
386 v8::Local<v8::Value> parsed_value = FromJSONString(
387 script_state->GetIsolate(), script_state->GetContext(),
388 response_modifier->method_data->stringified_data, exception_state);
389 if (exception_state.HadException()) {
390 change_payment_request_details_resolver_->Reject(
391 MakeGarbageCollected<DOMException>(DOMExceptionCode::kSyntaxError,
392 exception_state.Message()));
393 change_payment_request_details_resolver_.Clear();
394 return;
395 }
396 mod->setData(ScriptValue(script_state->GetIsolate(), parsed_value));
397 modifiers->emplace_back(mod);
398 }
399 }
400 dictionary->setModifiers(*modifiers);
401 }
402
403 if (response->shipping_options) {
404 auto* shipping_options =
405 MakeGarbageCollected<HeapVector<Member<PaymentShippingOption>>>();
406 for (const auto& response_shipping_option : *response->shipping_options) {
407 if (!response_shipping_option)
408 continue;
409
410 auto* shipping_option = MakeGarbageCollected<PaymentShippingOption>();
411 auto* amount = MakeGarbageCollected<PaymentCurrencyAmount>();
412 amount->setCurrency(response_shipping_option->amount->currency);
413 amount->setValue(response_shipping_option->amount->value);
414 shipping_option->setAmount(amount);
415 shipping_option->setId(response_shipping_option->id);
416 shipping_option->setLabel(response_shipping_option->label);
417 shipping_option->setSelected(response_shipping_option->selected);
418 shipping_options->emplace_back(shipping_option);
419 }
420 dictionary->setShippingOptions(*shipping_options);
421 }
422
423 if (response->stringified_payment_method_errors &&
424 !response->stringified_payment_method_errors.IsEmpty()) {
425 v8::Local<v8::Value> parsed_value = FromJSONString(
426 script_state->GetIsolate(), script_state->GetContext(),
427 response->stringified_payment_method_errors, exception_state);
428 if (exception_state.HadException()) {
429 change_payment_request_details_resolver_->Reject(
430 MakeGarbageCollected<DOMException>(DOMExceptionCode::kSyntaxError,
431 exception_state.Message()));
432 change_payment_request_details_resolver_.Clear();
433 return;
434 }
435 dictionary->setPaymentMethodErrors(
436 ScriptValue(script_state->GetIsolate(), parsed_value));
437 }
438
439 if (response->shipping_address_errors) {
440 auto* shipping_address_errors = MakeGarbageCollected<AddressErrors>();
441 shipping_address_errors->setAddressLine(
442 response->shipping_address_errors->address_line);
443 shipping_address_errors->setCity(response->shipping_address_errors->city);
444 shipping_address_errors->setCountry(
445 response->shipping_address_errors->country);
446 shipping_address_errors->setDependentLocality(
447 response->shipping_address_errors->dependent_locality);
448 shipping_address_errors->setOrganization(
449 response->shipping_address_errors->organization);
450 shipping_address_errors->setPhone(response->shipping_address_errors->phone);
451 shipping_address_errors->setPostalCode(
452 response->shipping_address_errors->postal_code);
453 shipping_address_errors->setRecipient(
454 response->shipping_address_errors->recipient);
455 shipping_address_errors->setRegion(
456 response->shipping_address_errors->region);
457 shipping_address_errors->setSortingCode(
458 response->shipping_address_errors->sorting_code);
459 dictionary->setShippingAddressErrors(shipping_address_errors);
460 }
461
462 change_payment_request_details_resolver_->Resolve(
463 dictionary->hasError() || dictionary->hasTotal() ||
464 dictionary->hasModifiers() ||
465 dictionary->hasPaymentMethodErrors() ||
466 dictionary->hasShippingOptions() ||
467 dictionary->hasShippingAddressErrors()
468 ? dictionary
469 : nullptr);
470 change_payment_request_details_resolver_.Clear();
471 }
472
OnHostConnectionError()473 void PaymentRequestEvent::OnHostConnectionError() {
474 if (change_payment_request_details_resolver_) {
475 change_payment_request_details_resolver_->Reject(
476 MakeGarbageCollected<DOMException>(DOMExceptionCode::kAbortError,
477 "Browser process disconnected"));
478 }
479 change_payment_request_details_resolver_.Clear();
480 payment_handler_host_.reset();
481 }
482
483 } // namespace blink
484