Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / dom / payments / PaymentResponse.cpp
blob1f107b07470e47a699fa3140915f6ca6ed41090b
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,
23 DOMEventTargetHelper)
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,
29 DOMEventTargetHelper)
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,
36 DOMEventTargetHelper)
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),
57 mRequest(aRequest),
58 mRequestId(aRequestId),
59 mMethodName(aMethodName),
60 mDetails(aDetails),
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 {
82 aRetVal = mRequestId;
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);
95 break;
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);
134 MOZ_ASSERT(aCx);
135 JS::Rooted<JS::Value> value(aCx);
136 if (NS_WARN_IF(!basicCardResponse.ToObjectInternal(aCx, &value))) {
137 return;
139 aRetVal.set(&value.toObject());
140 break;
142 default: {
143 MOZ_ASSERT(false);
144 break;
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;
165 // TODO:
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,
174 ErrorResult& aRv) {
175 MOZ_ASSERT(mRequest);
176 if (!mRequest->InFullyActiveDocument()) {
177 aRv.ThrowAbortError("The owner document is not fully active");
178 return nullptr;
181 if (mCompleteCalled) {
182 aRv.ThrowInvalidStateError(
183 "PaymentResponse.complete() has already been called");
184 return nullptr;
187 mCompleteCalled = true;
189 if (mTimer) {
190 mTimer->Cancel();
191 mTimer = nullptr;
194 RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
195 MOZ_ASSERT(manager);
196 manager->CompletePayment(mRequest, result, aRv);
197 if (aRv.Failed()) {
198 return nullptr;
201 if (NS_WARN_IF(!GetOwner())) {
202 aRv.ThrowAbortError("Global object should exist");
203 return nullptr;
206 nsIGlobalObject* global = GetOwner()->AsGlobal();
207 RefPtr<Promise> promise = Promise::Create(global, aRv);
208 if (aRv.Failed()) {
209 return nullptr;
212 mPromise = promise;
213 return promise.forget();
216 void PaymentResponse::RespondComplete() {
217 // mPromise may be null when timing out
218 if (mPromise) {
219 mPromise->MaybeResolve(JS::UndefinedHandleValue);
220 mPromise = nullptr;
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");
229 return nullptr;
232 nsIGlobalObject* global = GetOwner()->AsGlobal();
233 RefPtr<Promise> promise = Promise::Create(global, aRv);
234 if (aRv.Failed()) {
235 return nullptr;
238 if (mTimer) {
239 mTimer->Cancel();
240 mTimer = nullptr;
243 if (mCompleteCalled || mRetryPromise) {
244 aRv.ThrowInvalidStateError(
245 "PaymentResponse.complete() has already been called");
246 return nullptr;
249 if (mRetryPromise) {
250 aRv.ThrowInvalidStateError("Is retrying the PaymentRequest");
251 return nullptr;
254 ValidatePaymentValidationErrors(aErrors, aRv);
255 if (aRv.Failed()) {
256 return nullptr;
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);
262 if (aRv.Failed()) {
263 return nullptr;
266 MOZ_ASSERT(mRequest);
267 mRequest->RetryPayment(aCx, aErrors, aRv);
268 if (aRv.Failed()) {
269 return nullptr;
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) {
285 return;
287 mMethodName = aMethodName;
288 mShippingOption = aShippingOption;
289 mShippingAddress = aShippingAddress;
290 mDetails = aDetails;
291 mPayerName = aPayerName;
292 mPayerEmail = aPayerEmail;
293 mPayerPhone = aPayerPhone;
295 if (NS_WARN_IF(!GetOwner())) {
296 return;
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 {
317 MOZ_ASSERT(aCx);
318 if (!aErrors.mPaymentMethod.WasPassed()) {
319 return;
321 RefPtr<BasicCardService> service = BasicCardService::GetService();
322 MOZ_ASSERT(service);
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(),
327 aRv);
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()) {
336 return;
338 // check PaymentValidationErrors.payer
339 if (aErrors.mPayer.WasPassed()) {
340 PayerErrors payerErrors(aErrors.mPayer.Value());
341 if (payerErrors.mName.WasPassed() && !payerErrors.mName.Value().IsEmpty()) {
342 return;
344 if (payerErrors.mEmail.WasPassed() &&
345 !payerErrors.mEmail.Value().IsEmpty()) {
346 return;
348 if (payerErrors.mPhone.WasPassed() &&
349 !payerErrors.mPhone.Value().IsEmpty()) {
350 return;
353 // check PaymentValidationErrors.paymentMethod
354 if (aErrors.mPaymentMethod.WasPassed()) {
355 return;
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()) {
362 return;
364 if (addErrors.mCity.WasPassed() && !addErrors.mCity.Value().IsEmpty()) {
365 return;
367 if (addErrors.mCountry.WasPassed() &&
368 !addErrors.mCountry.Value().IsEmpty()) {
369 return;
371 if (addErrors.mDependentLocality.WasPassed() &&
372 !addErrors.mDependentLocality.Value().IsEmpty()) {
373 return;
375 if (addErrors.mOrganization.WasPassed() &&
376 !addErrors.mOrganization.Value().IsEmpty()) {
377 return;
379 if (addErrors.mPhone.WasPassed() && !addErrors.mPhone.Value().IsEmpty()) {
380 return;
382 if (addErrors.mPostalCode.WasPassed() &&
383 !addErrors.mPostalCode.Value().IsEmpty()) {
384 return;
386 if (addErrors.mRecipient.WasPassed() &&
387 !addErrors.mRecipient.Value().IsEmpty()) {
388 return;
390 if (addErrors.mRegion.WasPassed() && !addErrors.mRegion.Value().IsEmpty()) {
391 return;
393 if (addErrors.mRegionCode.WasPassed() &&
394 !addErrors.mRegionCode.Value().IsEmpty()) {
395 return;
397 if (addErrors.mSortingCode.WasPassed() &&
398 !addErrors.mSortingCode.Value().IsEmpty()) {
399 return;
402 aRv.ThrowAbortError("PaymentValidationErrors can not be an empty error");
405 NS_IMETHODIMP
406 PaymentResponse::Notify(nsITimer* timer) {
407 mTimer = nullptr;
409 if (!mRequest->InFullyActiveDocument()) {
410 return NS_OK;
413 if (mCompleteCalled) {
414 return NS_OK;
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(),
424 true);
425 return NS_OK;
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);
453 ErrorResult rv;
454 DispatchEvent(*event, rv);
455 return rv.StealNSResult();
458 } // namespace mozilla::dom