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