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 "mozilla/StaticPrefs_dom.h"
8 #include "mozilla/dom/PaymentResponse.h"
9 #include "mozilla/dom/BasicCardPaymentBinding.h"
10 #include "mozilla/dom/PaymentRequestUpdateEvent.h"
11 #include "BasicCardPayment.h"
12 #include "PaymentAddress.h"
13 #include "PaymentRequest.h"
14 #include "PaymentRequestManager.h"
15 #include "PaymentRequestUtils.h"
16 #include "mozilla/EventStateManager.h"
18 namespace mozilla::dom
{
20 NS_IMPL_CYCLE_COLLECTION_CLASS(PaymentResponse
)
22 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PaymentResponse
,
24 // Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because
25 // DOMEventTargetHelper does it for us.
26 NS_IMPL_CYCLE_COLLECTION_TRACE_END
28 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PaymentResponse
,
30 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mShippingAddress
)
31 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise
)
32 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer
)
33 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
35 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PaymentResponse
,
37 NS_IMPL_CYCLE_COLLECTION_UNLINK(mShippingAddress
)
38 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise
)
39 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTimer
)
40 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
42 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PaymentResponse
)
43 NS_INTERFACE_MAP_ENTRY(nsITimerCallback
)
44 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
46 NS_IMPL_ADDREF_INHERITED(PaymentResponse
, DOMEventTargetHelper
)
47 NS_IMPL_RELEASE_INHERITED(PaymentResponse
, DOMEventTargetHelper
)
49 PaymentResponse::PaymentResponse(
50 nsPIDOMWindowInner
* aWindow
, PaymentRequest
* aRequest
,
51 const nsAString
& aRequestId
, const nsAString
& aMethodName
,
52 const nsAString
& aShippingOption
, PaymentAddress
* aShippingAddress
,
53 const ResponseData
& aDetails
, const nsAString
& aPayerName
,
54 const nsAString
& aPayerEmail
, const nsAString
& aPayerPhone
)
55 : DOMEventTargetHelper(aWindow
),
56 mCompleteCalled(false),
58 mRequestId(aRequestId
),
59 mMethodName(aMethodName
),
61 mShippingOption(aShippingOption
),
62 mPayerName(aPayerName
),
63 mPayerEmail(aPayerEmail
),
64 mPayerPhone(aPayerPhone
),
65 mShippingAddress(aShippingAddress
) {
66 // TODO: from https://github.com/w3c/browser-payment-api/issues/480
67 // Add payerGivenName + payerFamilyName to PaymentAddress
68 NS_NewTimerWithCallback(getter_AddRefs(mTimer
), this,
69 StaticPrefs::dom_payments_response_timeout(),
70 nsITimer::TYPE_ONE_SHOT
,
71 aWindow
->EventTargetFor(TaskCategory::Other
));
74 PaymentResponse::~PaymentResponse() = default;
76 JSObject
* PaymentResponse::WrapObject(JSContext
* aCx
,
77 JS::Handle
<JSObject
*> aGivenProto
) {
78 return PaymentResponse_Binding::Wrap(aCx
, this, aGivenProto
);
81 void PaymentResponse::GetRequestId(nsString
& aRetVal
) const {
85 void PaymentResponse::GetMethodName(nsString
& aRetVal
) const {
86 aRetVal
= mMethodName
;
89 void PaymentResponse::GetDetails(JSContext
* aCx
,
90 JS::MutableHandle
<JSObject
*> aRetVal
) const {
91 switch (mDetails
.type()) {
92 case ResponseData::GeneralResponse
: {
93 const GeneralData
& rawData
= mDetails
.generalData();
94 DeserializeToJSObject(rawData
.data
, aCx
, aRetVal
);
97 case ResponseData::BasicCardResponse
: {
98 const BasicCardData
& rawData
= mDetails
.basicCardData();
99 BasicCardResponse basicCardResponse
;
100 if (!rawData
.cardholderName
.IsEmpty()) {
101 basicCardResponse
.mCardholderName
= rawData
.cardholderName
;
103 basicCardResponse
.mCardNumber
= rawData
.cardNumber
;
104 if (!rawData
.expiryMonth
.IsEmpty()) {
105 basicCardResponse
.mExpiryMonth
= rawData
.expiryMonth
;
107 if (!rawData
.expiryYear
.IsEmpty()) {
108 basicCardResponse
.mExpiryYear
= rawData
.expiryYear
;
110 if (!rawData
.cardSecurityCode
.IsEmpty()) {
111 basicCardResponse
.mCardSecurityCode
= rawData
.cardSecurityCode
;
113 if (!rawData
.billingAddress
.country
.IsEmpty() ||
114 !rawData
.billingAddress
.addressLine
.IsEmpty() ||
115 !rawData
.billingAddress
.region
.IsEmpty() ||
116 !rawData
.billingAddress
.regionCode
.IsEmpty() ||
117 !rawData
.billingAddress
.city
.IsEmpty() ||
118 !rawData
.billingAddress
.dependentLocality
.IsEmpty() ||
119 !rawData
.billingAddress
.postalCode
.IsEmpty() ||
120 !rawData
.billingAddress
.sortingCode
.IsEmpty() ||
121 !rawData
.billingAddress
.organization
.IsEmpty() ||
122 !rawData
.billingAddress
.recipient
.IsEmpty() ||
123 !rawData
.billingAddress
.phone
.IsEmpty()) {
124 basicCardResponse
.mBillingAddress
= new PaymentAddress(
125 GetOwner(), rawData
.billingAddress
.country
,
126 rawData
.billingAddress
.addressLine
, rawData
.billingAddress
.region
,
127 rawData
.billingAddress
.regionCode
, rawData
.billingAddress
.city
,
128 rawData
.billingAddress
.dependentLocality
,
129 rawData
.billingAddress
.postalCode
,
130 rawData
.billingAddress
.sortingCode
,
131 rawData
.billingAddress
.organization
,
132 rawData
.billingAddress
.recipient
, rawData
.billingAddress
.phone
);
135 JS::Rooted
<JS::Value
> value(aCx
);
136 if (NS_WARN_IF(!basicCardResponse
.ToObjectInternal(aCx
, &value
))) {
139 aRetVal
.set(&value
.toObject());
149 void PaymentResponse::GetShippingOption(nsString
& aRetVal
) const {
150 aRetVal
= mShippingOption
;
153 void PaymentResponse::GetPayerName(nsString
& aRetVal
) const {
154 aRetVal
= mPayerName
;
157 void PaymentResponse::GetPayerEmail(nsString
& aRetVal
) const {
158 aRetVal
= mPayerEmail
;
161 void PaymentResponse::GetPayerPhone(nsString
& aRetVal
) const {
162 aRetVal
= mPayerPhone
;
166 // Return a raw pointer here to avoid refcounting, but make sure it's safe
167 // (the object should be kept alive by the callee).
168 already_AddRefed
<PaymentAddress
> PaymentResponse::GetShippingAddress() const {
169 RefPtr
<PaymentAddress
> address
= mShippingAddress
;
170 return address
.forget();
173 already_AddRefed
<Promise
> PaymentResponse::Complete(PaymentComplete result
,
175 MOZ_ASSERT(mRequest
);
176 if (!mRequest
->InFullyActiveDocument()) {
177 aRv
.ThrowAbortError("The owner document is not fully active");
181 if (mCompleteCalled
) {
182 aRv
.ThrowInvalidStateError(
183 "PaymentResponse.complete() has already been called");
187 mCompleteCalled
= true;
194 RefPtr
<PaymentRequestManager
> manager
= PaymentRequestManager::GetSingleton();
196 manager
->CompletePayment(mRequest
, result
, aRv
);
201 if (NS_WARN_IF(!GetOwner())) {
202 aRv
.ThrowAbortError("Global object should exist");
206 nsIGlobalObject
* global
= GetOwner()->AsGlobal();
207 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
213 return promise
.forget();
216 void PaymentResponse::RespondComplete() {
217 // mPromise may be null when timing out
219 mPromise
->MaybeResolve(JS::UndefinedHandleValue
);
224 already_AddRefed
<Promise
> PaymentResponse::Retry(
225 JSContext
* aCx
, const PaymentValidationErrors
& aErrors
, ErrorResult
& aRv
) {
226 MOZ_ASSERT(mRequest
);
227 if (!mRequest
->InFullyActiveDocument()) {
228 aRv
.ThrowAbortError("The owner document is not fully active");
232 nsIGlobalObject
* global
= GetOwner()->AsGlobal();
233 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
243 if (mCompleteCalled
|| mRetryPromise
) {
244 aRv
.ThrowInvalidStateError(
245 "PaymentResponse.complete() has already been called");
250 aRv
.ThrowInvalidStateError("Is retrying the PaymentRequest");
254 ValidatePaymentValidationErrors(aErrors
, aRv
);
259 // Depending on the PMI, try to do IDL type conversion
260 // (e.g., basic-card expects at BasicCardErrors dictionary)
261 ConvertPaymentMethodErrors(aCx
, aErrors
, aRv
);
266 MOZ_ASSERT(mRequest
);
267 mRequest
->RetryPayment(aCx
, aErrors
, aRv
);
272 mRetryPromise
= promise
;
273 return promise
.forget();
276 void PaymentResponse::RespondRetry(const nsAString
& aMethodName
,
277 const nsAString
& aShippingOption
,
278 PaymentAddress
* aShippingAddress
,
279 const ResponseData
& aDetails
,
280 const nsAString
& aPayerName
,
281 const nsAString
& aPayerEmail
,
282 const nsAString
& aPayerPhone
) {
283 // mRetryPromise could be nulled when document activity is changed.
284 if (!mRetryPromise
) {
287 mMethodName
= aMethodName
;
288 mShippingOption
= aShippingOption
;
289 mShippingAddress
= aShippingAddress
;
291 mPayerName
= aPayerName
;
292 mPayerEmail
= aPayerEmail
;
293 mPayerPhone
= aPayerPhone
;
295 if (NS_WARN_IF(!GetOwner())) {
299 NS_NewTimerWithCallback(getter_AddRefs(mTimer
), this,
300 StaticPrefs::dom_payments_response_timeout(),
301 nsITimer::TYPE_ONE_SHOT
,
302 GetOwner()->EventTargetFor(TaskCategory::Other
));
303 MOZ_ASSERT(mRetryPromise
);
304 mRetryPromise
->MaybeResolve(JS::UndefinedHandleValue
);
305 mRetryPromise
= nullptr;
308 void PaymentResponse::RejectRetry(ErrorResult
&& aRejectReason
) {
309 MOZ_ASSERT(mRetryPromise
);
310 mRetryPromise
->MaybeReject(std::move(aRejectReason
));
311 mRetryPromise
= nullptr;
314 void PaymentResponse::ConvertPaymentMethodErrors(
315 JSContext
* aCx
, const PaymentValidationErrors
& aErrors
,
316 ErrorResult
& aRv
) const {
318 if (!aErrors
.mPaymentMethod
.WasPassed()) {
321 RefPtr
<BasicCardService
> service
= BasicCardService::GetService();
323 if (service
->IsBasicCardPayment(mMethodName
)) {
324 MOZ_ASSERT(aErrors
.mPaymentMethod
.Value(),
325 "The IDL says this is not nullable!");
326 service
->CheckForValidBasicCardErrors(aCx
, aErrors
.mPaymentMethod
.Value(),
331 void PaymentResponse::ValidatePaymentValidationErrors(
332 const PaymentValidationErrors
& aErrors
, ErrorResult
& aRv
) {
333 // Should not be empty errors
334 // check PaymentValidationErrors.error
335 if (aErrors
.mError
.WasPassed() && !aErrors
.mError
.Value().IsEmpty()) {
338 // check PaymentValidationErrors.payer
339 if (aErrors
.mPayer
.WasPassed()) {
340 PayerErrors
payerErrors(aErrors
.mPayer
.Value());
341 if (payerErrors
.mName
.WasPassed() && !payerErrors
.mName
.Value().IsEmpty()) {
344 if (payerErrors
.mEmail
.WasPassed() &&
345 !payerErrors
.mEmail
.Value().IsEmpty()) {
348 if (payerErrors
.mPhone
.WasPassed() &&
349 !payerErrors
.mPhone
.Value().IsEmpty()) {
353 // check PaymentValidationErrors.paymentMethod
354 if (aErrors
.mPaymentMethod
.WasPassed()) {
357 // check PaymentValidationErrors.shippingAddress
358 if (aErrors
.mShippingAddress
.WasPassed()) {
359 AddressErrors
addErrors(aErrors
.mShippingAddress
.Value());
360 if (addErrors
.mAddressLine
.WasPassed() &&
361 !addErrors
.mAddressLine
.Value().IsEmpty()) {
364 if (addErrors
.mCity
.WasPassed() && !addErrors
.mCity
.Value().IsEmpty()) {
367 if (addErrors
.mCountry
.WasPassed() &&
368 !addErrors
.mCountry
.Value().IsEmpty()) {
371 if (addErrors
.mDependentLocality
.WasPassed() &&
372 !addErrors
.mDependentLocality
.Value().IsEmpty()) {
375 if (addErrors
.mOrganization
.WasPassed() &&
376 !addErrors
.mOrganization
.Value().IsEmpty()) {
379 if (addErrors
.mPhone
.WasPassed() && !addErrors
.mPhone
.Value().IsEmpty()) {
382 if (addErrors
.mPostalCode
.WasPassed() &&
383 !addErrors
.mPostalCode
.Value().IsEmpty()) {
386 if (addErrors
.mRecipient
.WasPassed() &&
387 !addErrors
.mRecipient
.Value().IsEmpty()) {
390 if (addErrors
.mRegion
.WasPassed() && !addErrors
.mRegion
.Value().IsEmpty()) {
393 if (addErrors
.mRegionCode
.WasPassed() &&
394 !addErrors
.mRegionCode
.Value().IsEmpty()) {
397 if (addErrors
.mSortingCode
.WasPassed() &&
398 !addErrors
.mSortingCode
.Value().IsEmpty()) {
402 aRv
.ThrowAbortError("PaymentValidationErrors can not be an empty error");
406 PaymentResponse::Notify(nsITimer
* timer
) {
409 if (!mRequest
->InFullyActiveDocument()) {
413 if (mCompleteCalled
) {
417 mCompleteCalled
= true;
419 RefPtr
<PaymentRequestManager
> manager
= PaymentRequestManager::GetSingleton();
420 if (NS_WARN_IF(!manager
)) {
421 return NS_ERROR_FAILURE
;
423 manager
->CompletePayment(mRequest
, PaymentComplete::Unknown
, IgnoreErrors(),
428 nsresult
PaymentResponse::UpdatePayerDetail(const nsAString
& aPayerName
,
429 const nsAString
& aPayerEmail
,
430 const nsAString
& aPayerPhone
) {
431 MOZ_ASSERT(mRequest
->ReadyForUpdate());
432 PaymentOptions options
;
433 mRequest
->GetOptions(options
);
434 if (options
.mRequestPayerName
) {
435 mPayerName
= aPayerName
;
437 if (options
.mRequestPayerEmail
) {
438 mPayerEmail
= aPayerEmail
;
440 if (options
.mRequestPayerPhone
) {
441 mPayerPhone
= aPayerPhone
;
443 return DispatchUpdateEvent(u
"payerdetailchange"_ns
);
446 nsresult
PaymentResponse::DispatchUpdateEvent(const nsAString
& aType
) {
447 PaymentRequestUpdateEventInit init
;
448 RefPtr
<PaymentRequestUpdateEvent
> event
=
449 PaymentRequestUpdateEvent::Constructor(this, aType
, init
);
450 event
->SetTrusted(true);
451 event
->SetRequest(mRequest
);
454 DispatchEvent(*event
, rv
);
455 return rv
.StealNSResult();
458 } // namespace mozilla::dom