Bug 1435161 - Part 2 supporting PaymentResponse.retry(). r=baku
authorEden Chuang <echuang@mozilla.com>
Tue, 04 Sep 2018 12:28:40 +0200
changeset 435586 e65bec7e01c82025e19af0f9c11a492788d816df
parent 435585 ead0d6dcd6f07f4722fb20c2eb4628840343c4d9
child 435587 d3e7d6d6e0d565564575071bfae0abd63e046c77
push id107686
push usercsabou@mozilla.com
push dateTue, 11 Sep 2018 07:12:36 +0000
treeherdermozilla-inbound@d3e7d6d6e0d5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1435161
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1435161 - Part 2 supporting PaymentResponse.retry(). r=baku 1. Add PaymentValidationErrors and PayerErrorFields in PaymentRequest.webidl and PaymentResponse.retry() in PaymentResponse.webidl 2. Implement PaymentRequest.retryPayment() and PaymentRequestManager.retryPayment() in content process. 3. Add IPCPaymentRetryActionRequest in PPaymentRequest.ipdl to transfer the error fields to chrome process. 4. Implement PaymentResponse.retry() by reusing the code of show() and updateWith() of PaymentRequestService in chrome process.
dom/interfaces/payments/nsIPaymentRequest.idl
dom/payments/PaymentRequest.cpp
dom/payments/PaymentRequest.h
dom/payments/PaymentRequestData.cpp
dom/payments/PaymentRequestData.h
dom/payments/PaymentRequestManager.cpp
dom/payments/PaymentRequestManager.h
dom/payments/PaymentRequestService.cpp
dom/payments/PaymentResponse.cpp
dom/payments/PaymentResponse.h
dom/payments/ipc/PPaymentRequest.ipdl
dom/payments/ipc/PaymentRequestParent.cpp
dom/webidl/PaymentRequest.webidl
dom/webidl/PaymentResponse.webidl
--- a/dom/interfaces/payments/nsIPaymentRequest.idl
+++ b/dom/interfaces/payments/nsIPaymentRequest.idl
@@ -58,16 +58,20 @@ interface nsIPaymentDetails : nsISupport
   readonly attribute AString id;
   readonly attribute nsIPaymentItem totalItem;
   readonly attribute nsIArray displayItems;
   readonly attribute nsIArray shippingOptions;
   readonly attribute nsIArray modifiers;
   readonly attribute AString error;
   [implicit_jscontext]
   readonly attribute jsval shippingAddressErrors;
+  [implicit_jscontext]
+  readonly attribute jsval payer;
+  [implicit_jscontext]
+  readonly attribute jsval paymentMethod;
 };
 
 [scriptable, builtinclass, uuid(d53f9f20-138e-47cc-9fd5-db16a3f6d301)]
 interface nsIPaymentOptions : nsISupports
 {
   readonly attribute boolean requestPayerName;
   readonly attribute boolean requestPayerEmail;
   readonly attribute boolean requestPayerPhone;
--- a/dom/payments/PaymentRequest.cpp
+++ b/dom/payments/PaymentRequest.cpp
@@ -740,50 +740,59 @@ PaymentRequest::Show(const Optional<Owni
   mAcceptPromise = promise;
   mState = eInteractive;
   return promise.forget();
 }
 
 void
 PaymentRequest::RejectShowPayment(nsresult aRejectReason)
 {
-  MOZ_ASSERT(mAcceptPromise);
+  MOZ_ASSERT(mAcceptPromise || mResponse);
   MOZ_ASSERT(mState == eInteractive);
 
-  mAcceptPromise->MaybeReject(aRejectReason);
+  if (mResponse) {
+    mResponse->RejectRetry(aRejectReason);
+  } else {
+    mAcceptPromise->MaybeReject(aRejectReason);
+  }
   mState = eClosed;
   mAcceptPromise = nullptr;
 }
 
 void
 PaymentRequest::RespondShowPayment(const nsAString& aMethodName,
                                    const nsAString& aDetails,
                                    const nsAString& aPayerName,
                                    const nsAString& aPayerEmail,
                                    const nsAString& aPayerPhone,
                                    nsresult aRv)
 {
-  MOZ_ASSERT(mAcceptPromise);
+  MOZ_ASSERT(mAcceptPromise || mResponse);
   MOZ_ASSERT(mState == eInteractive);
 
   if (NS_FAILED(aRv)) {
     RejectShowPayment(aRv);
     return;
   }
 
   // https://github.com/w3c/payment-request/issues/692
   mShippingAddress.swap(mFullShippingAddress);
   mFullShippingAddress = nullptr;
 
-  RefPtr<PaymentResponse> paymentResponse =
-    new PaymentResponse(GetOwner(), this, mId, aMethodName,
-                        mShippingOption, mShippingAddress, aDetails,
-                        aPayerName, aPayerEmail, aPayerPhone);
-  mResponse = paymentResponse;
-  mAcceptPromise->MaybeResolve(paymentResponse);
+  if (mResponse) {
+    mResponse->RespondRetry(aMethodName, mShippingOption, mShippingAddress,
+                            aDetails, aPayerName, aPayerEmail, aPayerPhone);
+  } else {
+    RefPtr<PaymentResponse> paymentResponse =
+      new PaymentResponse(GetOwner(), this, mId, aMethodName,
+                          mShippingOption, mShippingAddress, aDetails,
+                          aPayerName, aPayerEmail, aPayerPhone);
+    mResponse = paymentResponse;
+    mAcceptPromise->MaybeResolve(paymentResponse);
+  }
 
   mState = eClosed;
   mAcceptPromise = nullptr;
 }
 
 void
 PaymentRequest::RespondComplete()
 {
@@ -898,16 +907,32 @@ PaymentRequest::AbortUpdate(nsresult aRv
 
   // Remember update error |aRv| and do the following steps in RespondShowPayment.
   // 1. Set target.state to closed
   // 2. Reject the promise target.acceptPromise with exception "aRv"
   // 3. Abort the algorithm with update error
   mUpdateError = aRv;
 }
 
+nsresult
+PaymentRequest::RetryPayment(JSContext* aCx, const PaymentValidationErrors& aErrors)
+{
+  if (mState == eInteractive) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+  RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
+  MOZ_ASSERT(manager);
+  nsresult rv = manager->RetryPayment(aCx, this, aErrors);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  mState = eInteractive;
+  return NS_OK;
+}
+
 void
 PaymentRequest::GetId(nsAString& aRetVal) const
 {
   aRetVal = mId;
 }
 
 void
 PaymentRequest::GetInternalId(nsAString& aRetVal)
--- a/dom/payments/PaymentRequest.h
+++ b/dom/payments/PaymentRequest.h
@@ -104,16 +104,18 @@ public:
                           const nsAString& aPayerPhone,
                           nsresult aRv);
   void RejectShowPayment(nsresult aRejectReason);
   void RespondComplete();
 
   already_AddRefed<Promise> Abort(ErrorResult& aRv);
   void RespondAbortPayment(bool aResult);
 
+  nsresult RetryPayment(JSContext* aCx, const PaymentValidationErrors& aErrors);
+
   void GetId(nsAString& aRetVal) const;
   void GetInternalId(nsAString& aRetVal);
   void SetId(const nsAString& aId);
 
   bool Equals(const nsAString& aInternalId) const;
 
   bool ReadyForUpdate();
   bool IsUpdating() const { return mUpdating; }
--- a/dom/payments/PaymentRequestData.cpp
+++ b/dom/payments/PaymentRequestData.cpp
@@ -478,16 +478,41 @@ PaymentDetails::GetShippingAddressErrors
   AddressErrors errors;
   errors.Init(mShippingAddressErrors);
   if (!ToJSValue(aCx, errors, aErrors)) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PaymentDetails::GetPayer(JSContext* aCx, JS::MutableHandleValue aErrors)
+{
+  PayerErrorFields errors;
+  errors.Init(mPayerErrors);
+  if (!ToJSValue(aCx, errors, aErrors)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PaymentDetails::GetPaymentMethod(JSContext* aCx, JS::MutableHandleValue aErrors)
+{
+  if (mPaymentMethodErrors.IsEmpty()) {
+    aErrors.set(JS::NullValue());
+    return NS_OK;
+  }
+  nsresult rv = DeserializeToJSValue(mPaymentMethodErrors, aCx ,aErrors);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
+}
+
 nsresult
 PaymentDetails::Update(nsIPaymentDetails* aDetails, const bool aRequestShipping)
 {
   MOZ_ASSERT(aDetails);
   /*
    * According to the spec [1], update the attributes if they present in new
    * details (i.e., PaymentDetailsUpdate); otherwise, keep original value.
    * Note |id| comes only from initial details (i.e., PaymentDetailsInit) and
@@ -542,16 +567,29 @@ PaymentDetails::Update(nsIPaymentDetails
 }
 
 nsString
 PaymentDetails::GetShippingAddressErrors() const
 {
   return mShippingAddressErrors;
 }
 
+nsresult
+PaymentDetails::UpdateErrors(const nsAString& aError,
+                             const nsAString& aPayerErrors,
+                             const nsAString& aPaymentMethodErrors,
+                             const nsAString& aShippingAddressErrors)
+{
+  mError = aError;
+  mPayerErrors = aPayerErrors;
+  mPaymentMethodErrors = aPaymentMethodErrors;
+  mShippingAddressErrors = aShippingAddressErrors;
+  return NS_OK;
+}
+
 /* PaymentOptions */
 
 NS_IMPL_ISUPPORTS(PaymentOptions,
                   nsIPaymentOptions)
 
 PaymentOptions::PaymentOptions(const bool aRequestPayerName,
                                const bool aRequestPayerEmail,
                                const bool aRequestPayerPhone,
@@ -722,16 +760,30 @@ PaymentRequest::UpdatePaymentDetails(nsI
 }
 
 void
 PaymentRequest::SetCompleteStatus(const nsAString& aCompleteStatus)
 {
   mCompleteStatus = aCompleteStatus;
 }
 
+nsresult
+PaymentRequest::UpdateErrors(const nsAString& aError,
+                             const nsAString& aPayerErrors,
+                             const nsAString& aPaymentMethodErrors,
+                             const nsAString& aShippingAddressErrors)
+{
+  PaymentDetails* rowDetails = static_cast<PaymentDetails*>(mPaymentDetails.get());
+  MOZ_ASSERT(rowDetails);
+  return rowDetails->UpdateErrors(aError,
+                                  aPayerErrors,
+                                  aPaymentMethodErrors,
+                                  aShippingAddressErrors);
+}
+
 NS_IMETHODIMP
 PaymentRequest::GetCompleteStatus(nsAString& aCompleteStatus)
 {
   aCompleteStatus = mCompleteStatus;
   return NS_OK;
 }
 
 /* PaymentAddress */
--- a/dom/payments/PaymentRequestData.h
+++ b/dom/payments/PaymentRequestData.h
@@ -128,16 +128,21 @@ public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPAYMENTDETAILS
 
 
   static nsresult Create(const IPCPaymentDetails& aIPCDetails,
                          nsIPaymentDetails** aDetails);
   nsresult Update(nsIPaymentDetails* aDetails, const bool aRequestShipping);
   nsString GetShippingAddressErrors() const;
+  nsresult UpdateErrors(const nsAString& aError,
+                        const nsAString& aPayerErrors,
+                        const nsAString& aPaymentMethodErrors,
+                        const nsAString& aShippingAddressErrors);
+
 private:
   PaymentDetails(const nsAString& aId,
                  nsIPaymentItem* aTotalItem,
                  nsIArray* aDisplayItems,
                  nsIArray* aShippingOptions,
                  nsIArray* aModifiers,
                  const nsAString& aError,
                  const nsAString& aShippingAddressError);
@@ -146,16 +151,18 @@ private:
 
   nsString mId;
   nsCOMPtr<nsIPaymentItem> mTotalItem;
   nsCOMPtr<nsIArray> mDisplayItems;
   nsCOMPtr<nsIArray> mShippingOptions;
   nsCOMPtr<nsIArray> mModifiers;
   nsString mError;
   nsString mShippingAddressErrors;
+  nsString mPayerErrors;
+  nsString mPaymentMethodErrors;
 };
 
 class PaymentOptions final : public nsIPaymentOptions
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPAYMENTOPTIONS
 
@@ -203,16 +210,22 @@ public:
 
   nsresult
   UpdatePaymentDetails(nsIPaymentDetails* aPaymentDetails,
                        const nsAString& aShippingOption);
 
   void
   SetCompleteStatus(const nsAString& aCompleteStatus);
 
+  nsresult
+  UpdateErrors(const nsAString& aError,
+               const nsAString& aPayerErrors,
+               const nsAString& aPaymentMethodErrors,
+               const nsAString& aShippingAddressErrors);
+
 private:
   ~PaymentRequest() = default;
 
   uint64_t mTabId;
   nsString mRequestId;
   nsString mCompleteStatus;
   nsCOMPtr<nsIPrincipal> mTopLevelPrincipal;
   nsCOMPtr<nsIArray> mPaymentMethods;
--- a/dom/payments/PaymentRequestManager.cpp
+++ b/dom/payments/PaymentRequestManager.cpp
@@ -535,16 +535,54 @@ PaymentRequestManager::ClosePayment(Paym
   }
   nsAutoString requestId;
   aRequest->GetInternalId(requestId);
   IPCPaymentCloseActionRequest action(requestId);
   return SendRequestPayment(aRequest, action, false);
 }
 
 nsresult
+PaymentRequestManager::RetryPayment(JSContext* aCx,
+                                    PaymentRequest* aRequest,
+                                    const PaymentValidationErrors& aErrors)
+{
+  NS_ENSURE_ARG_POINTER(aCx);
+  NS_ENSURE_ARG_POINTER(aRequest);
+
+  nsAutoString requestId;
+  aRequest->GetInternalId(requestId);
+
+  nsAutoString error(EmptyString());
+  if (aErrors.mError.WasPassed()) {
+    error = aErrors.mError.Value();
+  }
+
+  nsAutoString shippingAddressErrors(EmptyString());
+  aErrors.mShippingAddress.ToJSON(shippingAddressErrors);
+
+  nsAutoString payerErrors(EmptyString());
+  aErrors.mPayer.ToJSON(payerErrors);
+
+  nsAutoString paymentMethodErrors(EmptyString());
+  if (aErrors.mPaymentMethod.WasPassed()) {
+    JS::RootedObject object(aCx, aErrors.mPaymentMethod.Value());
+    nsresult rv = SerializeFromJSObject(aCx, object, paymentMethodErrors);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+  IPCPaymentRetryActionRequest action(requestId,
+                                      error,
+                                      payerErrors,
+                                      paymentMethodErrors,
+                                      shippingAddressErrors);
+  return SendRequestPayment(aRequest, action);
+}
+
+nsresult
 PaymentRequestManager::RespondPayment(PaymentRequest* aRequest,
                                       const IPCPaymentActionResponse& aResponse)
 {
   switch (aResponse.type()) {
     case IPCPaymentActionResponse::TIPCPaymentCanMakeActionResponse: {
       const IPCPaymentCanMakeActionResponse& response = aResponse;
       aRequest->RespondCanMakePayment(response.result());
       NotifyRequestDone(aRequest);
--- a/dom/payments/PaymentRequestManager.h
+++ b/dom/payments/PaymentRequestManager.h
@@ -53,16 +53,19 @@ public:
                            const PaymentComplete& aComplete,
                            bool aTimedOut = false);
   nsresult UpdatePayment(JSContext* aCx,
                          PaymentRequest* aRequest,
                          const PaymentDetailsUpdate& aDetails,
                          bool aRequestShipping,
                          bool aDeferredShow);
   nsresult ClosePayment(PaymentRequest* aRequest);
+  nsresult RetryPayment(JSContext* aCx,
+                        PaymentRequest* aRequest,
+                        const PaymentValidationErrors& aErrors);
 
   nsresult RespondPayment(PaymentRequest* aRequest,
                           const IPCPaymentActionResponse& aResponse);
   nsresult ChangeShippingAddress(PaymentRequest* aRequest,
                                  const IPCPaymentAddress& aAddress);
   nsresult ChangeShippingOption(PaymentRequest* aRequest,
                                 const nsAString& aOption);
 
--- a/dom/payments/PaymentRequestService.cpp
+++ b/dom/payments/PaymentRequestService.cpp
@@ -373,16 +373,36 @@ PaymentRequestService::RequestPayment(co
         return rv;
       }
       if (mShowingRequest == payment) {
         mShowingRequest = nullptr;
       }
       mRequestQueue.RemoveElement(payment);
       break;
     }
+    case IPCPaymentActionRequest::TIPCPaymentRetryActionRequest: {
+      const IPCPaymentRetryActionRequest& action = aAction;
+      nsCOMPtr<nsIPaymentRequest> payment;
+      rv = GetPaymentRequestById(aRequestId, getter_AddRefs(payment));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      MOZ_ASSERT(payment);
+      payments::PaymentRequest* rowPayment =
+        static_cast<payments::PaymentRequest*>(payment.get());
+      MOZ_ASSERT(rowPayment);
+      rowPayment->UpdateErrors(action.error(),
+                               action.payerErrors(),
+                               action.paymentMethodErrors(),
+                               action.shippingAddressErrors());
+      MOZ_ASSERT(mShowingRequest == payment);
+      rv = LaunchUIAction(aRequestId,
+                          IPCPaymentActionRequest::TIPCPaymentUpdateActionRequest);
+      break;
+    }
     default: {
       return NS_ERROR_FAILURE;
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/dom/payments/PaymentResponse.cpp
+++ b/dom/payments/PaymentResponse.cpp
@@ -26,17 +26,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
 NS_INTERFACE_MAP_END
 
 PaymentResponse::PaymentResponse(nsPIDOMWindowInner* aWindow,
                                  PaymentRequest* aRequest,
                                  const nsAString& aRequestId,
                                  const nsAString& aMethodName,
                                  const nsAString& aShippingOption,
-                                 RefPtr<PaymentAddress> aShippingAddress,
+                                 PaymentAddress* aShippingAddress,
                                  const nsAString& aDetails,
                                  const nsAString& aPayerName,
                                  const nsAString& aPayerEmail,
                                  const nsAString& aPayerPhone)
   : mOwner(aWindow)
   , mCompleteCalled(false)
   , mRequest(aRequest)
   , mRequestId(aRequestId)
@@ -178,16 +178,166 @@ PaymentResponse::RespondComplete()
 {
   // mPromise may be null when timing out
   if (mPromise) {
     mPromise->MaybeResolve(JS::UndefinedHandleValue);
     mPromise = nullptr;
   }
 }
 
+already_AddRefed<Promise>
+PaymentResponse::Retry(JSContext* aCx,
+                       const PaymentValidationErrors& aErrors,
+                       ErrorResult& aRv)
+{
+  nsIGlobalObject* global = mOwner->AsGlobal();
+  ErrorResult errResult;
+  RefPtr<Promise> promise = Promise::Create(global, errResult);
+  if (errResult.Failed()) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  nsIDocument* doc = mOwner->GetExtantDoc();
+  if (!doc || !doc->IsCurrentActiveDocument()) {
+    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+    return promise.forget();
+  }
+
+  if (mCompleteCalled || mRetryPromise) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return promise.forget();
+  }
+
+  nsresult rv = ValidatePaymentValidationErrors(aErrors);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    promise->MaybeReject(rv);
+    return promise.forget();
+  }
+
+  MOZ_ASSERT(mRequest);
+  rv = mRequest->RetryPayment(aCx, aErrors);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    promise->MaybeReject(rv);
+    return promise.forget();
+  }
+
+  mRetryPromise = promise;
+  return promise.forget();
+}
+
+void
+PaymentResponse::RespondRetry(const nsAString& aMethodName,
+                              const nsAString& aShippingOption,
+                              PaymentAddress* aShippingAddress,
+                              const nsAString& aDetails,
+                              const nsAString& aPayerName,
+                              const nsAString& aPayerEmail,
+                              const nsAString& aPayerPhone)
+{
+  mMethodName = aMethodName;
+  mShippingOption = aShippingOption;
+  mShippingAddress = aShippingAddress;
+  mDetails = aDetails;
+  mPayerName = aPayerName;
+  mPayerEmail = aPayerEmail;
+  mPayerPhone = aPayerPhone;
+
+  NS_NewTimerWithCallback(getter_AddRefs(mTimer),
+                          this,
+                          StaticPrefs::dom_payments_response_timeout(),
+                          nsITimer::TYPE_ONE_SHOT,
+                          mOwner->EventTargetFor(TaskCategory::Other));
+  MOZ_ASSERT(mRetryPromise);
+  mRetryPromise->MaybeResolve(JS::UndefinedHandleValue);
+  mRetryPromise = nullptr;
+}
+
+void
+PaymentResponse::RejectRetry(nsresult aRejectReason)
+{
+  MOZ_ASSERT(mRetryPromise);
+  mRetryPromise->MaybeReject(aRejectReason);
+  mRetryPromise = nullptr;
+}
+
+nsresult
+PaymentResponse::ValidatePaymentValidationErrors(const PaymentValidationErrors& aErrors)
+{
+  // Should not be empty errors
+  // check PaymentValidationErrors.error
+  if (aErrors.mError.WasPassed() && !aErrors.mError.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  // check PaymentValidationErrors.payer
+  PayerErrorFields payerErrors(aErrors.mPayer);
+  if (payerErrors.mName.WasPassed() && !payerErrors.mName.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  if (payerErrors.mEmail.WasPassed() && !payerErrors.mEmail.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  if (payerErrors.mPhone.WasPassed() && !payerErrors.mPhone.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  // check PaymentValidationErrors.paymentMethod
+  if (aErrors.mPaymentMethod.WasPassed()) {
+    return NS_OK;
+  }
+  // check PaymentValidationErrors.shippingAddress
+  AddressErrors addErrors(aErrors.mShippingAddress);
+  if (addErrors.mAddressLine.WasPassed() &&
+      !addErrors.mAddressLine.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  if (addErrors.mCity.WasPassed() && !addErrors.mCity.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  if (addErrors.mCountry.WasPassed() && !addErrors.mCountry.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  if (addErrors.mDependentLocality.WasPassed() &&
+      !addErrors.mDependentLocality.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  if (addErrors.mOrganization.WasPassed() &&
+      !addErrors.mOrganization.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  if (addErrors.mPhone.WasPassed() && !addErrors.mPhone.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  if (addErrors.mPostalCode.WasPassed() &&
+      !addErrors.mPostalCode.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  if (addErrors.mRecipient.WasPassed() &&
+      !addErrors.mRecipient.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  if (addErrors.mRegion.WasPassed() &&
+      !addErrors.mRegion.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  if (addErrors.mRegionCode.WasPassed() &&
+      !addErrors.mRegionCode.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  if (addErrors.mSortingCode.WasPassed() &&
+      !addErrors.mSortingCode.Value().IsEmpty()) {
+    return NS_OK;
+  }
+  return NS_ERROR_DOM_ABORT_ERR;
+}
+
 NS_IMETHODIMP
 PaymentResponse::Notify(nsITimer *timer)
 {
   mTimer = nullptr;
   if (mCompleteCalled) {
     return NS_OK;
   }
 
--- a/dom/payments/PaymentResponse.h
+++ b/dom/payments/PaymentResponse.h
@@ -28,17 +28,17 @@ public:
 
   NS_IMETHOD Notify(nsITimer* aTimer) override;
 
   PaymentResponse(nsPIDOMWindowInner* aWindow,
                   PaymentRequest* aRequest,
                   const nsAString& aRequestId,
                   const nsAString& aMethodName,
                   const nsAString& aShippingOption,
-                  RefPtr<PaymentAddress> aShippingAddress,
+                  PaymentAddress* aShippingAddress,
                   const nsAString& aDetails,
                   const nsAString& aPayerName,
                   const nsAString& aPayerEmail,
                   const nsAString& aPayerPhone);
 
   nsPIDOMWindowInner* GetParentObject() const
   {
     return mOwner;
@@ -64,19 +64,34 @@ public:
   void GetPayerPhone(nsString& aRetVal) const;
 
   // Return a raw pointer here to avoid refcounting, but make sure it's safe
   // (the object should be kept alive by the callee).
   already_AddRefed<Promise> Complete(PaymentComplete result, ErrorResult& aRv);
 
   void RespondComplete();
 
+  already_AddRefed<Promise> Retry(JSContext* aCx,
+                                  const PaymentValidationErrors& errorField,
+                                  ErrorResult& aRv);
+
+  void RespondRetry(const nsAString& aMethodName,
+                    const nsAString& aShippingOption,
+                    PaymentAddress* aShippingAddress,
+                    const nsAString& aDetails,
+                    const nsAString& aPayerName,
+                    const nsAString& aPayerEmail,
+                    const nsAString& aPayerPhone);
+  void RejectRetry(nsresult aRejectReason);
+
 protected:
   ~PaymentResponse();
 
+  nsresult ValidatePaymentValidationErrors(const PaymentValidationErrors& aErrors);
+
 private:
   nsCOMPtr<nsPIDOMWindowInner> mOwner;
   bool mCompleteCalled;
   PaymentRequest* mRequest;
   nsString mRequestId;
   nsString mMethodName;
   nsString mDetails;
   nsString mShippingOption;
@@ -84,14 +99,16 @@ private:
   nsString mPayerEmail;
   nsString mPayerPhone;
   RefPtr<PaymentAddress> mShippingAddress;
   // Promise for "PaymentResponse::Complete"
   RefPtr<Promise> mPromise;
   // Timer for timing out if the page doesn't call
   // complete()
   nsCOMPtr<nsITimer> mTimer;
+  // Promise for "PaymentResponse::Retry"
+  RefPtr<Promise> mRetryPromise;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PaymentResponse_h
--- a/dom/payments/ipc/PPaymentRequest.ipdl
+++ b/dom/payments/ipc/PPaymentRequest.ipdl
@@ -105,25 +105,35 @@ struct IPCPaymentUpdateActionRequest
   nsString shippingOption;
 };
 
 struct IPCPaymentCloseActionRequest
 {
   nsString requestId;
 };
 
+struct IPCPaymentRetryActionRequest
+{
+  nsString requestId;
+  nsString error;
+  nsString payerErrors;
+  nsString paymentMethodErrors;
+  nsString shippingAddressErrors;
+};
+
 union IPCPaymentActionRequest
 {
   IPCPaymentCreateActionRequest;
   IPCPaymentCanMakeActionRequest;
   IPCPaymentShowActionRequest;
   IPCPaymentAbortActionRequest;
   IPCPaymentCompleteActionRequest;
   IPCPaymentUpdateActionRequest;
   IPCPaymentCloseActionRequest;
+  IPCPaymentRetryActionRequest;
 };
 
 struct IPCPaymentCanMakeActionResponse
 {
   nsString requestId;
   bool result;
 };
 
--- a/dom/payments/ipc/PaymentRequestParent.cpp
+++ b/dom/payments/ipc/PaymentRequestParent.cpp
@@ -61,16 +61,21 @@ PaymentRequestParent::RecvRequestPayment
       mRequestId = request.requestId();
       break;
     }
     case IPCPaymentActionRequest::TIPCPaymentCloseActionRequest: {
       const IPCPaymentCloseActionRequest& request = aRequest;
       mRequestId = request.requestId();
       break;
     }
+    case IPCPaymentActionRequest::TIPCPaymentRetryActionRequest: {
+      const IPCPaymentRetryActionRequest& request = aRequest;
+      mRequestId = request.requestId();
+      break;
+    }
     default : {
       return IPC_FAIL(this, "Unknown PaymentRequest action type");
     }
   }
   nsCOMPtr<nsIPaymentRequestService> service =
     do_GetService(NS_PAYMENT_REQUEST_SERVICE_CONTRACT_ID);
   MOZ_ASSERT(service);
   PaymentRequestService* rowService =
--- a/dom/webidl/PaymentRequest.webidl
+++ b/dom/webidl/PaymentRequest.webidl
@@ -63,16 +63,29 @@ dictionary AddressErrors {
   DOMString phone;
   DOMString postalCode;
   DOMString recipient;
   DOMString region;
   DOMString regionCode;
   DOMString sortingCode;
 };
 
+dictionary PaymentValidationErrors {
+  PayerErrorFields payer;
+  AddressErrors shippingAddress;
+  DOMString error;
+  object paymentMethod;
+};
+
+dictionary PayerErrorFields {
+  DOMString email;
+  DOMString name;
+  DOMString phone;
+};
+
 dictionary PaymentDetailsUpdate : PaymentDetailsBase {
   DOMString     error;
   AddressErrors shippingAddressErrors;
   PaymentItem   total;
 };
 
 enum PaymentShippingType {
   "shipping",
--- a/dom/webidl/PaymentResponse.webidl
+++ b/dom/webidl/PaymentResponse.webidl
@@ -24,9 +24,13 @@ interface PaymentResponse {
   readonly attribute PaymentAddress? shippingAddress;
   readonly attribute DOMString?      shippingOption;
   readonly attribute DOMString?      payerName;
   readonly attribute DOMString?      payerEmail;
   readonly attribute DOMString?      payerPhone;
 
   [NewObject]
   Promise<void> complete(optional PaymentComplete result = "unknown");
+
+  // If the dictionary argument has no required members, it must be optional.
+  [NewObject]
+  Promise<void> retry(optional PaymentValidationErrors errorFields);
 };