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 "nsGlobalWindowInner.h"
17 #include "mozilla/EventStateManager.h"
19 namespace mozilla::dom
{
21 NS_IMPL_CYCLE_COLLECTION_CLASS(PaymentResponse
)
23 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PaymentResponse
,
25 // Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because
26 // DOMEventTargetHelper does it for us.
27 NS_IMPL_CYCLE_COLLECTION_TRACE_END
29 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PaymentResponse
,
31 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mShippingAddress
)
32 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise
)
33 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer
)
34 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
36 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PaymentResponse
,
38 NS_IMPL_CYCLE_COLLECTION_UNLINK(mShippingAddress
)
39 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise
)
40 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTimer
)
41 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
43 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PaymentResponse
)
44 NS_INTERFACE_MAP_ENTRY(nsITimerCallback
)
45 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
47 NS_IMPL_ADDREF_INHERITED(PaymentResponse
, DOMEventTargetHelper
)
48 NS_IMPL_RELEASE_INHERITED(PaymentResponse
, DOMEventTargetHelper
)
50 PaymentResponse::PaymentResponse(
51 nsPIDOMWindowInner
* aWindow
, PaymentRequest
* aRequest
,
52 const nsAString
& aRequestId
, const nsAString
& aMethodName
,
53 const nsAString
& aShippingOption
, PaymentAddress
* aShippingAddress
,
54 const ResponseData
& aDetails
, const nsAString
& aPayerName
,
55 const nsAString
& aPayerEmail
, const nsAString
& aPayerPhone
)
56 : DOMEventTargetHelper(aWindow
),
57 mCompleteCalled(false),
59 mRequestId(aRequestId
),
60 mMethodName(aMethodName
),
62 mShippingOption(aShippingOption
),
63 mPayerName(aPayerName
),
64 mPayerEmail(aPayerEmail
),
65 mPayerPhone(aPayerPhone
),
66 mShippingAddress(aShippingAddress
) {
67 // TODO: from https://github.com/w3c/browser-payment-api/issues/480
68 // Add payerGivenName + payerFamilyName to PaymentAddress
69 NS_NewTimerWithCallback(getter_AddRefs(mTimer
), this,
70 StaticPrefs::dom_payments_response_timeout(),
71 nsITimer::TYPE_ONE_SHOT
,
72 GetMainThreadSerialEventTarget());
75 PaymentResponse::~PaymentResponse() = default;
77 JSObject
* PaymentResponse::WrapObject(JSContext
* aCx
,
78 JS::Handle
<JSObject
*> aGivenProto
) {
79 return PaymentResponse_Binding::Wrap(aCx
, this, aGivenProto
);
82 void PaymentResponse::GetRequestId(nsString
& aRetVal
) const {
86 void PaymentResponse::GetMethodName(nsString
& aRetVal
) const {
87 aRetVal
= mMethodName
;
90 void PaymentResponse::GetDetails(JSContext
* aCx
,
91 JS::MutableHandle
<JSObject
*> aRetVal
) const {
92 switch (mDetails
.type()) {
93 case ResponseData::GeneralResponse
: {
94 const GeneralData
& rawData
= mDetails
.generalData();
95 DeserializeToJSObject(rawData
.data
, aCx
, aRetVal
);
98 case ResponseData::BasicCardResponse
: {
99 const BasicCardData
& rawData
= mDetails
.basicCardData();
100 BasicCardResponse basicCardResponse
;
101 if (!rawData
.cardholderName
.IsEmpty()) {
102 basicCardResponse
.mCardholderName
= rawData
.cardholderName
;
104 basicCardResponse
.mCardNumber
= rawData
.cardNumber
;
105 if (!rawData
.expiryMonth
.IsEmpty()) {
106 basicCardResponse
.mExpiryMonth
= rawData
.expiryMonth
;
108 if (!rawData
.expiryYear
.IsEmpty()) {
109 basicCardResponse
.mExpiryYear
= rawData
.expiryYear
;
111 if (!rawData
.cardSecurityCode
.IsEmpty()) {
112 basicCardResponse
.mCardSecurityCode
= rawData
.cardSecurityCode
;
114 if (!rawData
.billingAddress
.country
.IsEmpty() ||
115 !rawData
.billingAddress
.addressLine
.IsEmpty() ||
116 !rawData
.billingAddress
.region
.IsEmpty() ||
117 !rawData
.billingAddress
.regionCode
.IsEmpty() ||
118 !rawData
.billingAddress
.city
.IsEmpty() ||
119 !rawData
.billingAddress
.dependentLocality
.IsEmpty() ||
120 !rawData
.billingAddress
.postalCode
.IsEmpty() ||
121 !rawData
.billingAddress
.sortingCode
.IsEmpty() ||
122 !rawData
.billingAddress
.organization
.IsEmpty() ||
123 !rawData
.billingAddress
.recipient
.IsEmpty() ||
124 !rawData
.billingAddress
.phone
.IsEmpty()) {
125 basicCardResponse
.mBillingAddress
= new PaymentAddress(
126 GetOwnerWindow(), rawData
.billingAddress
.country
,
127 rawData
.billingAddress
.addressLine
, rawData
.billingAddress
.region
,
128 rawData
.billingAddress
.regionCode
, rawData
.billingAddress
.city
,
129 rawData
.billingAddress
.dependentLocality
,
130 rawData
.billingAddress
.postalCode
,
131 rawData
.billingAddress
.sortingCode
,
132 rawData
.billingAddress
.organization
,
133 rawData
.billingAddress
.recipient
, rawData
.billingAddress
.phone
);
136 JS::Rooted
<JS::Value
> value(aCx
);
137 if (NS_WARN_IF(!basicCardResponse
.ToObjectInternal(aCx
, &value
))) {
140 aRetVal
.set(&value
.toObject());
150 void PaymentResponse::GetShippingOption(nsString
& aRetVal
) const {
151 aRetVal
= mShippingOption
;
154 void PaymentResponse::GetPayerName(nsString
& aRetVal
) const {
155 aRetVal
= mPayerName
;
158 void PaymentResponse::GetPayerEmail(nsString
& aRetVal
) const {
159 aRetVal
= mPayerEmail
;
162 void PaymentResponse::GetPayerPhone(nsString
& aRetVal
) const {
163 aRetVal
= mPayerPhone
;
167 // Return a raw pointer here to avoid refcounting, but make sure it's safe
168 // (the object should be kept alive by the callee).
169 already_AddRefed
<PaymentAddress
> PaymentResponse::GetShippingAddress() const {
170 RefPtr
<PaymentAddress
> address
= mShippingAddress
;
171 return address
.forget();
174 already_AddRefed
<Promise
> PaymentResponse::Complete(PaymentComplete result
,
176 MOZ_ASSERT(mRequest
);
177 if (!mRequest
->InFullyActiveDocument()) {
178 aRv
.ThrowAbortError("The owner document is not fully active");
182 if (mCompleteCalled
) {
183 aRv
.ThrowInvalidStateError(
184 "PaymentResponse.complete() has already been called");
188 mCompleteCalled
= true;
195 RefPtr
<PaymentRequestManager
> manager
= PaymentRequestManager::GetSingleton();
197 manager
->CompletePayment(mRequest
, result
, aRv
);
202 RefPtr
<Promise
> promise
= Promise::Create(GetOwnerGlobal(), aRv
);
208 return promise
.forget();
211 void PaymentResponse::RespondComplete() {
212 // mPromise may be null when timing out
214 mPromise
->MaybeResolve(JS::UndefinedHandleValue
);
219 already_AddRefed
<Promise
> PaymentResponse::Retry(
220 JSContext
* aCx
, const PaymentValidationErrors
& aErrors
, ErrorResult
& aRv
) {
221 MOZ_ASSERT(mRequest
);
222 if (!mRequest
->InFullyActiveDocument()) {
223 aRv
.ThrowAbortError("The owner document is not fully active");
227 RefPtr
<Promise
> promise
= Promise::Create(GetOwnerGlobal(), aRv
);
237 if (mCompleteCalled
|| mRetryPromise
) {
238 aRv
.ThrowInvalidStateError(
239 "PaymentResponse.complete() has already been called");
244 aRv
.ThrowInvalidStateError("Is retrying the PaymentRequest");
248 ValidatePaymentValidationErrors(aErrors
, aRv
);
253 // Depending on the PMI, try to do IDL type conversion
254 // (e.g., basic-card expects at BasicCardErrors dictionary)
255 ConvertPaymentMethodErrors(aCx
, aErrors
, aRv
);
260 MOZ_ASSERT(mRequest
);
261 mRequest
->RetryPayment(aCx
, aErrors
, aRv
);
266 mRetryPromise
= promise
;
267 return promise
.forget();
270 void PaymentResponse::RespondRetry(const nsAString
& aMethodName
,
271 const nsAString
& aShippingOption
,
272 PaymentAddress
* aShippingAddress
,
273 const ResponseData
& aDetails
,
274 const nsAString
& aPayerName
,
275 const nsAString
& aPayerEmail
,
276 const nsAString
& aPayerPhone
) {
277 // mRetryPromise could be nulled when document activity is changed.
278 if (!mRetryPromise
) {
281 mMethodName
= aMethodName
;
282 mShippingOption
= aShippingOption
;
283 mShippingAddress
= aShippingAddress
;
285 mPayerName
= aPayerName
;
286 mPayerEmail
= aPayerEmail
;
287 mPayerPhone
= aPayerPhone
;
289 if (NS_WARN_IF(!GetOwnerGlobal())) {
293 NS_NewTimerWithCallback(getter_AddRefs(mTimer
), this,
294 StaticPrefs::dom_payments_response_timeout(),
295 nsITimer::TYPE_ONE_SHOT
,
296 GetMainThreadSerialEventTarget());
297 MOZ_ASSERT(mRetryPromise
);
298 mRetryPromise
->MaybeResolve(JS::UndefinedHandleValue
);
299 mRetryPromise
= nullptr;
302 void PaymentResponse::RejectRetry(ErrorResult
&& aRejectReason
) {
303 MOZ_ASSERT(mRetryPromise
);
304 mRetryPromise
->MaybeReject(std::move(aRejectReason
));
305 mRetryPromise
= nullptr;
308 void PaymentResponse::ConvertPaymentMethodErrors(
309 JSContext
* aCx
, const PaymentValidationErrors
& aErrors
,
310 ErrorResult
& aRv
) const {
312 if (!aErrors
.mPaymentMethod
.WasPassed()) {
315 RefPtr
<BasicCardService
> service
= BasicCardService::GetService();
317 if (service
->IsBasicCardPayment(mMethodName
)) {
318 MOZ_ASSERT(aErrors
.mPaymentMethod
.Value(),
319 "The IDL says this is not nullable!");
320 service
->CheckForValidBasicCardErrors(aCx
, aErrors
.mPaymentMethod
.Value(),
325 void PaymentResponse::ValidatePaymentValidationErrors(
326 const PaymentValidationErrors
& aErrors
, ErrorResult
& aRv
) {
327 // Should not be empty errors
328 // check PaymentValidationErrors.error
329 if (aErrors
.mError
.WasPassed() && !aErrors
.mError
.Value().IsEmpty()) {
332 // check PaymentValidationErrors.payer
333 if (aErrors
.mPayer
.WasPassed()) {
334 PayerErrors
payerErrors(aErrors
.mPayer
.Value());
335 if (payerErrors
.mName
.WasPassed() && !payerErrors
.mName
.Value().IsEmpty()) {
338 if (payerErrors
.mEmail
.WasPassed() &&
339 !payerErrors
.mEmail
.Value().IsEmpty()) {
342 if (payerErrors
.mPhone
.WasPassed() &&
343 !payerErrors
.mPhone
.Value().IsEmpty()) {
347 // check PaymentValidationErrors.paymentMethod
348 if (aErrors
.mPaymentMethod
.WasPassed()) {
351 // check PaymentValidationErrors.shippingAddress
352 if (aErrors
.mShippingAddress
.WasPassed()) {
353 AddressErrors
addErrors(aErrors
.mShippingAddress
.Value());
354 if (addErrors
.mAddressLine
.WasPassed() &&
355 !addErrors
.mAddressLine
.Value().IsEmpty()) {
358 if (addErrors
.mCity
.WasPassed() && !addErrors
.mCity
.Value().IsEmpty()) {
361 if (addErrors
.mCountry
.WasPassed() &&
362 !addErrors
.mCountry
.Value().IsEmpty()) {
365 if (addErrors
.mDependentLocality
.WasPassed() &&
366 !addErrors
.mDependentLocality
.Value().IsEmpty()) {
369 if (addErrors
.mOrganization
.WasPassed() &&
370 !addErrors
.mOrganization
.Value().IsEmpty()) {
373 if (addErrors
.mPhone
.WasPassed() && !addErrors
.mPhone
.Value().IsEmpty()) {
376 if (addErrors
.mPostalCode
.WasPassed() &&
377 !addErrors
.mPostalCode
.Value().IsEmpty()) {
380 if (addErrors
.mRecipient
.WasPassed() &&
381 !addErrors
.mRecipient
.Value().IsEmpty()) {
384 if (addErrors
.mRegion
.WasPassed() && !addErrors
.mRegion
.Value().IsEmpty()) {
387 if (addErrors
.mRegionCode
.WasPassed() &&
388 !addErrors
.mRegionCode
.Value().IsEmpty()) {
391 if (addErrors
.mSortingCode
.WasPassed() &&
392 !addErrors
.mSortingCode
.Value().IsEmpty()) {
396 aRv
.ThrowAbortError("PaymentValidationErrors can not be an empty error");
400 PaymentResponse::Notify(nsITimer
* timer
) {
403 if (!mRequest
->InFullyActiveDocument()) {
407 if (mCompleteCalled
) {
411 mCompleteCalled
= true;
413 RefPtr
<PaymentRequestManager
> manager
= PaymentRequestManager::GetSingleton();
414 if (NS_WARN_IF(!manager
)) {
415 return NS_ERROR_FAILURE
;
417 manager
->CompletePayment(mRequest
, PaymentComplete::Unknown
, IgnoreErrors(),
422 nsresult
PaymentResponse::UpdatePayerDetail(const nsAString
& aPayerName
,
423 const nsAString
& aPayerEmail
,
424 const nsAString
& aPayerPhone
) {
425 MOZ_ASSERT(mRequest
->ReadyForUpdate());
426 PaymentOptions options
;
427 mRequest
->GetOptions(options
);
428 if (options
.mRequestPayerName
) {
429 mPayerName
= aPayerName
;
431 if (options
.mRequestPayerEmail
) {
432 mPayerEmail
= aPayerEmail
;
434 if (options
.mRequestPayerPhone
) {
435 mPayerPhone
= aPayerPhone
;
437 return DispatchUpdateEvent(u
"payerdetailchange"_ns
);
440 nsresult
PaymentResponse::DispatchUpdateEvent(const nsAString
& aType
) {
441 PaymentRequestUpdateEventInit init
;
442 RefPtr
<PaymentRequestUpdateEvent
> event
=
443 PaymentRequestUpdateEvent::Constructor(this, aType
, init
);
444 event
->SetTrusted(true);
445 event
->SetRequest(mRequest
);
448 DispatchEvent(*event
, rv
);
449 return rv
.StealNSResult();
452 } // namespace mozilla::dom