PaymentResponse.cpp (14803B)
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/. */ 6 7 #include "mozilla/dom/PaymentResponse.h" 8 9 #include "BasicCardPayment.h" 10 #include "PaymentAddress.h" 11 #include "PaymentRequest.h" 12 #include "PaymentRequestManager.h" 13 #include "PaymentRequestUtils.h" 14 #include "mozilla/EventStateManager.h" 15 #include "mozilla/StaticPrefs_dom.h" 16 #include "mozilla/dom/BasicCardPaymentBinding.h" 17 #include "mozilla/dom/PaymentRequestUpdateEvent.h" 18 #include "nsGlobalWindowInner.h" 19 20 namespace mozilla::dom { 21 22 NS_IMPL_CYCLE_COLLECTION_CLASS(PaymentResponse) 23 24 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PaymentResponse, 25 DOMEventTargetHelper) 26 // Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because 27 // DOMEventTargetHelper does it for us. 28 NS_IMPL_CYCLE_COLLECTION_TRACE_END 29 30 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PaymentResponse, 31 DOMEventTargetHelper) 32 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mShippingAddress) 33 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise) 34 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer) 35 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 36 37 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PaymentResponse, 38 DOMEventTargetHelper) 39 NS_IMPL_CYCLE_COLLECTION_UNLINK(mShippingAddress) 40 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise) 41 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTimer) 42 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 43 44 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PaymentResponse) 45 NS_INTERFACE_MAP_ENTRY(nsITimerCallback) 46 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 47 48 NS_IMPL_ADDREF_INHERITED(PaymentResponse, DOMEventTargetHelper) 49 NS_IMPL_RELEASE_INHERITED(PaymentResponse, DOMEventTargetHelper) 50 51 PaymentResponse::PaymentResponse( 52 nsPIDOMWindowInner* aWindow, PaymentRequest* aRequest, 53 const nsAString& aRequestId, const nsAString& aMethodName, 54 const nsAString& aShippingOption, PaymentAddress* aShippingAddress, 55 const ResponseData& aDetails, const nsAString& aPayerName, 56 const nsAString& aPayerEmail, const nsAString& aPayerPhone) 57 : DOMEventTargetHelper(aWindow), 58 mCompleteCalled(false), 59 mRequest(aRequest), 60 mRequestId(aRequestId), 61 mMethodName(aMethodName), 62 mDetails(aDetails), 63 mShippingOption(aShippingOption), 64 mPayerName(aPayerName), 65 mPayerEmail(aPayerEmail), 66 mPayerPhone(aPayerPhone), 67 mShippingAddress(aShippingAddress) { 68 // TODO: from https://github.com/w3c/browser-payment-api/issues/480 69 // Add payerGivenName + payerFamilyName to PaymentAddress 70 NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, 71 StaticPrefs::dom_payments_response_timeout(), 72 nsITimer::TYPE_ONE_SHOT, 73 GetMainThreadSerialEventTarget()); 74 } 75 76 PaymentResponse::~PaymentResponse() = default; 77 78 JSObject* PaymentResponse::WrapObject(JSContext* aCx, 79 JS::Handle<JSObject*> aGivenProto) { 80 return PaymentResponse_Binding::Wrap(aCx, this, aGivenProto); 81 } 82 83 void PaymentResponse::GetRequestId(nsString& aRetVal) const { 84 aRetVal = mRequestId; 85 } 86 87 void PaymentResponse::GetMethodName(nsString& aRetVal) const { 88 aRetVal = mMethodName; 89 } 90 91 void PaymentResponse::GetDetails(JSContext* aCx, 92 JS::MutableHandle<JSObject*> aRetVal) const { 93 switch (mDetails.type()) { 94 case ResponseData::GeneralResponse: { 95 const GeneralData& rawData = mDetails.generalData(); 96 DeserializeToJSObject(rawData.data, aCx, aRetVal); 97 break; 98 } 99 case ResponseData::BasicCardResponse: { 100 const BasicCardData& rawData = mDetails.basicCardData(); 101 BasicCardResponse basicCardResponse; 102 if (!rawData.cardholderName.IsEmpty()) { 103 basicCardResponse.mCardholderName = rawData.cardholderName; 104 } 105 basicCardResponse.mCardNumber = rawData.cardNumber; 106 if (!rawData.expiryMonth.IsEmpty()) { 107 basicCardResponse.mExpiryMonth = rawData.expiryMonth; 108 } 109 if (!rawData.expiryYear.IsEmpty()) { 110 basicCardResponse.mExpiryYear = rawData.expiryYear; 111 } 112 if (!rawData.cardSecurityCode.IsEmpty()) { 113 basicCardResponse.mCardSecurityCode = rawData.cardSecurityCode; 114 } 115 if (!rawData.billingAddress.country.IsEmpty() || 116 !rawData.billingAddress.addressLine.IsEmpty() || 117 !rawData.billingAddress.region.IsEmpty() || 118 !rawData.billingAddress.regionCode.IsEmpty() || 119 !rawData.billingAddress.city.IsEmpty() || 120 !rawData.billingAddress.dependentLocality.IsEmpty() || 121 !rawData.billingAddress.postalCode.IsEmpty() || 122 !rawData.billingAddress.sortingCode.IsEmpty() || 123 !rawData.billingAddress.organization.IsEmpty() || 124 !rawData.billingAddress.recipient.IsEmpty() || 125 !rawData.billingAddress.phone.IsEmpty()) { 126 basicCardResponse.mBillingAddress = new PaymentAddress( 127 GetOwnerWindow(), rawData.billingAddress.country, 128 rawData.billingAddress.addressLine, rawData.billingAddress.region, 129 rawData.billingAddress.regionCode, rawData.billingAddress.city, 130 rawData.billingAddress.dependentLocality, 131 rawData.billingAddress.postalCode, 132 rawData.billingAddress.sortingCode, 133 rawData.billingAddress.organization, 134 rawData.billingAddress.recipient, rawData.billingAddress.phone); 135 } 136 MOZ_ASSERT(aCx); 137 JS::Rooted<JS::Value> value(aCx); 138 if (NS_WARN_IF(!basicCardResponse.ToObjectInternal(aCx, &value))) { 139 return; 140 } 141 aRetVal.set(&value.toObject()); 142 break; 143 } 144 default: { 145 MOZ_ASSERT(false); 146 break; 147 } 148 } 149 } 150 151 void PaymentResponse::GetShippingOption(nsString& aRetVal) const { 152 aRetVal = mShippingOption; 153 } 154 155 void PaymentResponse::GetPayerName(nsString& aRetVal) const { 156 aRetVal = mPayerName; 157 } 158 159 void PaymentResponse::GetPayerEmail(nsString& aRetVal) const { 160 aRetVal = mPayerEmail; 161 } 162 163 void PaymentResponse::GetPayerPhone(nsString& aRetVal) const { 164 aRetVal = mPayerPhone; 165 } 166 167 // TODO: 168 // Return a raw pointer here to avoid refcounting, but make sure it's safe 169 // (the object should be kept alive by the callee). 170 already_AddRefed<PaymentAddress> PaymentResponse::GetShippingAddress() const { 171 RefPtr<PaymentAddress> address = mShippingAddress; 172 return address.forget(); 173 } 174 175 already_AddRefed<Promise> PaymentResponse::Complete(PaymentComplete result, 176 ErrorResult& aRv) { 177 MOZ_ASSERT(mRequest); 178 if (!mRequest->InFullyActiveDocument()) { 179 aRv.ThrowAbortError("The owner document is not fully active"); 180 return nullptr; 181 } 182 183 if (mCompleteCalled) { 184 aRv.ThrowInvalidStateError( 185 "PaymentResponse.complete() has already been called"); 186 return nullptr; 187 } 188 189 mCompleteCalled = true; 190 191 if (mTimer) { 192 mTimer->Cancel(); 193 mTimer = nullptr; 194 } 195 196 RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton(); 197 MOZ_ASSERT(manager); 198 manager->CompletePayment(mRequest, result, aRv); 199 if (aRv.Failed()) { 200 return nullptr; 201 } 202 203 RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv); 204 if (aRv.Failed()) { 205 return nullptr; 206 } 207 208 mPromise = promise; 209 return promise.forget(); 210 } 211 212 void PaymentResponse::RespondComplete() { 213 // mPromise may be null when timing out 214 if (mPromise) { 215 mPromise->MaybeResolve(JS::UndefinedHandleValue); 216 mPromise = nullptr; 217 } 218 } 219 220 already_AddRefed<Promise> PaymentResponse::Retry( 221 JSContext* aCx, const PaymentValidationErrors& aErrors, ErrorResult& aRv) { 222 MOZ_ASSERT(mRequest); 223 if (!mRequest->InFullyActiveDocument()) { 224 aRv.ThrowAbortError("The owner document is not fully active"); 225 return nullptr; 226 } 227 228 RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv); 229 if (aRv.Failed()) { 230 return nullptr; 231 } 232 233 if (mTimer) { 234 mTimer->Cancel(); 235 mTimer = nullptr; 236 } 237 238 if (mCompleteCalled || mRetryPromise) { 239 aRv.ThrowInvalidStateError( 240 "PaymentResponse.complete() has already been called"); 241 return nullptr; 242 } 243 244 if (mRetryPromise) { 245 aRv.ThrowInvalidStateError("Is retrying the PaymentRequest"); 246 return nullptr; 247 } 248 249 ValidatePaymentValidationErrors(aErrors, aRv); 250 if (aRv.Failed()) { 251 return nullptr; 252 } 253 254 // Depending on the PMI, try to do IDL type conversion 255 // (e.g., basic-card expects at BasicCardErrors dictionary) 256 ConvertPaymentMethodErrors(aCx, aErrors, aRv); 257 if (aRv.Failed()) { 258 return nullptr; 259 } 260 261 MOZ_ASSERT(mRequest); 262 mRequest->RetryPayment(aCx, aErrors, aRv); 263 if (aRv.Failed()) { 264 return nullptr; 265 } 266 267 mRetryPromise = promise; 268 return promise.forget(); 269 } 270 271 void PaymentResponse::RespondRetry(const nsAString& aMethodName, 272 const nsAString& aShippingOption, 273 PaymentAddress* aShippingAddress, 274 const ResponseData& aDetails, 275 const nsAString& aPayerName, 276 const nsAString& aPayerEmail, 277 const nsAString& aPayerPhone) { 278 // mRetryPromise could be nulled when document activity is changed. 279 if (!mRetryPromise) { 280 return; 281 } 282 mMethodName = aMethodName; 283 mShippingOption = aShippingOption; 284 mShippingAddress = aShippingAddress; 285 mDetails = aDetails; 286 mPayerName = aPayerName; 287 mPayerEmail = aPayerEmail; 288 mPayerPhone = aPayerPhone; 289 290 if (NS_WARN_IF(!GetOwnerGlobal())) { 291 return; 292 } 293 294 NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, 295 StaticPrefs::dom_payments_response_timeout(), 296 nsITimer::TYPE_ONE_SHOT, 297 GetMainThreadSerialEventTarget()); 298 MOZ_ASSERT(mRetryPromise); 299 mRetryPromise->MaybeResolve(JS::UndefinedHandleValue); 300 mRetryPromise = nullptr; 301 } 302 303 void PaymentResponse::RejectRetry(ErrorResult&& aRejectReason) { 304 MOZ_ASSERT(mRetryPromise); 305 mRetryPromise->MaybeReject(std::move(aRejectReason)); 306 mRetryPromise = nullptr; 307 } 308 309 void PaymentResponse::ConvertPaymentMethodErrors( 310 JSContext* aCx, const PaymentValidationErrors& aErrors, 311 ErrorResult& aRv) const { 312 MOZ_ASSERT(aCx); 313 if (!aErrors.mPaymentMethod.WasPassed()) { 314 return; 315 } 316 RefPtr<BasicCardService> service = BasicCardService::GetService(); 317 MOZ_ASSERT(service); 318 if (service->IsBasicCardPayment(mMethodName)) { 319 MOZ_ASSERT(aErrors.mPaymentMethod.Value(), 320 "The IDL says this is not nullable!"); 321 service->CheckForValidBasicCardErrors(aCx, aErrors.mPaymentMethod.Value(), 322 aRv); 323 } 324 } 325 326 void PaymentResponse::ValidatePaymentValidationErrors( 327 const PaymentValidationErrors& aErrors, ErrorResult& aRv) { 328 // Should not be empty errors 329 // check PaymentValidationErrors.error 330 if (aErrors.mError.WasPassed() && !aErrors.mError.Value().IsEmpty()) { 331 return; 332 } 333 // check PaymentValidationErrors.payer 334 if (aErrors.mPayer.WasPassed()) { 335 PayerErrors payerErrors(aErrors.mPayer.Value()); 336 if (payerErrors.mName.WasPassed() && !payerErrors.mName.Value().IsEmpty()) { 337 return; 338 } 339 if (payerErrors.mEmail.WasPassed() && 340 !payerErrors.mEmail.Value().IsEmpty()) { 341 return; 342 } 343 if (payerErrors.mPhone.WasPassed() && 344 !payerErrors.mPhone.Value().IsEmpty()) { 345 return; 346 } 347 } 348 // check PaymentValidationErrors.paymentMethod 349 if (aErrors.mPaymentMethod.WasPassed()) { 350 return; 351 } 352 // check PaymentValidationErrors.shippingAddress 353 if (aErrors.mShippingAddress.WasPassed()) { 354 AddressErrors addErrors(aErrors.mShippingAddress.Value()); 355 if (addErrors.mAddressLine.WasPassed() && 356 !addErrors.mAddressLine.Value().IsEmpty()) { 357 return; 358 } 359 if (addErrors.mCity.WasPassed() && !addErrors.mCity.Value().IsEmpty()) { 360 return; 361 } 362 if (addErrors.mCountry.WasPassed() && 363 !addErrors.mCountry.Value().IsEmpty()) { 364 return; 365 } 366 if (addErrors.mDependentLocality.WasPassed() && 367 !addErrors.mDependentLocality.Value().IsEmpty()) { 368 return; 369 } 370 if (addErrors.mOrganization.WasPassed() && 371 !addErrors.mOrganization.Value().IsEmpty()) { 372 return; 373 } 374 if (addErrors.mPhone.WasPassed() && !addErrors.mPhone.Value().IsEmpty()) { 375 return; 376 } 377 if (addErrors.mPostalCode.WasPassed() && 378 !addErrors.mPostalCode.Value().IsEmpty()) { 379 return; 380 } 381 if (addErrors.mRecipient.WasPassed() && 382 !addErrors.mRecipient.Value().IsEmpty()) { 383 return; 384 } 385 if (addErrors.mRegion.WasPassed() && !addErrors.mRegion.Value().IsEmpty()) { 386 return; 387 } 388 if (addErrors.mRegionCode.WasPassed() && 389 !addErrors.mRegionCode.Value().IsEmpty()) { 390 return; 391 } 392 if (addErrors.mSortingCode.WasPassed() && 393 !addErrors.mSortingCode.Value().IsEmpty()) { 394 return; 395 } 396 } 397 aRv.ThrowAbortError("PaymentValidationErrors can not be an empty error"); 398 } 399 400 NS_IMETHODIMP 401 PaymentResponse::Notify(nsITimer* timer) { 402 mTimer = nullptr; 403 404 if (!mRequest->InFullyActiveDocument()) { 405 return NS_OK; 406 } 407 408 if (mCompleteCalled) { 409 return NS_OK; 410 } 411 412 mCompleteCalled = true; 413 414 RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton(); 415 if (NS_WARN_IF(!manager)) { 416 return NS_ERROR_FAILURE; 417 } 418 manager->CompletePayment(mRequest, PaymentComplete::Unknown, IgnoreErrors(), 419 true); 420 return NS_OK; 421 } 422 423 nsresult PaymentResponse::UpdatePayerDetail(const nsAString& aPayerName, 424 const nsAString& aPayerEmail, 425 const nsAString& aPayerPhone) { 426 MOZ_ASSERT(mRequest->ReadyForUpdate()); 427 PaymentOptions options; 428 mRequest->GetOptions(options); 429 if (options.mRequestPayerName) { 430 mPayerName = aPayerName; 431 } 432 if (options.mRequestPayerEmail) { 433 mPayerEmail = aPayerEmail; 434 } 435 if (options.mRequestPayerPhone) { 436 mPayerPhone = aPayerPhone; 437 } 438 return DispatchUpdateEvent(u"payerdetailchange"_ns); 439 } 440 441 nsresult PaymentResponse::DispatchUpdateEvent(const nsAString& aType) { 442 PaymentRequestUpdateEventInit init; 443 RefPtr<PaymentRequestUpdateEvent> event = 444 PaymentRequestUpdateEvent::Constructor(this, aType, init); 445 event->SetTrusted(true); 446 event->SetRequest(mRequest); 447 448 ErrorResult rv; 449 DispatchEvent(*event, rv); 450 return rv.StealNSResult(); 451 } 452 453 } // namespace mozilla::dom