Bug 1902759 - When a new tab is opened, TalkBack focus is shifted to the new tab...
[gecko.git] / dom / payments / PaymentResponse.cpp
blob6813b28788ec82b01a9c9904c1113ad23639eeb1
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,
24 DOMEventTargetHelper)
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,
30 DOMEventTargetHelper)
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,
37 DOMEventTargetHelper)
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),
58 mRequest(aRequest),
59 mRequestId(aRequestId),
60 mMethodName(aMethodName),
61 mDetails(aDetails),
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 {
83 aRetVal = mRequestId;
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);
96 break;
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);
135 MOZ_ASSERT(aCx);
136 JS::Rooted<JS::Value> value(aCx);
137 if (NS_WARN_IF(!basicCardResponse.ToObjectInternal(aCx, &value))) {
138 return;
140 aRetVal.set(&value.toObject());
141 break;
143 default: {
144 MOZ_ASSERT(false);
145 break;
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;
166 // TODO:
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,
175 ErrorResult& aRv) {
176 MOZ_ASSERT(mRequest);
177 if (!mRequest->InFullyActiveDocument()) {
178 aRv.ThrowAbortError("The owner document is not fully active");
179 return nullptr;
182 if (mCompleteCalled) {
183 aRv.ThrowInvalidStateError(
184 "PaymentResponse.complete() has already been called");
185 return nullptr;
188 mCompleteCalled = true;
190 if (mTimer) {
191 mTimer->Cancel();
192 mTimer = nullptr;
195 RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
196 MOZ_ASSERT(manager);
197 manager->CompletePayment(mRequest, result, aRv);
198 if (aRv.Failed()) {
199 return nullptr;
202 RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv);
203 if (aRv.Failed()) {
204 return nullptr;
207 mPromise = promise;
208 return promise.forget();
211 void PaymentResponse::RespondComplete() {
212 // mPromise may be null when timing out
213 if (mPromise) {
214 mPromise->MaybeResolve(JS::UndefinedHandleValue);
215 mPromise = nullptr;
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");
224 return nullptr;
227 RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv);
228 if (aRv.Failed()) {
229 return nullptr;
232 if (mTimer) {
233 mTimer->Cancel();
234 mTimer = nullptr;
237 if (mCompleteCalled || mRetryPromise) {
238 aRv.ThrowInvalidStateError(
239 "PaymentResponse.complete() has already been called");
240 return nullptr;
243 if (mRetryPromise) {
244 aRv.ThrowInvalidStateError("Is retrying the PaymentRequest");
245 return nullptr;
248 ValidatePaymentValidationErrors(aErrors, aRv);
249 if (aRv.Failed()) {
250 return nullptr;
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);
256 if (aRv.Failed()) {
257 return nullptr;
260 MOZ_ASSERT(mRequest);
261 mRequest->RetryPayment(aCx, aErrors, aRv);
262 if (aRv.Failed()) {
263 return nullptr;
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) {
279 return;
281 mMethodName = aMethodName;
282 mShippingOption = aShippingOption;
283 mShippingAddress = aShippingAddress;
284 mDetails = aDetails;
285 mPayerName = aPayerName;
286 mPayerEmail = aPayerEmail;
287 mPayerPhone = aPayerPhone;
289 if (NS_WARN_IF(!GetOwnerGlobal())) {
290 return;
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 {
311 MOZ_ASSERT(aCx);
312 if (!aErrors.mPaymentMethod.WasPassed()) {
313 return;
315 RefPtr<BasicCardService> service = BasicCardService::GetService();
316 MOZ_ASSERT(service);
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(),
321 aRv);
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()) {
330 return;
332 // check PaymentValidationErrors.payer
333 if (aErrors.mPayer.WasPassed()) {
334 PayerErrors payerErrors(aErrors.mPayer.Value());
335 if (payerErrors.mName.WasPassed() && !payerErrors.mName.Value().IsEmpty()) {
336 return;
338 if (payerErrors.mEmail.WasPassed() &&
339 !payerErrors.mEmail.Value().IsEmpty()) {
340 return;
342 if (payerErrors.mPhone.WasPassed() &&
343 !payerErrors.mPhone.Value().IsEmpty()) {
344 return;
347 // check PaymentValidationErrors.paymentMethod
348 if (aErrors.mPaymentMethod.WasPassed()) {
349 return;
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()) {
356 return;
358 if (addErrors.mCity.WasPassed() && !addErrors.mCity.Value().IsEmpty()) {
359 return;
361 if (addErrors.mCountry.WasPassed() &&
362 !addErrors.mCountry.Value().IsEmpty()) {
363 return;
365 if (addErrors.mDependentLocality.WasPassed() &&
366 !addErrors.mDependentLocality.Value().IsEmpty()) {
367 return;
369 if (addErrors.mOrganization.WasPassed() &&
370 !addErrors.mOrganization.Value().IsEmpty()) {
371 return;
373 if (addErrors.mPhone.WasPassed() && !addErrors.mPhone.Value().IsEmpty()) {
374 return;
376 if (addErrors.mPostalCode.WasPassed() &&
377 !addErrors.mPostalCode.Value().IsEmpty()) {
378 return;
380 if (addErrors.mRecipient.WasPassed() &&
381 !addErrors.mRecipient.Value().IsEmpty()) {
382 return;
384 if (addErrors.mRegion.WasPassed() && !addErrors.mRegion.Value().IsEmpty()) {
385 return;
387 if (addErrors.mRegionCode.WasPassed() &&
388 !addErrors.mRegionCode.Value().IsEmpty()) {
389 return;
391 if (addErrors.mSortingCode.WasPassed() &&
392 !addErrors.mSortingCode.Value().IsEmpty()) {
393 return;
396 aRv.ThrowAbortError("PaymentValidationErrors can not be an empty error");
399 NS_IMETHODIMP
400 PaymentResponse::Notify(nsITimer* timer) {
401 mTimer = nullptr;
403 if (!mRequest->InFullyActiveDocument()) {
404 return NS_OK;
407 if (mCompleteCalled) {
408 return NS_OK;
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(),
418 true);
419 return NS_OK;
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);
447 ErrorResult rv;
448 DispatchEvent(*event, rv);
449 return rv.StealNSResult();
452 } // namespace mozilla::dom