1/*
2 * Copyright (C) 2017-2019 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "PaymentRequest.h"
28
29#if ENABLE(PAYMENT_REQUEST)
30
31#include "ApplePayPaymentHandler.h"
32#include "Document.h"
33#include "EventNames.h"
34#include "JSDOMPromise.h"
35#include "JSPaymentDetailsUpdate.h"
36#include "JSPaymentResponse.h"
37#include "Page.h"
38#include "PaymentAddress.h"
39#include "PaymentCoordinator.h"
40#include "PaymentCurrencyAmount.h"
41#include "PaymentDetailsInit.h"
42#include "PaymentHandler.h"
43#include "PaymentMethodChangeEvent.h"
44#include "PaymentMethodData.h"
45#include "PaymentOptions.h"
46#include "PaymentRequestUpdateEvent.h"
47#include "PaymentValidationErrors.h"
48#include "ScriptController.h"
49#include <JavaScriptCore/JSONObject.h>
50#include <JavaScriptCore/ThrowScope.h>
51#include <wtf/ASCIICType.h>
52#include <wtf/IsoMallocInlines.h>
53#include <wtf/RunLoop.h>
54#include <wtf/Scope.h>
55#include <wtf/UUID.h>
56
57namespace WebCore {
58
59WTF_MAKE_ISO_ALLOCATED_IMPL(PaymentRequest);
60
61// Implements the IsWellFormedCurrencyCode abstract operation from ECMA 402
62// https://tc39.github.io/ecma402/#sec-iswellformedcurrencycode
63static bool isWellFormedCurrencyCode(const String& currency)
64{
65 if (currency.length() == 3)
66 return currency.isAllSpecialCharacters<isASCIIAlpha>();
67 return false;
68}
69
70// Implements the "valid decimal monetary value" validity checker
71// https://www.w3.org/TR/payment-request/#dfn-valid-decimal-monetary-value
72static bool isValidDecimalMonetaryValue(StringView value)
73{
74 enum class State {
75 Start,
76 Sign,
77 Digit,
78 Dot,
79 DotDigit,
80 };
81
82 auto state = State::Start;
83 for (auto character : value.codeUnits()) {
84 switch (state) {
85 case State::Start:
86 if (character == '-') {
87 state = State::Sign;
88 break;
89 }
90
91 if (isASCIIDigit(character)) {
92 state = State::Digit;
93 break;
94 }
95
96 return false;
97
98 case State::Sign:
99 if (isASCIIDigit(character)) {
100 state = State::Digit;
101 break;
102 }
103
104 return false;
105
106 case State::Digit:
107 if (character == '.') {
108 state = State::Dot;
109 break;
110 }
111
112 if (isASCIIDigit(character)) {
113 state = State::Digit;
114 break;
115 }
116
117 return false;
118
119 case State::Dot:
120 if (isASCIIDigit(character)) {
121 state = State::DotDigit;
122 break;
123 }
124
125 return false;
126
127 case State::DotDigit:
128 if (isASCIIDigit(character)) {
129 state = State::DotDigit;
130 break;
131 }
132
133 return false;
134 }
135 }
136
137 if (state == State::Digit || state == State::DotDigit)
138 return true;
139
140 return false;
141}
142
143// Implements the "check and canonicalize amount" validity checker
144// https://www.w3.org/TR/payment-request/#dfn-check-and-canonicalize-amount
145static ExceptionOr<void> checkAndCanonicalizeAmount(PaymentCurrencyAmount& amount)
146{
147 if (!isWellFormedCurrencyCode(amount.currency))
148 return Exception { RangeError, makeString("\"", amount.currency, "\" is not a valid currency code.") };
149
150 if (!isValidDecimalMonetaryValue(amount.value))
151 return Exception { TypeError, makeString("\"", amount.value, "\" is not a valid decimal monetary value.") };
152
153 amount.currency = amount.currency.convertToASCIIUppercase();
154 return { };
155}
156
157// Implements the "check and canonicalize total" validity checker
158// https://www.w3.org/TR/payment-request/#dfn-check-and-canonicalize-total
159static ExceptionOr<void> checkAndCanonicalizeTotal(PaymentCurrencyAmount& total)
160{
161 auto exception = checkAndCanonicalizeAmount(total);
162 if (exception.hasException())
163 return exception;
164
165 if (total.value[0] == '-')
166 return Exception { TypeError, "Total currency values cannot be negative."_s };
167
168 return { };
169}
170
171// Implements "validate a standardized payment method identifier"
172// https://www.w3.org/TR/payment-method-id/#validity-0
173static bool isValidStandardizedPaymentMethodIdentifier(StringView identifier)
174{
175 enum class State {
176 Start,
177 Hyphen,
178 LowerAlpha,
179 Digit,
180 };
181
182 auto state = State::Start;
183 for (auto character : identifier.codeUnits()) {
184 switch (state) {
185 case State::Start:
186 case State::Hyphen:
187 if (isASCIILower(character)) {
188 state = State::LowerAlpha;
189 break;
190 }
191
192 return false;
193
194 case State::LowerAlpha:
195 case State::Digit:
196 if (isASCIILower(character)) {
197 state = State::LowerAlpha;
198 break;
199 }
200
201 if (isASCIIDigit(character)) {
202 state = State::Digit;
203 break;
204 }
205
206 if (character == '-') {
207 state = State::Hyphen;
208 break;
209 }
210
211 return false;
212 }
213 }
214
215 return state == State::LowerAlpha || state == State::Digit;
216}
217
218// Implements "validate a URL-based payment method identifier"
219// https://www.w3.org/TR/payment-method-id/#validation
220static bool isValidURLBasedPaymentMethodIdentifier(const URL& url)
221{
222 if (!url.protocolIs("https"))
223 return false;
224
225 if (!url.user().isEmpty() || !url.pass().isEmpty())
226 return false;
227
228 return true;
229}
230
231// Implements "validate a payment method identifier"
232// https://www.w3.org/TR/payment-method-id/#validity
233Optional<PaymentRequest::MethodIdentifier> convertAndValidatePaymentMethodIdentifier(const String& identifier)
234{
235 URL url { URL(), identifier };
236 if (!url.isValid()) {
237 if (isValidStandardizedPaymentMethodIdentifier(identifier))
238 return { identifier };
239 return WTF::nullopt;
240 }
241
242 if (isValidURLBasedPaymentMethodIdentifier(url))
243 return { WTFMove(url) };
244
245 return WTF::nullopt;
246}
247
248enum class ShouldValidatePaymentMethodIdentifier {
249 No,
250 Yes,
251};
252
253static ExceptionOr<std::tuple<String, Vector<String>>> checkAndCanonicalizeDetails(JSC::ExecState& execState, PaymentDetailsBase& details, bool requestShipping, ShouldValidatePaymentMethodIdentifier shouldValidatePaymentMethodIdentifier)
254{
255 for (auto& item : details.displayItems) {
256 auto exception = checkAndCanonicalizeAmount(item.amount);
257 if (exception.hasException())
258 return exception.releaseException();
259 }
260
261 String selectedShippingOption;
262 if (requestShipping) {
263 HashSet<String> seenShippingOptionIDs;
264 for (auto& shippingOption : details.shippingOptions) {
265 auto exception = checkAndCanonicalizeAmount(shippingOption.amount);
266 if (exception.hasException())
267 return exception.releaseException();
268
269 auto addResult = seenShippingOptionIDs.add(shippingOption.id);
270 if (!addResult.isNewEntry)
271 return Exception { TypeError, "Shipping option IDs must be unique." };
272
273 if (shippingOption.selected)
274 selectedShippingOption = shippingOption.id;
275 }
276 }
277
278 Vector<String> serializedModifierData;
279 serializedModifierData.reserveInitialCapacity(details.modifiers.size());
280 for (auto& modifier : details.modifiers) {
281 if (shouldValidatePaymentMethodIdentifier == ShouldValidatePaymentMethodIdentifier::Yes) {
282 auto paymentMethodIdentifier = convertAndValidatePaymentMethodIdentifier(modifier.supportedMethods);
283 if (!paymentMethodIdentifier)
284 return Exception { RangeError, makeString('"', modifier.supportedMethods, "\" is an invalid payment method identifier.") };
285 }
286
287 if (modifier.total) {
288 auto exception = checkAndCanonicalizeTotal(modifier.total->amount);
289 if (exception.hasException())
290 return exception.releaseException();
291 }
292
293 for (auto& item : modifier.additionalDisplayItems) {
294 auto exception = checkAndCanonicalizeAmount(item.amount);
295 if (exception.hasException())
296 return exception.releaseException();
297 }
298
299 String serializedData;
300 if (modifier.data) {
301 auto scope = DECLARE_THROW_SCOPE(execState.vm());
302 serializedData = JSONStringify(&execState, modifier.data.get(), 0);
303 if (scope.exception())
304 return Exception { ExistingExceptionError };
305 modifier.data.clear();
306 }
307 serializedModifierData.uncheckedAppend(WTFMove(serializedData));
308 }
309
310 return std::make_tuple(WTFMove(selectedShippingOption), WTFMove(serializedModifierData));
311}
312
313// Implements the PaymentRequest Constructor
314// https://www.w3.org/TR/payment-request/#constructor
315ExceptionOr<Ref<PaymentRequest>> PaymentRequest::create(Document& document, Vector<PaymentMethodData>&& methodData, PaymentDetailsInit&& details, PaymentOptions&& options)
316{
317 auto canCreateSession = PaymentHandler::canCreateSession(document);
318 if (canCreateSession.hasException())
319 return canCreateSession.releaseException();
320
321 if (details.id.isNull())
322 details.id = createCanonicalUUIDString();
323
324 if (methodData.isEmpty())
325 return Exception { TypeError, "At least one payment method is required."_s };
326
327 Vector<Method> serializedMethodData;
328 serializedMethodData.reserveInitialCapacity(methodData.size());
329 for (auto& paymentMethod : methodData) {
330 auto identifier = convertAndValidatePaymentMethodIdentifier(paymentMethod.supportedMethods);
331 if (!identifier)
332 return Exception { RangeError, makeString('"', paymentMethod.supportedMethods, "\" is an invalid payment method identifier.") };
333
334 String serializedData;
335 if (paymentMethod.data) {
336 auto scope = DECLARE_THROW_SCOPE(document.execState()->vm());
337 serializedData = JSONStringify(document.execState(), paymentMethod.data.get(), 0);
338 if (scope.exception())
339 return Exception { ExistingExceptionError };
340 }
341 serializedMethodData.uncheckedAppend({ WTFMove(*identifier), WTFMove(serializedData) });
342 }
343
344 auto totalResult = checkAndCanonicalizeTotal(details.total.amount);
345 if (totalResult.hasException())
346 return totalResult.releaseException();
347
348 auto detailsResult = checkAndCanonicalizeDetails(*document.execState(), details, options.requestShipping, ShouldValidatePaymentMethodIdentifier::No);
349 if (detailsResult.hasException())
350 return detailsResult.releaseException();
351
352 auto shippingOptionAndModifierData = detailsResult.releaseReturnValue();
353 return adoptRef(*new PaymentRequest(document, WTFMove(options), WTFMove(details), WTFMove(std::get<1>(shippingOptionAndModifierData)), WTFMove(serializedMethodData), WTFMove(std::get<0>(shippingOptionAndModifierData))));
354}
355
356bool PaymentRequest::enabledForContext(ScriptExecutionContext& context)
357{
358 return PaymentHandler::enabledForContext(context);
359}
360
361PaymentRequest::PaymentRequest(Document& document, PaymentOptions&& options, PaymentDetailsInit&& details, Vector<String>&& serializedModifierData, Vector<Method>&& serializedMethodData, String&& selectedShippingOption)
362 : ActiveDOMObject { document }
363 , m_options { WTFMove(options) }
364 , m_details { WTFMove(details) }
365 , m_serializedModifierData { WTFMove(serializedModifierData) }
366 , m_serializedMethodData { WTFMove(serializedMethodData) }
367 , m_shippingOption { WTFMove(selectedShippingOption) }
368{
369 suspendIfNeeded();
370}
371
372PaymentRequest::~PaymentRequest()
373{
374 ASSERT(!hasPendingActivity());
375 ASSERT(!m_activePaymentHandler);
376}
377
378static ExceptionOr<JSC::JSValue> parse(ScriptExecutionContext& context, const String& string)
379{
380 auto scope = DECLARE_THROW_SCOPE(context.vm());
381 JSC::JSValue data = JSONParse(context.execState(), string);
382 if (scope.exception())
383 return Exception { ExistingExceptionError };
384 return WTFMove(data);
385}
386
387// https://www.w3.org/TR/payment-request/#show()-method
388void PaymentRequest::show(Document& document, RefPtr<DOMPromise>&& detailsPromise, ShowPromise&& promise)
389{
390 if (!document.frame()) {
391 promise.reject(Exception { AbortError });
392 return;
393 }
394
395 if (!UserGestureIndicator::processingUserGesture()) {
396 promise.reject(Exception { SecurityError, "show() must be triggered by user activation." });
397 return;
398 }
399
400 if (m_state != State::Created) {
401 promise.reject(Exception { InvalidStateError });
402 return;
403 }
404
405 if (PaymentHandler::hasActiveSession(document)) {
406 promise.reject(Exception { AbortError });
407 return;
408 }
409
410 m_state = State::Interactive;
411 ASSERT(!m_showPromise);
412 m_showPromise = WTFMove(promise);
413
414 RefPtr<PaymentHandler> selectedPaymentHandler;
415 for (auto& paymentMethod : m_serializedMethodData) {
416 auto data = parse(document, paymentMethod.serializedData);
417 if (data.hasException()) {
418 settleShowPromise(data.releaseException());
419 return;
420 }
421
422 auto handler = PaymentHandler::create(document, *this, paymentMethod.identifier);
423 if (!handler)
424 continue;
425
426 auto result = handler->convertData(data.releaseReturnValue());
427 if (result.hasException()) {
428 settleShowPromise(result.releaseException());
429 return;
430 }
431
432 if (!selectedPaymentHandler)
433 selectedPaymentHandler = WTFMove(handler);
434 }
435
436 if (!selectedPaymentHandler) {
437 settleShowPromise(Exception { NotSupportedError });
438 return;
439 }
440
441 auto exception = selectedPaymentHandler->show(document);
442 if (exception.hasException()) {
443 settleShowPromise(exception.releaseException());
444 return;
445 }
446
447 ASSERT(!m_activePaymentHandler);
448 m_activePaymentHandler = PaymentHandlerWithPendingActivity { selectedPaymentHandler.releaseNonNull(), makePendingActivity(*this) };
449
450 if (!detailsPromise)
451 return;
452
453 exception = updateWith(UpdateReason::ShowDetailsResolved, detailsPromise.releaseNonNull());
454 ASSERT(!exception.hasException());
455}
456
457void PaymentRequest::abortWithException(Exception&& exception)
458{
459 ASSERT(m_state == State::Interactive);
460 closeActivePaymentHandler();
461
462 if (m_response)
463 m_response->abortWithException(WTFMove(exception));
464 else
465 settleShowPromise(WTFMove(exception));
466}
467
468void PaymentRequest::settleShowPromise(ExceptionOr<PaymentResponse&>&& result)
469{
470 if (auto showPromise = std::exchange(m_showPromise, WTF::nullopt))
471 showPromise->settle(WTFMove(result));
472}
473
474void PaymentRequest::closeActivePaymentHandler()
475{
476 if (auto activePaymentHandler = std::exchange(m_activePaymentHandler, WTF::nullopt))
477 activePaymentHandler->paymentHandler->hide();
478
479 m_isUpdating = false;
480 m_state = State::Closed;
481}
482
483void PaymentRequest::stop()
484{
485 closeActivePaymentHandler();
486 settleShowPromise(Exception { AbortError });
487}
488
489// https://www.w3.org/TR/payment-request/#abort()-method
490void PaymentRequest::abort(AbortPromise&& promise)
491{
492 if (m_response && m_response->hasRetryPromise()) {
493 promise.reject(Exception { InvalidStateError });
494 return;
495 }
496
497 if (m_state != State::Interactive) {
498 promise.reject(Exception { InvalidStateError });
499 return;
500 }
501
502 abortWithException(Exception { AbortError });
503 promise.resolve();
504}
505
506// https://www.w3.org/TR/payment-request/#canmakepayment()-method
507void PaymentRequest::canMakePayment(Document& document, CanMakePaymentPromise&& promise)
508{
509 if (m_state != State::Created) {
510 promise.reject(Exception { InvalidStateError });
511 return;
512 }
513
514 for (auto& paymentMethod : m_serializedMethodData) {
515 auto handler = PaymentHandler::create(document, *this, paymentMethod.identifier);
516 if (!handler)
517 continue;
518
519 handler->canMakePayment(document, [promise = WTFMove(promise)](bool canMakePayment) mutable {
520 promise.resolve(canMakePayment);
521 });
522 return;
523 }
524
525 promise.resolve(false);
526}
527
528const String& PaymentRequest::id() const
529{
530 return m_details.id;
531}
532
533Optional<PaymentShippingType> PaymentRequest::shippingType() const
534{
535 if (m_options.requestShipping)
536 return m_options.shippingType;
537 return WTF::nullopt;
538}
539
540bool PaymentRequest::canSuspendForDocumentSuspension() const
541{
542 return !hasPendingActivity();
543}
544
545void PaymentRequest::shippingAddressChanged(Ref<PaymentAddress>&& shippingAddress)
546{
547 whenDetailsSettled([this, protectedThis = makeRefPtr(this), shippingAddress = makeRefPtr(shippingAddress.get())]() mutable {
548 m_shippingAddress = WTFMove(shippingAddress);
549 dispatchEvent(PaymentRequestUpdateEvent::create(eventNames().shippingaddresschangeEvent));
550 });
551}
552
553void PaymentRequest::shippingOptionChanged(const String& shippingOption)
554{
555 whenDetailsSettled([this, protectedThis = makeRefPtr(this), shippingOption]() mutable {
556 m_shippingOption = shippingOption;
557 dispatchEvent(PaymentRequestUpdateEvent::create(eventNames().shippingoptionchangeEvent));
558 });
559}
560
561void PaymentRequest::paymentMethodChanged(const String& methodName, PaymentMethodChangeEvent::MethodDetailsFunction&& methodDetailsFunction)
562{
563 whenDetailsSettled([this, protectedThis = makeRefPtr(this), methodName, methodDetailsFunction = WTFMove(methodDetailsFunction)]() mutable {
564 auto& eventName = eventNames().paymentmethodchangeEvent;
565 if (hasEventListeners(eventName))
566 dispatchEvent(PaymentMethodChangeEvent::create(eventName, methodName, WTFMove(methodDetailsFunction)));
567 else
568 activePaymentHandler()->detailsUpdated(UpdateReason::PaymentMethodChanged, { }, { }, { }, { });
569 });
570}
571
572ExceptionOr<void> PaymentRequest::updateWith(UpdateReason reason, Ref<DOMPromise>&& promise)
573{
574 if (m_state != State::Interactive)
575 return Exception { InvalidStateError };
576
577 if (m_isUpdating)
578 return Exception { InvalidStateError };
579
580 m_isUpdating = true;
581
582 ASSERT(!m_detailsPromise);
583 m_detailsPromise = WTFMove(promise);
584 m_detailsPromise->whenSettled([this, protectedThis = makeRefPtr(this), reason]() {
585 settleDetailsPromise(reason);
586 });
587
588 return { };
589}
590
591ExceptionOr<void> PaymentRequest::completeMerchantValidation(Event& event, Ref<DOMPromise>&& merchantSessionPromise)
592{
593 if (m_state != State::Interactive)
594 return Exception { InvalidStateError };
595
596 event.stopPropagation();
597 event.stopImmediatePropagation();
598
599 m_merchantSessionPromise = WTFMove(merchantSessionPromise);
600 m_merchantSessionPromise->whenSettled([this, protectedThis = makeRefPtr(this)]() {
601 if (m_state != State::Interactive)
602 return;
603
604 if (m_merchantSessionPromise->status() == DOMPromise::Status::Rejected) {
605 abortWithException(Exception { AbortError });
606 return;
607 }
608
609 auto exception = activePaymentHandler()->merchantValidationCompleted(m_merchantSessionPromise->result());
610 if (exception.hasException()) {
611 abortWithException(exception.releaseException());
612 return;
613 }
614 });
615
616 return { };
617}
618
619void PaymentRequest::settleDetailsPromise(UpdateReason reason)
620{
621 auto scopeExit = makeScopeExit([&] {
622 m_isUpdating = false;
623 m_isCancelPending = false;
624 m_detailsPromise = nullptr;
625 });
626
627 if (m_state != State::Interactive)
628 return;
629
630 if (m_isCancelPending || m_detailsPromise->status() == DOMPromise::Status::Rejected) {
631 abortWithException(Exception { AbortError });
632 return;
633 }
634
635 auto& context = *m_detailsPromise->scriptExecutionContext();
636 auto throwScope = DECLARE_THROW_SCOPE(context.vm());
637 auto detailsUpdate = convertDictionary<PaymentDetailsUpdate>(*context.execState(), m_detailsPromise->result());
638 if (throwScope.exception()) {
639 abortWithException(Exception { ExistingExceptionError });
640 return;
641 }
642
643 auto totalResult = checkAndCanonicalizeTotal(detailsUpdate.total.amount);
644 if (totalResult.hasException()) {
645 abortWithException(totalResult.releaseException());
646 return;
647 }
648
649 auto detailsResult = checkAndCanonicalizeDetails(*context.execState(), detailsUpdate, m_options.requestShipping, ShouldValidatePaymentMethodIdentifier::Yes);
650 if (detailsResult.hasException()) {
651 abortWithException(detailsResult.releaseException());
652 return;
653 }
654
655 auto shippingOptionAndModifierData = detailsResult.releaseReturnValue();
656
657 m_details.total = WTFMove(detailsUpdate.total);
658 m_details.displayItems = WTFMove(detailsUpdate.displayItems);
659 if (m_options.requestShipping) {
660 m_details.shippingOptions = WTFMove(detailsUpdate.shippingOptions);
661 m_shippingOption = WTFMove(std::get<0>(shippingOptionAndModifierData));
662 }
663
664 m_details.modifiers = WTFMove(detailsUpdate.modifiers);
665 m_serializedModifierData = WTFMove(std::get<1>(shippingOptionAndModifierData));
666
667 auto result = activePaymentHandler()->detailsUpdated(reason, WTFMove(detailsUpdate.error), WTFMove(detailsUpdate.shippingAddressErrors), WTFMove(detailsUpdate.payerErrors), detailsUpdate.paymentMethodErrors.get());
668 if (result.hasException()) {
669 abortWithException(result.releaseException());
670 return;
671 }
672}
673
674void PaymentRequest::whenDetailsSettled(std::function<void()>&& callback)
675{
676 auto whenSettledFunction = [this, callback = WTFMove(callback)] {
677 ASSERT(m_state == State::Interactive);
678 ASSERT(!m_isUpdating);
679 ASSERT(!m_isCancelPending);
680 ASSERT_UNUSED(this, this);
681 callback();
682 };
683
684 if (!m_detailsPromise) {
685 whenSettledFunction();
686 return;
687 }
688
689 m_detailsPromise->whenSettled([this, protectedThis = makeRefPtr(this), whenSettledFunction = WTFMove(whenSettledFunction)] {
690 if (m_state == State::Interactive)
691 whenSettledFunction();
692 });
693}
694
695void PaymentRequest::accept(const String& methodName, PaymentResponse::DetailsFunction&& detailsFunction, Ref<PaymentAddress>&& shippingAddress, const String& payerName, const String& payerEmail, const String& payerPhone)
696{
697 ASSERT(!m_isUpdating);
698 ASSERT(m_state == State::Interactive);
699
700 bool isRetry = m_response;
701 if (!isRetry) {
702 m_response = PaymentResponse::create(scriptExecutionContext(), *this);
703 m_response->setRequestId(m_details.id);
704 }
705
706 m_response->setMethodName(methodName);
707 m_response->setDetailsFunction(WTFMove(detailsFunction));
708 m_response->setShippingAddress(m_options.requestShipping ? shippingAddress.ptr() : nullptr);
709 m_response->setShippingOption(m_options.requestShipping ? m_shippingOption : String { });
710 m_response->setPayerName(m_options.requestPayerName ? payerName : String { });
711 m_response->setPayerEmail(m_options.requestPayerEmail ? payerEmail : String { });
712 m_response->setPayerPhone(m_options.requestPayerPhone ? payerPhone : String { });
713
714 if (!isRetry)
715 settleShowPromise(*m_response);
716 else {
717 ASSERT(m_response->hasRetryPromise());
718 m_response->settleRetryPromise();
719 }
720
721 m_state = State::Closed;
722}
723
724ExceptionOr<void> PaymentRequest::complete(Optional<PaymentComplete>&& result)
725{
726 ASSERT(m_state == State::Closed);
727 if (!m_activePaymentHandler)
728 return Exception { AbortError };
729
730 activePaymentHandler()->complete(WTFMove(result));
731 m_activePaymentHandler = WTF::nullopt;
732 return { };
733}
734
735ExceptionOr<void> PaymentRequest::retry(PaymentValidationErrors&& errors)
736{
737 ASSERT(m_state == State::Closed);
738 if (!m_activePaymentHandler)
739 return Exception { AbortError };
740
741 m_state = State::Interactive;
742 return activePaymentHandler()->retry(WTFMove(errors));
743}
744
745void PaymentRequest::cancel()
746{
747 m_activePaymentHandler = WTF::nullopt;
748
749 if (m_isUpdating) {
750 m_isCancelPending = true;
751 return;
752 }
753
754 abortWithException(Exception { AbortError });
755}
756
757} // namespace WebCore
758
759#endif // ENABLE(PAYMENT_REQUEST)
760