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