1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "BasicCardPayment.h"
8 #include "mozilla/dom/Document.h"
9 #include "mozilla/dom/Element.h"
10 #include "mozilla/dom/FeaturePolicyUtils.h"
11 #include "mozilla/dom/PaymentMethodChangeEvent.h"
12 #include "mozilla/dom/PaymentRequest.h"
13 #include "mozilla/dom/PaymentRequestChild.h"
14 #include "mozilla/dom/PaymentRequestManager.h"
15 #include "mozilla/dom/RootedDictionary.h"
16 #include "mozilla/dom/UserActivation.h"
17 #include "mozilla/dom/WindowContext.h"
18 #include "mozilla/intl/Locale.h"
19 #include "mozilla/intl/LocaleService.h"
20 #include "mozilla/StaticPrefs_dom.h"
21 #include "nsContentUtils.h"
22 #include "nsIDUtils.h"
23 #include "nsImportModule.h"
24 #include "nsIRegion.h"
25 #include "nsIScriptError.h"
26 #include "nsIURLParser.h"
28 #include "nsServiceManagerUtils.h"
29 #include "mozilla/dom/MerchantValidationEvent.h"
30 #include "PaymentResponse.h"
32 using mozilla::intl::LocaleService
;
34 namespace mozilla::dom
{
36 NS_IMPL_CYCLE_COLLECTION_CLASS(PaymentRequest
)
38 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PaymentRequest
,
40 // Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because
41 // DOMEventTargetHelper does it for us.
42 NS_IMPL_CYCLE_COLLECTION_TRACE_END
44 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PaymentRequest
,
46 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResultPromise
)
47 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAcceptPromise
)
48 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbortPromise
)
49 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponse
)
50 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mShippingAddress
)
51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFullShippingAddress
)
52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument
)
53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
55 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PaymentRequest
,
57 NS_IMPL_CYCLE_COLLECTION_UNLINK(mResultPromise
)
58 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAcceptPromise
)
59 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAbortPromise
)
60 NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponse
)
61 NS_IMPL_CYCLE_COLLECTION_UNLINK(mShippingAddress
)
62 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFullShippingAddress
)
63 tmp
->UnregisterActivityObserver();
64 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument
)
65 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
67 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PaymentRequest
)
68 NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity
)
69 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
71 NS_IMPL_ADDREF_INHERITED(PaymentRequest
, DOMEventTargetHelper
)
72 NS_IMPL_RELEASE_INHERITED(PaymentRequest
, DOMEventTargetHelper
)
74 bool PaymentRequest::PrefEnabled(JSContext
* aCx
, JSObject
* aObj
) {
75 #if defined(NIGHTLY_BUILD)
76 if (!XRE_IsContentProcess()) {
79 if (!StaticPrefs::dom_payments_request_enabled()) {
82 RefPtr
<PaymentRequestManager
> manager
= PaymentRequestManager::GetSingleton();
85 nsCOMPtr
<nsIRegion
> regionJsm
=
86 do_ImportESModule("resource://gre/modules/Region.sys.mjs", "Region");
88 nsresult rv
= regionJsm
->GetHome(region
);
93 if (!manager
->IsRegionSupported(region
)) {
97 LocaleService::GetInstance()->GetAppLocaleAsBCP47(locale
);
98 mozilla::intl::Locale loc
;
99 auto result
= mozilla::intl::LocaleParser::TryParse(locale
, loc
);
100 if (!(result
.isOk() && loc
.Canonicalize().isOk() &&
101 loc
.Language().EqualTo("en") && loc
.Region().EqualTo("US"))) {
111 void PaymentRequest::IsValidStandardizedPMI(const nsAString
& aIdentifier
,
114 * The syntax of a standardized payment method identifier is given by the
117 * stdpmi = part *( "-" part )
118 * part = 1loweralpha *( DIGIT / loweralpha )
119 * loweralpha = %x61-7A
121 const char16_t
* start
= aIdentifier
.BeginReading();
122 const char16_t
* end
= aIdentifier
.EndReading();
123 while (start
!= end
) {
124 // the first char must be in the range %x61-7A
125 if ((*start
< 'a' || *start
> 'z')) {
127 error
.AssignLiteral("'");
128 error
.Append(NS_ConvertUTF16toUTF8(aIdentifier
));
129 error
.AppendLiteral("' is not valid. The character '");
130 error
.Append(NS_ConvertUTF16toUTF8(start
, 1));
132 "' at the beginning or after the '-' must be in the range [a-z].");
133 aRv
.ThrowRangeError(error
);
137 // the rest can be in the range %x61-7A + DIGITs
138 while (start
!= end
&& *start
!= '-' &&
139 ((*start
>= 'a' && *start
<= 'z') ||
140 (*start
>= '0' && *start
<= '9'))) {
143 // if the char is not in the range %x61-7A + DIGITs, it must be '-'
144 if (start
!= end
&& *start
!= '-') {
146 error
.AssignLiteral("'");
147 error
.Append(NS_ConvertUTF16toUTF8(aIdentifier
));
148 error
.AppendLiteral("' is not valid. The character '");
149 error
.Append(NS_ConvertUTF16toUTF8(start
, 1));
150 error
.AppendLiteral("' must be in the range [a-zA-z0-9-].");
151 aRv
.ThrowRangeError(error
);
156 // the last char can not be '-'
159 error
.AssignLiteral("'");
160 error
.Append(NS_ConvertUTF16toUTF8(aIdentifier
));
161 error
.AppendLiteral("' is not valid. The last character '");
162 error
.Append(NS_ConvertUTF16toUTF8(start
, 1));
163 error
.AppendLiteral("' must be in the range [a-z0-9].");
164 aRv
.ThrowRangeError(error
);
171 void PaymentRequest::IsValidPaymentMethodIdentifier(
172 const nsAString
& aIdentifier
, ErrorResult
& aRv
) {
173 if (aIdentifier
.IsEmpty()) {
174 aRv
.ThrowTypeError("Payment method identifier is required.");
178 * URL-based payment method identifier
180 * 1. If url's scheme is not "https", return false.
181 * 2. If url's username or password is not the empty string, return false.
182 * 3. Otherwise, return true.
184 nsCOMPtr
<nsIURLParser
> urlParser
= do_GetService(NS_STDURLPARSER_CONTRACTID
);
185 MOZ_ASSERT(urlParser
);
186 uint32_t schemePos
= 0;
187 int32_t schemeLen
= 0;
188 uint32_t authorityPos
= 0;
189 int32_t authorityLen
= 0;
190 NS_ConvertUTF16toUTF8
url(aIdentifier
);
192 urlParser
->ParseURL(url
.get(), url
.Length(), &schemePos
, &schemeLen
,
193 &authorityPos
, &authorityLen
, nullptr, nullptr);
196 error
.AppendLiteral("Error parsing payment method identifier '");
197 error
.Append(NS_ConvertUTF16toUTF8(aIdentifier
));
198 error
.AppendLiteral("'as a URL.");
199 aRv
.ThrowRangeError(error
);
203 if (schemeLen
== -1) {
204 // The PMI is not a URL-based PMI, check if it is a standardized PMI
205 IsValidStandardizedPMI(aIdentifier
, aRv
);
208 if (!Substring(aIdentifier
, schemePos
, schemeLen
).EqualsASCII("https")) {
210 error
.AssignLiteral("'");
211 error
.Append(NS_ConvertUTF16toUTF8(aIdentifier
));
212 error
.AppendLiteral("' is not valid. The scheme must be 'https'.");
213 aRv
.ThrowRangeError(error
);
216 if (Substring(aIdentifier
, authorityPos
, authorityLen
).IsEmpty()) {
218 error
.AssignLiteral("'");
219 error
.Append(NS_ConvertUTF16toUTF8(aIdentifier
));
220 error
.AppendLiteral("' is not valid. hostname can not be empty.");
221 aRv
.ThrowRangeError(error
);
225 uint32_t usernamePos
= 0;
226 int32_t usernameLen
= 0;
227 uint32_t passwordPos
= 0;
228 int32_t passwordLen
= 0;
229 uint32_t hostnamePos
= 0;
230 int32_t hostnameLen
= 0;
233 NS_ConvertUTF16toUTF8
authority(
234 Substring(aIdentifier
, authorityPos
, authorityLen
));
235 rv
= urlParser
->ParseAuthority(
236 authority
.get(), authority
.Length(), &usernamePos
, &usernameLen
,
237 &passwordPos
, &passwordLen
, &hostnamePos
, &hostnameLen
, &port
);
239 // Handle the special cases that URLParser treats it as an invalid URL, but
240 // are used in web-platform-test
242 // https://:@example.com // should be considered as valid
243 // https://:password@example.com. // should be considered as invalid
244 int32_t atPos
= authority
.FindChar('@');
246 // only accept the case https://:@xxx
247 if (atPos
== 1 && authority
.CharAt(0) == ':') {
253 // for the fail cases, don't care about what the actual length is.
255 usernameLen
= INT32_MAX
;
257 passwordLen
= INT32_MAX
;
265 // Parse server information when both username and password are empty or do
267 if ((usernameLen
<= 0) && (passwordLen
<= 0)) {
268 if (authority
.Length() - atPos
- 1 == 0) {
270 error
.AssignLiteral("'");
271 error
.Append(NS_ConvertUTF16toUTF8(aIdentifier
));
272 error
.AppendLiteral("' is not valid. hostname can not be empty.");
273 aRv
.ThrowRangeError(error
);
276 // Re-using nsIURLParser::ParseServerInfo to extract the hostname and port
277 // information. This can help us to handle complicated IPv6 cases.
278 nsAutoCString
serverInfo(
279 Substring(authority
, atPos
+ 1, authority
.Length() - atPos
- 1));
280 rv
= urlParser
->ParseServerInfo(serverInfo
.get(), serverInfo
.Length(),
281 &hostnamePos
, &hostnameLen
, &port
);
283 // ParseServerInfo returns NS_ERROR_MALFORMED_URI in all fail cases, we
284 // probably need a followup bug to figure out the fail reason.
286 error
.AssignLiteral("Error extracting hostname from '");
287 error
.Append(serverInfo
);
288 error
.AppendLiteral("'.");
289 aRv
.ThrowRangeError(error
);
294 // PMI is valid when usernameLen/passwordLen equals to -1 or 0.
295 if (usernameLen
> 0 || passwordLen
> 0) {
297 error
.AssignLiteral("'");
298 error
.Append(NS_ConvertUTF16toUTF8(aIdentifier
));
299 error
.AssignLiteral("' is not valid. Username and password must be empty.");
300 aRv
.ThrowRangeError(error
);
304 // PMI is valid when hostnameLen is larger than 0
305 if (hostnameLen
<= 0) {
307 error
.AssignLiteral("'");
308 error
.Append(NS_ConvertUTF16toUTF8(aIdentifier
));
309 error
.AppendLiteral("' is not valid. hostname can not be empty.");
310 aRv
.ThrowRangeError(error
);
315 void PaymentRequest::IsValidMethodData(
316 JSContext
* aCx
, const Sequence
<PaymentMethodData
>& aMethodData
,
318 if (!aMethodData
.Length()) {
319 aRv
.ThrowTypeError("At least one payment method is required.");
323 nsTArray
<nsString
> methods
;
324 for (const PaymentMethodData
& methodData
: aMethodData
) {
325 IsValidPaymentMethodIdentifier(methodData
.mSupportedMethods
, aRv
);
330 RefPtr
<BasicCardService
> service
= BasicCardService::GetService();
332 if (service
->IsBasicCardPayment(methodData
.mSupportedMethods
)) {
333 if (!methodData
.mData
.WasPassed()) {
338 if (!service
->IsValidBasicCardRequest(aCx
, methodData
.mData
.Value(),
340 aRv
.ThrowTypeError(NS_ConvertUTF16toUTF8(error
));
344 if (!methods
.Contains(methodData
.mSupportedMethods
)) {
345 methods
.AppendElement(methodData
.mSupportedMethods
);
347 aRv
.ThrowRangeError(nsPrintfCString(
348 "Duplicate payment method '%s'",
349 NS_ConvertUTF16toUTF8(methodData
.mSupportedMethods
).get()));
355 void PaymentRequest::IsValidNumber(const nsAString
& aItem
,
356 const nsAString
& aStr
, ErrorResult
& aRv
) {
357 nsresult error
= NS_ERROR_FAILURE
;
359 if (!aStr
.IsEmpty()) {
360 nsAutoString
aValue(aStr
);
362 // If the beginning character is '-', we will check the second one.
363 int beginningIndex
= (aValue
.First() == '-') ? 1 : 0;
366 // - the beginning character is a digit in [0-9], and
367 // - the last character is not '.'
369 // https://w3c.github.io/browser-payment-api/#dfn-valid-decimal-monetary-value
371 // For example, ".1" is not valid for '.' is not in [0-9],
372 // and " 0.1" either for beginning with ' '
373 if (aValue
.Last() != '.' && aValue
.CharAt(beginningIndex
) >= '0' &&
374 aValue
.CharAt(beginningIndex
) <= '9') {
375 aValue
.ToFloat(&error
);
379 if (NS_FAILED(error
)) {
380 nsAutoCString errorMsg
;
381 errorMsg
.AssignLiteral("The amount.value of \"");
382 errorMsg
.Append(NS_ConvertUTF16toUTF8(aItem
));
383 errorMsg
.AppendLiteral("\"(");
384 errorMsg
.Append(NS_ConvertUTF16toUTF8(aStr
));
385 errorMsg
.AppendLiteral(") must be a valid decimal monetary value.");
386 aRv
.ThrowTypeError(errorMsg
);
391 void PaymentRequest::IsNonNegativeNumber(const nsAString
& aItem
,
392 const nsAString
& aStr
,
394 nsresult error
= NS_ERROR_FAILURE
;
396 if (!aStr
.IsEmpty()) {
397 nsAutoString
aValue(aStr
);
399 // - the beginning character is a digit in [0-9], and
400 // - the last character is not '.'
401 if (aValue
.Last() != '.' && aValue
.First() >= '0' &&
402 aValue
.First() <= '9') {
403 aValue
.ToFloat(&error
);
407 if (NS_FAILED(error
)) {
408 nsAutoCString errorMsg
;
409 errorMsg
.AssignLiteral("The amount.value of \"");
410 errorMsg
.Append(NS_ConvertUTF16toUTF8(aItem
));
411 errorMsg
.AppendLiteral("\"(");
412 errorMsg
.Append(NS_ConvertUTF16toUTF8(aStr
));
413 errorMsg
.AppendLiteral(
414 ") must be a valid and non-negative decimal monetary value.");
415 aRv
.ThrowTypeError(errorMsg
);
420 void PaymentRequest::IsValidCurrency(const nsAString
& aItem
,
421 const nsAString
& aCurrency
,
424 * According to spec in
425 * https://w3c.github.io/payment-request/#validity-checkers, perform currency
426 * validation with following criteria
427 * 1. The currency length must be 3.
428 * 2. The currency contains any character that must be in the range "A" to
429 * "Z" (U+0041 to U+005A) or the range "a" to "z" (U+0061 to U+007A)
431 if (aCurrency
.Length() != 3) {
433 error
.AssignLiteral("The length amount.currency of \"");
434 error
.Append(NS_ConvertUTF16toUTF8(aItem
));
435 error
.AppendLiteral("\"(");
436 error
.Append(NS_ConvertUTF16toUTF8(aCurrency
));
437 error
.AppendLiteral(") must be 3.");
438 aRv
.ThrowRangeError(error
);
441 // Don't use nsUnicharUtils::ToUpperCase, it converts the invalid "ınr" PMI to
442 // to the valid one "INR".
443 for (uint32_t idx
= 0; idx
< aCurrency
.Length(); ++idx
) {
444 if ((aCurrency
.CharAt(idx
) >= 'A' && aCurrency
.CharAt(idx
) <= 'Z') ||
445 (aCurrency
.CharAt(idx
) >= 'a' && aCurrency
.CharAt(idx
) <= 'z')) {
449 error
.AssignLiteral("The character amount.currency of \"");
450 error
.Append(NS_ConvertUTF16toUTF8(aItem
));
451 error
.AppendLiteral("\"(");
452 error
.Append(NS_ConvertUTF16toUTF8(aCurrency
));
454 ") must be in the range 'A' to 'Z'(U+0041 to U+005A) or 'a' to "
455 "'z'(U+0061 to U+007A).");
456 aRv
.ThrowRangeError(error
);
461 void PaymentRequest::IsValidCurrencyAmount(const nsAString
& aItem
,
462 const PaymentCurrencyAmount
& aAmount
,
463 const bool aIsTotalItem
,
465 IsValidCurrency(aItem
, aAmount
.mCurrency
, aRv
);
470 IsNonNegativeNumber(aItem
, aAmount
.mValue
, aRv
);
475 IsValidNumber(aItem
, aAmount
.mValue
, aRv
);
482 void PaymentRequest::IsValidDetailsInit(const PaymentDetailsInit
& aDetails
,
483 const bool aRequestShipping
,
485 // Check the amount.value and amount.currency of detail.total
486 IsValidCurrencyAmount(u
"details.total"_ns
, aDetails
.mTotal
.mAmount
,
492 return IsValidDetailsBase(aDetails
, aRequestShipping
, aRv
);
495 void PaymentRequest::IsValidDetailsUpdate(const PaymentDetailsUpdate
& aDetails
,
496 const bool aRequestShipping
,
498 // Check the amount.value and amount.currency of detail.total
499 if (aDetails
.mTotal
.WasPassed()) {
500 IsValidCurrencyAmount(u
"details.total"_ns
, aDetails
.mTotal
.Value().mAmount
,
507 IsValidDetailsBase(aDetails
, aRequestShipping
, aRv
);
510 void PaymentRequest::IsValidDetailsBase(const PaymentDetailsBase
& aDetails
,
511 const bool aRequestShipping
,
513 // Check the amount.value of each item in the display items
514 if (aDetails
.mDisplayItems
.WasPassed()) {
515 const Sequence
<PaymentItem
>& displayItems
= aDetails
.mDisplayItems
.Value();
516 for (const PaymentItem
& displayItem
: displayItems
) {
517 IsValidCurrencyAmount(displayItem
.mLabel
, displayItem
.mAmount
,
518 false, // isTotalItem
526 // Check the shipping option
527 if (aDetails
.mShippingOptions
.WasPassed() && aRequestShipping
) {
528 const Sequence
<PaymentShippingOption
>& shippingOptions
=
529 aDetails
.mShippingOptions
.Value();
530 nsTArray
<nsString
> seenIDs
;
531 for (const PaymentShippingOption
& shippingOption
: shippingOptions
) {
532 IsValidCurrencyAmount(u
"details.shippingOptions"_ns
,
533 shippingOption
.mAmount
,
534 false, // isTotalItem
539 if (seenIDs
.Contains(shippingOption
.mId
)) {
541 error
.AssignLiteral("Duplicate shippingOption id '");
542 error
.Append(NS_ConvertUTF16toUTF8(shippingOption
.mId
));
543 error
.AppendLiteral("'");
544 aRv
.ThrowTypeError(error
);
547 seenIDs
.AppendElement(shippingOption
.mId
);
551 // Check payment details modifiers
552 if (aDetails
.mModifiers
.WasPassed()) {
553 const Sequence
<PaymentDetailsModifier
>& modifiers
=
554 aDetails
.mModifiers
.Value();
555 for (const PaymentDetailsModifier
& modifier
: modifiers
) {
556 IsValidPaymentMethodIdentifier(modifier
.mSupportedMethods
, aRv
);
560 if (modifier
.mTotal
.WasPassed()) {
561 IsValidCurrencyAmount(u
"details.modifiers.total"_ns
,
562 modifier
.mTotal
.Value().mAmount
,
569 if (modifier
.mAdditionalDisplayItems
.WasPassed()) {
570 const Sequence
<PaymentItem
>& displayItems
=
571 modifier
.mAdditionalDisplayItems
.Value();
572 for (const PaymentItem
& displayItem
: displayItems
) {
573 IsValidCurrencyAmount(displayItem
.mLabel
, displayItem
.mAmount
,
574 false, // isTotalItem
585 already_AddRefed
<PaymentRequest
> PaymentRequest::Constructor(
586 const GlobalObject
& aGlobal
, const Sequence
<PaymentMethodData
>& aMethodData
,
587 const PaymentDetailsInit
& aDetails
, const PaymentOptions
& aOptions
,
589 nsCOMPtr
<nsPIDOMWindowInner
> window
=
590 do_QueryInterface(aGlobal
.GetAsSupports());
592 aRv
.ThrowAbortError("No global object for creating PaymentRequest");
596 nsCOMPtr
<Document
> doc
= window
->GetExtantDoc();
598 aRv
.ThrowAbortError("No document for creating PaymentRequest");
602 // the feature can only be used in an active document
603 if (!doc
->IsCurrentActiveDocument()) {
604 aRv
.ThrowSecurityError(
605 "Can't create a PaymentRequest for an inactive document");
609 if (!FeaturePolicyUtils::IsFeatureAllowed(doc
, u
"payment"_ns
)) {
610 aRv
.ThrowSecurityError(
611 "Document's Feature Policy does not allow to create a PaymentRequest");
615 // Get the top same process document
616 nsCOMPtr
<Document
> topSameProcessDoc
= doc
;
617 topSameProcessDoc
= doc
;
618 while (topSameProcessDoc
) {
619 nsCOMPtr
<Document
> parent
= topSameProcessDoc
->GetInProcessParentDocument();
620 if (!parent
|| !parent
->IsContentDocument()) {
623 topSameProcessDoc
= parent
;
625 nsCOMPtr
<nsIPrincipal
> topLevelPrincipal
= topSameProcessDoc
->NodePrincipal();
627 // Check payment methods and details
628 IsValidMethodData(aGlobal
.Context(), aMethodData
, aRv
);
632 IsValidDetailsInit(aDetails
, aOptions
.mRequestShipping
, aRv
);
637 RefPtr
<PaymentRequestManager
> manager
= PaymentRequestManager::GetSingleton();
638 if (NS_WARN_IF(!manager
)) {
642 // Create PaymentRequest and set its |mId|
643 RefPtr
<PaymentRequest
> request
;
644 manager
->CreatePayment(aGlobal
.Context(), window
, topLevelPrincipal
,
645 aMethodData
, aDetails
, aOptions
,
646 getter_AddRefs(request
), aRv
);
650 return request
.forget();
653 already_AddRefed
<PaymentRequest
> PaymentRequest::CreatePaymentRequest(
654 nsPIDOMWindowInner
* aWindow
, ErrorResult
& aRv
) {
655 // Generate a unique id for identification
657 if (NS_WARN_IF(NS_FAILED(nsID::GenerateUUIDInPlace(uuid
)))) {
659 "Failed to create an internal UUID for the PaymentRequest");
663 NSID_TrimBracketsUTF16
id(uuid
);
665 // Create payment request with generated id
666 RefPtr
<PaymentRequest
> request
= new PaymentRequest(aWindow
, id
);
667 return request
.forget();
670 PaymentRequest::PaymentRequest(nsPIDOMWindowInner
* aWindow
,
671 const nsAString
& aInternalId
)
672 : DOMEventTargetHelper(aWindow
),
673 mInternalId(aInternalId
),
674 mShippingAddress(nullptr),
676 mRequestShipping(false),
680 RegisterActivityObserver();
683 already_AddRefed
<Promise
> PaymentRequest::CanMakePayment(ErrorResult
& aRv
) {
684 if (!InFullyActiveDocument()) {
685 aRv
.ThrowAbortError("The owner document is not fully active");
689 if (mState
!= eCreated
) {
690 aRv
.ThrowInvalidStateError(
691 "The PaymentRequest's state should be 'Created'");
695 if (mResultPromise
) {
696 // XXX This doesn't match the spec but does match Chromium.
697 aRv
.ThrowNotAllowedError(
698 "PaymentRequest.CanMakePayment() has already been called");
702 nsIGlobalObject
* global
= GetOwnerGlobal();
703 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
708 RefPtr
<PaymentRequestManager
> manager
= PaymentRequestManager::GetSingleton();
710 manager
->CanMakePayment(this, aRv
);
714 mResultPromise
= promise
;
715 return promise
.forget();
718 void PaymentRequest::RespondCanMakePayment(bool aResult
) {
719 MOZ_ASSERT(mResultPromise
);
720 mResultPromise
->MaybeResolve(aResult
);
721 mResultPromise
= nullptr;
724 already_AddRefed
<Promise
> PaymentRequest::Show(
725 const Optional
<OwningNonNull
<Promise
>>& aDetailsPromise
, ErrorResult
& aRv
) {
726 if (!InFullyActiveDocument()) {
727 aRv
.ThrowAbortError("The owner document is not fully active");
731 nsIGlobalObject
* global
= GetOwnerGlobal();
732 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryInterface(global
);
733 Document
* doc
= win
->GetExtantDoc();
735 if (!UserActivation::IsHandlingUserInput()) {
736 nsString msg
= nsLiteralString(
737 u
"User activation is now required to call PaymentRequest.show()");
738 nsContentUtils::ReportToConsoleNonLocalized(
739 msg
, nsIScriptError::warningFlag
, "Security"_ns
, doc
);
740 if (StaticPrefs::dom_payments_request_user_interaction_required()) {
741 aRv
.ThrowSecurityError(NS_ConvertUTF16toUTF8(msg
));
746 if (mState
!= eCreated
) {
747 aRv
.ThrowInvalidStateError(
748 "The PaymentRequest's state should be 'Created'");
752 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
758 if (aDetailsPromise
.WasPassed()) {
759 aDetailsPromise
.Value().AppendNativeHandler(this);
763 RefPtr
<PaymentRequestManager
> manager
= PaymentRequestManager::GetSingleton();
765 manager
->ShowPayment(this, aRv
);
771 mAcceptPromise
= promise
;
772 mState
= eInteractive
;
773 return promise
.forget();
776 void PaymentRequest::RejectShowPayment(ErrorResult
&& aRejectReason
) {
777 MOZ_ASSERT(mAcceptPromise
|| mResponse
);
778 MOZ_ASSERT(mState
== eInteractive
);
781 mResponse
->RejectRetry(std::move(aRejectReason
));
783 mAcceptPromise
->MaybeReject(std::move(aRejectReason
));
786 mAcceptPromise
= nullptr;
789 void PaymentRequest::RespondShowPayment(const nsAString
& aMethodName
,
790 const ResponseData
& aDetails
,
791 const nsAString
& aPayerName
,
792 const nsAString
& aPayerEmail
,
793 const nsAString
& aPayerPhone
,
794 ErrorResult
&& aResult
) {
795 MOZ_ASSERT(mState
== eInteractive
);
797 if (aResult
.Failed()) {
798 RejectShowPayment(std::move(aResult
));
802 // https://github.com/w3c/payment-request/issues/692
803 mShippingAddress
.swap(mFullShippingAddress
);
804 mFullShippingAddress
= nullptr;
807 mResponse
->RespondRetry(aMethodName
, mShippingOption
, mShippingAddress
,
808 aDetails
, aPayerName
, aPayerEmail
, aPayerPhone
);
809 } else if (mAcceptPromise
) {
810 RefPtr
<PaymentResponse
> paymentResponse
= new PaymentResponse(
811 GetOwner(), this, mId
, aMethodName
, mShippingOption
, mShippingAddress
,
812 aDetails
, aPayerName
, aPayerEmail
, aPayerPhone
);
813 mResponse
= paymentResponse
;
814 mAcceptPromise
->MaybeResolve(paymentResponse
);
816 // mAccpetPromise could be nulled through document activity changed. And
817 // there is nothing to do here.
823 mAcceptPromise
= nullptr;
826 void PaymentRequest::RespondComplete() {
827 MOZ_ASSERT(mResponse
);
828 mResponse
->RespondComplete();
831 already_AddRefed
<Promise
> PaymentRequest::Abort(ErrorResult
& aRv
) {
832 if (!InFullyActiveDocument()) {
833 aRv
.ThrowAbortError("The owner document is not fully active");
837 if (mState
!= eInteractive
) {
838 aRv
.ThrowSecurityError(
839 "The PaymentRequest's state should be 'Interactive'");
844 aRv
.ThrowInvalidStateError(
845 "PaymentRequest.abort() has already been called");
849 nsIGlobalObject
* global
= GetOwnerGlobal();
850 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
855 RefPtr
<PaymentRequestManager
> manager
= PaymentRequestManager::GetSingleton();
857 manager
->AbortPayment(this, aRv
);
862 mAbortPromise
= promise
;
863 return promise
.forget();
866 void PaymentRequest::RespondAbortPayment(bool aSuccess
) {
867 // Check whether we are aborting the update:
869 // - If |mUpdateError| is failed, we are aborting the update as
870 // |mUpdateError| was set in method |AbortUpdate|.
871 // => Reject |mAcceptPromise| and reset |mUpdateError| to complete
872 // the action, regardless of |aSuccess|.
874 // - Otherwise, we are handling |Abort| method call from merchant.
875 // => Resolve/Reject |mAbortPromise| based on |aSuccess|.
876 if (mUpdateError
.Failed()) {
877 // Respond show with mUpdateError, set mUpdating to false.
879 RespondShowPayment(u
""_ns
, ResponseData(), u
""_ns
, u
""_ns
, u
""_ns
,
880 std::move(mUpdateError
));
884 if (mState
!= eInteractive
) {
890 mAbortPromise
->MaybeResolve(JS::UndefinedHandleValue
);
891 mAbortPromise
= nullptr;
892 ErrorResult abortResult
;
893 abortResult
.ThrowAbortError("The PaymentRequest is aborted");
894 RejectShowPayment(std::move(abortResult
));
896 mAbortPromise
->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR
);
897 mAbortPromise
= nullptr;
902 void PaymentRequest::UpdatePayment(JSContext
* aCx
,
903 const PaymentDetailsUpdate
& aDetails
,
906 if (mState
!= eInteractive
) {
907 aRv
.ThrowInvalidStateError(
908 "The PaymentRequest state should be 'Interactive'");
911 RefPtr
<PaymentRequestManager
> manager
= PaymentRequestManager::GetSingleton();
913 manager
->UpdatePayment(aCx
, this, aDetails
, mRequestShipping
, aRv
);
916 void PaymentRequest::AbortUpdate(ErrorResult
& aReason
) {
917 // AbortUpdate has the responsiblity to call aReason.SuppressException() when
920 MOZ_ASSERT(aReason
.Failed());
922 // Completely ignoring the call when the owner document is not fully active.
923 if (!InFullyActiveDocument()) {
924 aReason
.SuppressException();
928 // Completely ignoring the call when the PaymentRequest state is not
930 if (mState
!= eInteractive
) {
931 aReason
.SuppressException();
934 // Try to close down any remaining user interface. Should recevie
935 // RespondAbortPayment from chrome process.
936 // Completely ignoring the call when failed to send action to chrome process.
937 RefPtr
<PaymentRequestManager
> manager
= PaymentRequestManager::GetSingleton();
939 IgnoredErrorResult result
;
940 manager
->AbortPayment(this, result
);
941 if (result
.Failed()) {
942 aReason
.SuppressException();
946 // Remember update error |aReason| and do the following steps in
947 // RespondShowPayment.
948 // 1. Set target.state to closed
949 // 2. Reject the promise target.acceptPromise with exception "aRv"
950 // 3. Abort the algorithm with update error
951 mUpdateError
= std::move(aReason
);
954 void PaymentRequest::RetryPayment(JSContext
* aCx
,
955 const PaymentValidationErrors
& aErrors
,
957 if (mState
== eInteractive
) {
958 aRv
.ThrowInvalidStateError(
959 "Call Retry() when the PaymentReqeust state is 'Interactive'");
962 RefPtr
<PaymentRequestManager
> manager
= PaymentRequestManager::GetSingleton();
964 manager
->RetryPayment(aCx
, this, aErrors
, aRv
);
968 mState
= eInteractive
;
971 void PaymentRequest::GetId(nsAString
& aRetVal
) const { aRetVal
= mId
; }
973 void PaymentRequest::GetInternalId(nsAString
& aRetVal
) {
974 aRetVal
= mInternalId
;
977 void PaymentRequest::SetId(const nsAString
& aId
) { mId
= aId
; }
979 bool PaymentRequest::Equals(const nsAString
& aInternalId
) const {
980 return mInternalId
.Equals(aInternalId
);
983 bool PaymentRequest::ReadyForUpdate() {
984 return mState
== eInteractive
&& !mUpdating
;
987 void PaymentRequest::SetUpdating(bool aUpdating
) { mUpdating
= aUpdating
; }
989 already_AddRefed
<PaymentResponse
> PaymentRequest::GetResponse() const {
990 RefPtr
<PaymentResponse
> response
= mResponse
;
991 return response
.forget();
994 nsresult
PaymentRequest::DispatchUpdateEvent(const nsAString
& aType
) {
995 MOZ_ASSERT(ReadyForUpdate());
997 PaymentRequestUpdateEventInit init
;
998 init
.mBubbles
= false;
999 init
.mCancelable
= false;
1001 RefPtr
<PaymentRequestUpdateEvent
> event
=
1002 PaymentRequestUpdateEvent::Constructor(this, aType
, init
);
1003 event
->SetTrusted(true);
1004 event
->SetRequest(this);
1007 DispatchEvent(*event
, rv
);
1008 return rv
.StealNSResult();
1011 nsresult
PaymentRequest::DispatchMerchantValidationEvent(
1012 const nsAString
& aType
) {
1013 MOZ_ASSERT(ReadyForUpdate());
1015 MerchantValidationEventInit init
;
1016 init
.mBubbles
= false;
1017 init
.mCancelable
= false;
1018 init
.mValidationURL
.Truncate();
1021 RefPtr
<MerchantValidationEvent
> event
=
1022 MerchantValidationEvent::Constructor(this, aType
, init
, rv
);
1024 return rv
.StealNSResult();
1026 event
->SetTrusted(true);
1027 event
->SetRequest(this);
1029 DispatchEvent(*event
, rv
);
1030 return rv
.StealNSResult();
1033 nsresult
PaymentRequest::DispatchPaymentMethodChangeEvent(
1034 const nsAString
& aMethodName
, const ChangeDetails
& aMethodDetails
) {
1035 MOZ_ASSERT(ReadyForUpdate());
1037 PaymentRequestUpdateEventInit init
;
1038 init
.mBubbles
= false;
1039 init
.mCancelable
= false;
1041 RefPtr
<PaymentMethodChangeEvent
> event
=
1042 PaymentMethodChangeEvent::Constructor(this, u
"paymentmethodchange"_ns
,
1043 init
, aMethodName
, aMethodDetails
);
1044 event
->SetTrusted(true);
1045 event
->SetRequest(this);
1048 DispatchEvent(*event
, rv
);
1049 return rv
.StealNSResult();
1052 already_AddRefed
<PaymentAddress
> PaymentRequest::GetShippingAddress() const {
1053 RefPtr
<PaymentAddress
> address
= mShippingAddress
;
1054 return address
.forget();
1057 nsresult
PaymentRequest::UpdateShippingAddress(
1058 const nsAString
& aCountry
, const nsTArray
<nsString
>& aAddressLine
,
1059 const nsAString
& aRegion
, const nsAString
& aRegionCode
,
1060 const nsAString
& aCity
, const nsAString
& aDependentLocality
,
1061 const nsAString
& aPostalCode
, const nsAString
& aSortingCode
,
1062 const nsAString
& aOrganization
, const nsAString
& aRecipient
,
1063 const nsAString
& aPhone
) {
1064 nsTArray
<nsString
> emptyArray
;
1065 mShippingAddress
= new PaymentAddress(
1066 GetOwner(), aCountry
, emptyArray
, aRegion
, aRegionCode
, aCity
,
1067 aDependentLocality
, aPostalCode
, aSortingCode
, u
""_ns
, u
""_ns
, u
""_ns
);
1068 mFullShippingAddress
=
1069 new PaymentAddress(GetOwner(), aCountry
, aAddressLine
, aRegion
,
1070 aRegionCode
, aCity
, aDependentLocality
, aPostalCode
,
1071 aSortingCode
, aOrganization
, aRecipient
, aPhone
);
1072 // Fire shippingaddresschange event
1073 return DispatchUpdateEvent(u
"shippingaddresschange"_ns
);
1076 void PaymentRequest::SetShippingOption(const nsAString
& aShippingOption
) {
1077 mShippingOption
= aShippingOption
;
1080 void PaymentRequest::GetShippingOption(nsAString
& aRetVal
) const {
1081 aRetVal
= mShippingOption
;
1084 nsresult
PaymentRequest::UpdateShippingOption(
1085 const nsAString
& aShippingOption
) {
1086 mShippingOption
= aShippingOption
;
1088 // Fire shippingaddresschange event
1089 return DispatchUpdateEvent(u
"shippingoptionchange"_ns
);
1092 nsresult
PaymentRequest::UpdatePaymentMethod(
1093 const nsAString
& aMethodName
, const ChangeDetails
& aMethodDetails
) {
1094 return DispatchPaymentMethodChangeEvent(aMethodName
, aMethodDetails
);
1097 void PaymentRequest::SetShippingType(
1098 const Nullable
<PaymentShippingType
>& aShippingType
) {
1099 mShippingType
= aShippingType
;
1102 Nullable
<PaymentShippingType
> PaymentRequest::GetShippingType() const {
1103 return mShippingType
;
1106 void PaymentRequest::GetOptions(PaymentOptions
& aRetVal
) const {
1110 void PaymentRequest::SetOptions(const PaymentOptions
& aOptions
) {
1111 mOptions
= aOptions
;
1114 void PaymentRequest::ResolvedCallback(JSContext
* aCx
,
1115 JS::Handle
<JS::Value
> aValue
,
1117 if (!InFullyActiveDocument()) {
1123 if (NS_WARN_IF(!aValue
.isObject())) {
1128 // Converting value to a PaymentDetailsUpdate dictionary
1129 RootedDictionary
<PaymentDetailsUpdate
> details(aCx
);
1130 if (!details
.Init(aCx
, aValue
)) {
1131 result
.StealExceptionFromJSContext(aCx
);
1132 AbortUpdate(result
);
1136 IsValidDetailsUpdate(details
, mRequestShipping
, result
);
1137 if (result
.Failed()) {
1138 AbortUpdate(result
);
1142 // Update the PaymentRequest with the new details
1143 UpdatePayment(aCx
, details
, result
);
1144 if (result
.Failed()) {
1145 AbortUpdate(result
);
1150 void PaymentRequest::RejectedCallback(JSContext
* aCx
,
1151 JS::Handle
<JS::Value
> aValue
,
1153 if (!InFullyActiveDocument()) {
1159 result
.ThrowAbortError(
1160 "Details promise for PaymentRequest.show() is rejected by merchant");
1161 AbortUpdate(result
);
1164 bool PaymentRequest::InFullyActiveDocument() {
1165 nsIGlobalObject
* global
= GetOwnerGlobal();
1170 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryInterface(global
);
1172 Document
* doc
= win
->GetExtantDoc();
1173 if (!doc
|| !doc
->IsCurrentActiveDocument()) {
1177 WindowContext
* winContext
= win
->GetWindowContext();
1182 while (winContext
) {
1183 if (!winContext
->IsCurrent()) {
1186 winContext
= winContext
->GetParentWindowContext();
1192 void PaymentRequest::RegisterActivityObserver() {
1193 if (nsPIDOMWindowInner
* window
= GetOwner()) {
1194 mDocument
= window
->GetExtantDoc();
1196 mDocument
->RegisterActivityObserver(
1197 NS_ISUPPORTS_CAST(nsIDocumentActivity
*, this));
1202 void PaymentRequest::UnregisterActivityObserver() {
1204 mDocument
->UnregisterActivityObserver(
1205 NS_ISUPPORTS_CAST(nsIDocumentActivity
*, this));
1209 void PaymentRequest::NotifyOwnerDocumentActivityChanged() {
1210 nsPIDOMWindowInner
* window
= GetOwner();
1211 NS_ENSURE_TRUE_VOID(window
);
1212 Document
* doc
= window
->GetExtantDoc();
1213 NS_ENSURE_TRUE_VOID(doc
);
1215 if (!InFullyActiveDocument()) {
1216 if (mState
== eInteractive
) {
1217 if (mAcceptPromise
) {
1218 mAcceptPromise
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
);
1219 mAcceptPromise
= nullptr;
1222 ErrorResult rejectReason
;
1223 rejectReason
.ThrowAbortError("The owner documnet is not fully active");
1224 mResponse
->RejectRetry(std::move(rejectReason
));
1226 if (mAbortPromise
) {
1227 mAbortPromise
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
);
1228 mAbortPromise
= nullptr;
1231 if (mState
== eCreated
) {
1232 if (mResultPromise
) {
1233 mResultPromise
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
);
1234 mResultPromise
= nullptr;
1237 RefPtr
<PaymentRequestManager
> mgr
= PaymentRequestManager::GetSingleton();
1238 mgr
->ClosePayment(this);
1242 PaymentRequest::~PaymentRequest() {
1243 // Suppress any pending unreported exception on mUpdateError. We don't use
1244 // IgnoredErrorResult for mUpdateError because that doesn't play very nice
1245 // with move assignment operators.
1246 mUpdateError
.SuppressException();
1249 // If we're being destroyed, the PaymentRequestManager isn't holding any
1250 // references to us and we can't be waiting for any replies.
1251 mIPC
->MaybeDelete(false);
1253 UnregisterActivityObserver();
1256 JSObject
* PaymentRequest::WrapObject(JSContext
* aCx
,
1257 JS::Handle
<JSObject
*> aGivenProto
) {
1258 return PaymentRequest_Binding::Wrap(aCx
, this, aGivenProto
);
1261 } // namespace mozilla::dom