tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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