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