Bug 1472026 - Implement PaymentResponse.prototype.onpayerdetailchange. r=edenchuang,baku
authorMarcos Cáceres <mcaceres@mozilla.com>
Wed, 19 Sep 2018 11:23:33 +0000
changeset 437231 2a9df8249cc8
parent 437230 c1f872c41359
child 437232 8f1ea035b4e3
push id69568
push usermcaceres@mozilla.com
push dateWed, 19 Sep 2018 11:27:54 +0000
treeherderautoland@2a9df8249cc8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersedenchuang, baku
bugs1472026
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 1472026 - Implement PaymentResponse.prototype.onpayerdetailchange. r=edenchuang,baku Implement PaymentResponse.prototype.onpayerdetailchange, per spec. Differential Revision: https://phabricator.services.mozilla.com/D5841
dom/interfaces/payments/nsIPaymentRequestService.idl
dom/payments/PaymentRequest.cpp
dom/payments/PaymentRequest.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/PaymentRequestChild.cpp
dom/payments/ipc/PaymentRequestChild.h
dom/payments/ipc/PaymentRequestParent.cpp
dom/payments/ipc/PaymentRequestParent.h
dom/payments/test/PayerDetailsChromeScript.js
dom/payments/test/mochitest.ini
dom/payments/test/test_payerDetails.html
dom/webidl/PaymentResponse.webidl
xpcom/ds/StaticAtoms.py
--- a/dom/interfaces/payments/nsIPaymentRequestService.idl
+++ b/dom/interfaces/payments/nsIPaymentRequestService.idl
@@ -50,16 +50,28 @@ interface nsIPaymentRequestService : nsI
   /**
    *  Inform the merchant the shipping option has changed.
    *  @param requestId - the request identifier of the payment request.
    *  @param option - the shipping option ID string.
    */
   void changeShippingOption(in AString requestId, in AString option);
 
   /**
+   *  Inform the merchant the payer's details changed in the PaymentResponse.
+   *  @param requestId - the request identifier of the payment request.
+   *  @param aPayerName - the changed payer's name.
+   *  @param aPayerEmail - the changed payer's email.
+   *  @param aPayerPhone - the changed payer's phone.
+   */
+  void changePayerDetail(in AString requestId,
+                         in AString aPayerName,
+                         in AString aPayerEmail,
+                         in AString aPayerPhone);
+
+  /**
    *  Following APIs are for testing or platform code only. UI implementation
    *  should not use them.
    */
   /**
    *  Clean up the all managed payment requests.
    *  This API is for testing only.
    */
   void cleanup();
--- a/dom/payments/PaymentRequest.cpp
+++ b/dom/payments/PaymentRequest.cpp
@@ -600,17 +600,16 @@ PaymentRequest::Constructor(const Global
   // Create PaymentRequest and set its |mId|
   RefPtr<PaymentRequest> request;
   rv = manager->CreatePayment(aGlobal.Context(), window, topLevelPrincipal, aMethodData,
                               aDetails, aOptions, getter_AddRefs(request));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
     return nullptr;
   }
-
   return request.forget();
 }
 
 already_AddRefed<PaymentRequest>
 PaymentRequest::CreatePaymentRequest(nsPIDOMWindowInner* aWindow, nsresult& aRv)
 {
   // Generate a unique id for identification
   nsID uuid;
@@ -960,16 +959,23 @@ PaymentRequest::ReadyForUpdate()
 }
 
 void
 PaymentRequest::SetUpdating(bool aUpdating)
 {
   mUpdating = aUpdating;
 }
 
+already_AddRefed<PaymentResponse>
+PaymentRequest::GetResponse() const
+{
+  RefPtr<PaymentResponse> response = mResponse;
+  return response.forget();
+}
+
 nsresult
 PaymentRequest::DispatchUpdateEvent(const nsAString& aType)
 {
   MOZ_ASSERT(ReadyForUpdate());
 
   PaymentRequestUpdateEventInit init;
   init.mBubbles = false;
   init.mCancelable = false;
@@ -1067,16 +1073,26 @@ PaymentRequest::SetShippingType(const Nu
 }
 
 Nullable<PaymentShippingType>
 PaymentRequest::GetShippingType() const
 {
   return mShippingType;
 }
 
+void PaymentRequest::GetOptions(PaymentOptions& aRetVal) const
+{
+  aRetVal = mOptions;
+}
+
+void PaymentRequest::SetOptions(const PaymentOptions& aOptions)
+{
+  mOptions = aOptions;
+}
+
 void
 PaymentRequest::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
 {
   MOZ_ASSERT(aCx);
   mUpdating = false;
   if (NS_WARN_IF(!aValue.isObject())) {
     return;
   }
--- a/dom/payments/PaymentRequest.h
+++ b/dom/payments/PaymentRequest.h
@@ -19,89 +19,87 @@
 namespace mozilla {
 namespace dom {
 
 class EventHandlerNonNull;
 class PaymentAddress;
 class PaymentRequestChild;
 class PaymentResponse;
 
-class PaymentRequest final : public DOMEventTargetHelper
-                           , public PromiseNativeHandler
-			   , public nsIDocumentActivity
+class PaymentRequest final
+  : public DOMEventTargetHelper
+  , public PromiseNativeHandler
+  , public nsIDocumentActivity
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(PaymentRequest, DOMEventTargetHelper)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(PaymentRequest,
+                                                         DOMEventTargetHelper)
   NS_DECL_NSIDOCUMENTACTIVITY
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
-  static already_AddRefed<PaymentRequest>
-  CreatePaymentRequest(nsPIDOMWindowInner* aWindow, nsresult& aRv);
+  static already_AddRefed<PaymentRequest> CreatePaymentRequest(
+    nsPIDOMWindowInner* aWindow,
+    nsresult& aRv);
 
   static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
 
   static nsresult IsValidStandardizedPMI(const nsAString& aIdentifier,
                                          nsAString& aErrorMsg);
 
   static nsresult IsValidPaymentMethodIdentifier(const nsAString& aIdentifier,
                                                  nsAString& aErrorMsg);
 
-  static nsresult IsValidMethodData(JSContext* aCx,
-                                    const Sequence<PaymentMethodData>& aMethodData,
-                                    nsAString& aErrorMsg);
+  static nsresult IsValidMethodData(
+    JSContext* aCx,
+    const Sequence<PaymentMethodData>& aMethodData,
+    nsAString& aErrorMsg);
 
-  static nsresult
-  IsValidNumber(const nsAString& aItem,
-                const nsAString& aStr,
-                nsAString& aErrorMsg);
-  static nsresult
-  IsNonNegativeNumber(const nsAString& aItem,
-                      const nsAString& aStr,
-                      nsAString& aErrorMsg);
+  static nsresult IsValidNumber(const nsAString& aItem,
+                                const nsAString& aStr,
+                                nsAString& aErrorMsg);
+  static nsresult IsNonNegativeNumber(const nsAString& aItem,
+                                      const nsAString& aStr,
+                                      nsAString& aErrorMsg);
 
-  static nsresult
-  IsValidCurrencyAmount(const nsAString& aItem,
-                        const PaymentCurrencyAmount& aAmount,
-                        const bool aIsTotalItem,
-                        nsAString& aErrorMsg);
+  static nsresult IsValidCurrencyAmount(const nsAString& aItem,
+                                        const PaymentCurrencyAmount& aAmount,
+                                        const bool aIsTotalItem,
+                                        nsAString& aErrorMsg);
 
-  static nsresult
-  IsValidCurrency(const nsAString& aItem,
-                  const nsAString& aCurrency,
-                  nsAString& aErrorMsg);
+  static nsresult IsValidCurrency(const nsAString& aItem,
+                                  const nsAString& aCurrency,
+                                  nsAString& aErrorMsg);
 
-  static nsresult
-  IsValidDetailsInit(const PaymentDetailsInit& aDetails,
-                     const bool aRequestShipping,
-                     nsAString& aErrorMsg);
+  static nsresult IsValidDetailsInit(const PaymentDetailsInit& aDetails,
+                                     const bool aRequestShipping,
+                                     nsAString& aErrorMsg);
+
+  static nsresult IsValidDetailsUpdate(const PaymentDetailsUpdate& aDetails,
+                                       const bool aRequestShipping);
 
-  static nsresult
-  IsValidDetailsUpdate(const PaymentDetailsUpdate& aDetails,
-                       const bool aRequestShipping);
+  static nsresult IsValidDetailsBase(const PaymentDetailsBase& aDetails,
+                                     const bool aRequestShipping,
+                                     nsAString& aErrorMsg);
 
-  static nsresult
-  IsValidDetailsBase(const PaymentDetailsBase& aDetails,
-                     const bool aRequestShipping,
-                     nsAString& aErrorMsg);
-
-  static already_AddRefed<PaymentRequest>
-  Constructor(const GlobalObject& aGlobal,
-              const Sequence<PaymentMethodData>& aMethodData,
-              const PaymentDetailsInit& aDetails,
-              const PaymentOptions& aOptions,
-              ErrorResult& aRv);
+  static already_AddRefed<PaymentRequest> Constructor(
+    const GlobalObject& aGlobal,
+    const Sequence<PaymentMethodData>& aMethodData,
+    const PaymentDetailsInit& aDetails,
+    const PaymentOptions& aOptions,
+    ErrorResult& aRv);
 
   already_AddRefed<Promise> CanMakePayment(ErrorResult& aRv);
   void RespondCanMakePayment(bool aResult);
 
-  already_AddRefed<Promise> Show(const Optional<OwningNonNull<Promise>>& detailsPromise,
-                                 ErrorResult& aRv);
+  already_AddRefed<Promise> Show(
+    const Optional<OwningNonNull<Promise>>& detailsPromise,
+    ErrorResult& aRv);
   void RespondShowPayment(const nsAString& aMethodName,
                           const nsAString& aDetails,
                           const nsAString& aPayerName,
                           const nsAString& aPayerEmail,
                           const nsAString& aPayerPhone,
                           nsresult aRv);
   void RejectShowPayment(nsresult aRejectReason);
   void RespondComplete();
@@ -116,65 +114,61 @@ public:
   void SetId(const nsAString& aId);
 
   bool Equals(const nsAString& aInternalId) const;
 
   bool ReadyForUpdate();
   bool IsUpdating() const { return mUpdating; }
   void SetUpdating(bool aUpdating);
 
+  already_AddRefed<PaymentResponse> GetResponse() const;
+
   already_AddRefed<PaymentAddress> GetShippingAddress() const;
   // Update mShippingAddress and fire shippingaddresschange event
   nsresult UpdateShippingAddress(const nsAString& aCountry,
                                  const nsTArray<nsString>& aAddressLine,
                                  const nsAString& aRegion,
                                  const nsAString& aCity,
                                  const nsAString& aDependentLocality,
                                  const nsAString& aPostalCode,
                                  const nsAString& aSortingCode,
                                  const nsAString& aOrganization,
                                  const nsAString& aRecipient,
                                  const nsAString& aPhone);
 
-
   void SetShippingOption(const nsAString& aShippingOption);
   void GetShippingOption(nsAString& aRetVal) const;
+  void GetOptions(PaymentOptions& aRetVal) const;
+  void SetOptions(const PaymentOptions& aOptions);
   nsresult UpdateShippingOption(const nsAString& aShippingOption);
 
-  nsresult UpdatePayment(JSContext* aCx, const PaymentDetailsUpdate& aDetails,
+  nsresult UpdatePayment(JSContext* aCx,
+                         const PaymentDetailsUpdate& aDetails,
                          bool aDeferredShow);
   void AbortUpdate(nsresult aRv, bool aDeferredShow);
 
   void SetShippingType(const Nullable<PaymentShippingType>& aShippingType);
   Nullable<PaymentShippingType> GetShippingType() const;
 
-  inline void ShippingWasRequested()
-  {
-    mRequestShipping = true;
-  }
+  inline void ShippingWasRequested() { mRequestShipping = true; }
 
-  void
-  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
-  void
-  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   IMPL_EVENT_HANDLER(merchantvalidation);
   IMPL_EVENT_HANDLER(shippingaddresschange);
   IMPL_EVENT_HANDLER(shippingoptionchange);
   IMPL_EVENT_HANDLER(paymentmethodchange);
 
-  void SetIPC(PaymentRequestChild* aChild)
-  {
-    mIPC = aChild;
-  }
+  void SetIPC(PaymentRequestChild* aChild) { mIPC = aChild; }
 
-  PaymentRequestChild* GetIPC()
-  {
-    return mIPC;
-  }
+  PaymentRequestChild* GetIPC() const { return mIPC; }
+
+private:
+  PaymentOptions mOptions;
 
 protected:
   ~PaymentRequest();
 
   void RegisterActivityObserver();
   void UnregisterActivityObserver();
 
   nsresult DispatchUpdateEvent(const nsAString& aType);
@@ -201,37 +195,37 @@ protected:
   RefPtr<PaymentAddress> mShippingAddress;
   // The full shipping address to be used in the response upon payment.
   RefPtr<PaymentAddress> mFullShippingAddress;
   // It is populated when the user chooses a shipping option.
   nsString mShippingOption;
 
   Nullable<PaymentShippingType> mShippingType;
 
-  // "true" when there is a pending updateWith() call to update the payment request
-  // and "false" otherwise.
+  // "true" when there is a pending updateWith() call to update the payment
+  // request and "false" otherwise.
   bool mUpdating;
 
   // Whether shipping was requested. This models [[options]].requestShipping,
   // but we don't actually store the full [[options]] internal slot.
   bool mRequestShipping;
 
   // True if the user passed a promise to show, causing us to defer telling the
   // front end about it.
   bool mDeferredShow;
 
   // The error is set in AbortUpdate(). The value is NS_OK by default.
   nsresult mUpdateError;
 
-  enum {
+  enum
+  {
     eUnknown,
     eCreated,
     eInteractive,
     eClosed
   } mState;
 
   PaymentRequestChild* mIPC;
 };
-
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PaymentRequest_h
--- a/dom/payments/PaymentRequestManager.cpp
+++ b/dom/payments/PaymentRequestManager.cpp
@@ -377,17 +377,17 @@ PaymentRequestManager::CreatePayment(JSC
   NS_ENSURE_ARG_POINTER(aTopLevelPrincipal);
   *aRequest = nullptr;
   nsresult rv;
 
   RefPtr<PaymentRequest> request = PaymentRequest::CreatePaymentRequest(aWindow, rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-
+  request->SetOptions(aOptions);
   /*
    *  Set request's |mId| to details.id if details.id exists.
    *  Otherwise, set |mId| to internal id.
    */
   nsAutoString requestId;
   if (aDetails.mId.WasPassed() && !aDetails.mId.Value().IsEmpty()) {
     requestId = aDetails.mId.Value();
   } else {
@@ -682,10 +682,25 @@ PaymentRequestManager::ChangeShippingAdd
 
 nsresult
 PaymentRequestManager::ChangeShippingOption(PaymentRequest* aRequest,
                                             const nsAString& aOption)
 {
   return aRequest->UpdateShippingOption(aOption);
 }
 
+nsresult
+PaymentRequestManager::ChangePayerDetail(PaymentRequest* aRequest,
+                                         const nsAString& aPayerName,
+                                         const nsAString& aPayerEmail,
+                                         const nsAString& aPayerPhone)
+{
+  MOZ_ASSERT(aRequest);
+  RefPtr<PaymentResponse> response = aRequest->GetResponse();
+  // ignoring the case call changePayerDetail during show().
+  if (!response) {
+    return NS_OK;
+  }
+  return response->UpdatePayerDetail(aPayerName, aPayerEmail, aPayerPhone);
+}
+
 } // end of namespace dom
 } // end of namespace mozilla
--- a/dom/payments/PaymentRequestManager.h
+++ b/dom/payments/PaymentRequestManager.h
@@ -64,16 +64,21 @@ public:
 
   nsresult RespondPayment(PaymentRequest* aRequest,
                           const IPCPaymentActionResponse& aResponse);
   nsresult ChangeShippingAddress(PaymentRequest* aRequest,
                                  const IPCPaymentAddress& aAddress);
   nsresult ChangeShippingOption(PaymentRequest* aRequest,
                                 const nsAString& aOption);
 
+  nsresult ChangePayerDetail(PaymentRequest* aRequest,
+                             const nsAString& aPayerName,
+                             const nsAString& aPayerEmail,
+                             const nsAString& aPayerPhone);
+
   // Called to ensure that we don't "leak" aRequest if we shut down while it had
   // an active request to the parent.
   void RequestIPCOver(PaymentRequest* aRequest);
 
 private:
   PaymentRequestManager() = default;
   ~PaymentRequestManager()
   {
--- a/dom/payments/PaymentRequestService.cpp
+++ b/dom/payments/PaymentRequestService.cpp
@@ -559,10 +559,36 @@ PaymentRequestService::IsBasicCardPaymen
     NS_ENSURE_SUCCESS(rv, false);
     if (service->IsBasicCardPayment(supportedMethods)) {
       return true;
     }
   }
   return false;
 }
 
+NS_IMETHODIMP
+PaymentRequestService::ChangePayerDetail(const nsAString& aRequestId,
+                                         const nsAString& aPayerName,
+                                         const nsAString& aPayerEmail,
+                                         const nsAString& aPayerPhone)
+{
+  nsCOMPtr<nsIPaymentRequest> request;
+  nsresult rv = GetPaymentRequestById(aRequestId, getter_AddRefs(request));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  MOZ_ASSERT(request);
+  payments::PaymentRequest* rowRequest =
+    static_cast<payments::PaymentRequest*>(request.get());
+  if (!rowRequest->GetIPC()) {
+    return NS_ERROR_FAILURE;
+  }
+  rv = rowRequest->GetIPC()->ChangePayerDetail(
+    aRequestId, aPayerName, aPayerEmail, aPayerPhone);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 } // end of namespace dom
 } // end of namespace mozilla
--- a/dom/payments/PaymentResponse.cpp
+++ b/dom/payments/PaymentResponse.cpp
@@ -2,46 +2,65 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/dom/PaymentResponse.h"
 #include "mozilla/dom/BasicCardPaymentBinding.h"
+#include "mozilla/dom/PaymentRequestUpdateEvent.h"
 #include "BasicCardPayment.h"
 #include "PaymentAddress.h"
 #include "PaymentRequestUtils.h"
+#include "mozilla/EventStateManager.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PaymentResponse, mOwner,
-                                      mShippingAddress, mPromise)
+NS_IMPL_CYCLE_COLLECTION_CLASS(PaymentResponse)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PaymentResponse,
+                                               DOMEventTargetHelper)
+  // Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because
+  // DOMEventTargetHelper does it for us.
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(PaymentResponse)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(PaymentResponse)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PaymentResponse,
+                                                  DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mShippingAddress)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PaymentResponse,
+                                                DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mShippingAddress)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTimer)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PaymentResponse)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
-NS_INTERFACE_MAP_END
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(PaymentResponse, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(PaymentResponse, DOMEventTargetHelper)
 
 PaymentResponse::PaymentResponse(nsPIDOMWindowInner* aWindow,
                                  PaymentRequest* aRequest,
                                  const nsAString& aRequestId,
                                  const nsAString& aMethodName,
                                  const nsAString& aShippingOption,
                                  PaymentAddress* aShippingAddress,
                                  const nsAString& aDetails,
                                  const nsAString& aPayerName,
                                  const nsAString& aPayerEmail,
                                  const nsAString& aPayerPhone)
-  : mOwner(aWindow)
+  : DOMEventTargetHelper(aWindow)
   , mCompleteCalled(false)
   , mRequest(aRequest)
   , mRequestId(aRequestId)
   , mMethodName(aMethodName)
   , mDetails(aDetails)
   , mShippingOption(aShippingOption)
   , mPayerName(aPayerName)
   , mPayerEmail(aPayerEmail)
@@ -53,19 +72,17 @@ PaymentResponse::PaymentResponse(nsPIDOM
   // Add payerGivenName + payerFamilyName to PaymentAddress
   NS_NewTimerWithCallback(getter_AddRefs(mTimer),
                           this,
                           StaticPrefs::dom_payments_response_timeout(),
                           nsITimer::TYPE_ONE_SHOT,
                           aWindow->EventTargetFor(TaskCategory::Other));
 }
 
-PaymentResponse::~PaymentResponse()
-{
-}
+PaymentResponse::~PaymentResponse() = default;
 
 JSObject*
 PaymentResponse::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return PaymentResponse_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 void
@@ -76,25 +93,26 @@ PaymentResponse::GetRequestId(nsString& 
 
 void
 PaymentResponse::GetMethodName(nsString& aRetVal) const
 {
   aRetVal = mMethodName;
 }
 
 void
-PaymentResponse::GetDetails(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const
+PaymentResponse::GetDetails(JSContext* aCx,
+                            JS::MutableHandle<JSObject*> aRetVal) const
 {
   RefPtr<BasicCardService> service = BasicCardService::GetService();
   MOZ_ASSERT(service);
   if (!service->IsBasicCardPayment(mMethodName)) {
     DeserializeToJSObject(mDetails, aCx, aRetVal);
   } else {
     BasicCardResponse response;
-    nsresult rv = service->DecodeBasicCardData(mDetails, mOwner, response);
+    nsresult rv = service->DecodeBasicCardData(mDetails, GetOwner(), response);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
     }
 
     MOZ_ASSERT(aCx);
     JS::RootedValue value(aCx);
     if (NS_WARN_IF(!response.ToObjectInternal(aCx, &value))) {
       return;
@@ -110,22 +128,24 @@ PaymentResponse::GetShippingOption(nsStr
 }
 
 void
 PaymentResponse::GetPayerName(nsString& aRetVal) const
 {
   aRetVal = mPayerName;
 }
 
-void PaymentResponse::GetPayerEmail(nsString& aRetVal) const
+void
+PaymentResponse::GetPayerEmail(nsString& aRetVal) const
 {
   aRetVal = mPayerEmail;
 }
 
-void PaymentResponse::GetPayerPhone(nsString& aRetVal) const
+void
+PaymentResponse::GetPayerPhone(nsString& aRetVal) const
 {
   aRetVal = mPayerPhone;
 }
 
 // TODO:
 // 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<PaymentAddress>
@@ -156,17 +176,22 @@ PaymentResponse::Complete(PaymentComplet
     return nullptr;
   }
   nsresult rv = manager->CompletePayment(mRequest, result);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  nsIGlobalObject* global = mOwner->AsGlobal();
+  if (NS_WARN_IF(!GetOwner())) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsIGlobalObject* global = GetOwner()->AsGlobal();
   ErrorResult errResult;
   RefPtr<Promise> promise = Promise::Create(global, errResult);
   if (errResult.Failed()) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   mPromise = promise;
@@ -183,30 +208,35 @@ PaymentResponse::RespondComplete()
   }
 }
 
 already_AddRefed<Promise>
 PaymentResponse::Retry(JSContext* aCx,
                        const PaymentValidationErrors& aErrors,
                        ErrorResult& aRv)
 {
-  nsIGlobalObject* global = mOwner->AsGlobal();
+  nsIGlobalObject* global = GetOwner()->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 (NS_WARN_IF(!GetOwner())) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsIDocument* doc = GetOwner()->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();
@@ -241,36 +271,41 @@ PaymentResponse::RespondRetry(const nsAS
   mMethodName = aMethodName;
   mShippingOption = aShippingOption;
   mShippingAddress = aShippingAddress;
   mDetails = aDetails;
   mPayerName = aPayerName;
   mPayerEmail = aPayerEmail;
   mPayerPhone = aPayerPhone;
 
+  if (NS_WARN_IF(!GetOwner())) {
+    return;
+  }
+
   NS_NewTimerWithCallback(getter_AddRefs(mTimer),
                           this,
                           StaticPrefs::dom_payments_response_timeout(),
                           nsITimer::TYPE_ONE_SHOT,
-                          mOwner->EventTargetFor(TaskCategory::Other));
+                          GetOwner()->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)
+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);
@@ -313,43 +348,76 @@ PaymentResponse::ValidatePaymentValidati
   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()) {
+  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)
+PaymentResponse::Notify(nsITimer* timer)
 {
   mTimer = nullptr;
   if (mCompleteCalled) {
     return NS_OK;
   }
 
   mCompleteCalled = true;
 
   RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
   if (NS_WARN_IF(!manager)) {
     return NS_ERROR_FAILURE;
   }
 
   return manager->CompletePayment(mRequest, PaymentComplete::Unknown, true);
 }
 
+nsresult
+PaymentResponse::UpdatePayerDetail(const nsAString& aPayerName,
+                                   const nsAString& aPayerEmail,
+                                   const nsAString& aPayerPhone)
+{
+  MOZ_ASSERT(mRequest->ReadyForUpdate());
+  PaymentOptions options;
+  mRequest->GetOptions(options);
+  if (options.mRequestPayerName) {
+    mPayerName = aPayerName;
+  }
+  if (options.mRequestPayerEmail) {
+    mPayerEmail = aPayerEmail;
+  }
+  if (options.mRequestPayerPhone) {
+    mPayerPhone = aPayerPhone;
+  }
+  return DispatchUpdateEvent(NS_LITERAL_STRING("payerdetailchange"));
+}
+
+nsresult
+PaymentResponse::DispatchUpdateEvent(const nsAString& aType)
+{
+  PaymentRequestUpdateEventInit init;
+  RefPtr<PaymentRequestUpdateEvent> event =
+    PaymentRequestUpdateEvent::Constructor(this, aType, init);
+  event->SetTrusted(true);
+  event->SetRequest(mRequest);
+
+  ErrorResult rv;
+  DispatchEvent(*event, rv);
+  return rv.StealNSResult();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/payments/PaymentResponse.h
+++ b/dom/payments/PaymentResponse.h
@@ -2,53 +2,51 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_PaymentResponse_h
 #define mozilla_dom_PaymentResponse_h
 
+#include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/PaymentResponseBinding.h" // PaymentComplete
 #include "nsPIDOMWindow.h"
-#include "nsWrapperCache.h"
 #include "nsITimer.h"
 
 namespace mozilla {
 namespace dom {
 
 class PaymentAddress;
 class PaymentRequest;
 class Promise;
 
-class PaymentResponse final : public nsITimerCallback,
-                              public nsWrapperCache
+class PaymentResponse final
+  : public DOMEventTargetHelper
+  , public nsITimerCallback
 {
 public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PaymentResponse)
+  NS_DECL_ISUPPORTS_INHERITED
+
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(PaymentResponse,
+                                                         DOMEventTargetHelper)
 
   NS_IMETHOD Notify(nsITimer* aTimer) override;
 
   PaymentResponse(nsPIDOMWindowInner* aWindow,
                   PaymentRequest* aRequest,
                   const nsAString& aRequestId,
                   const nsAString& aMethodName,
                   const nsAString& aShippingOption,
                   PaymentAddress* aShippingAddress,
                   const nsAString& aDetails,
                   const nsAString& aPayerName,
                   const nsAString& aPayerEmail,
                   const nsAString& aPayerPhone);
 
-  nsPIDOMWindowInner* GetParentObject() const
-  {
-    return mOwner;
-  }
-
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   void GetRequestId(nsString& aRetVal) const;
 
   void GetMethodName(nsString& aRetVal) const;
 
   void GetDetails(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal) const;
@@ -64,16 +62,22 @@ 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();
 
+  IMPL_EVENT_HANDLER(payerdetailchange);
+
+  nsresult UpdatePayerDetail(const nsAString& aPayerName,
+                             const nsAString& aPayerEmail,
+                             const nsAString& aPayerPhone);
+
   already_AddRefed<Promise> Retry(JSContext* aCx,
                                   const PaymentValidationErrors& errorField,
                                   ErrorResult& aRv);
 
   void RespondRetry(const nsAString& aMethodName,
                     const nsAString& aShippingOption,
                     PaymentAddress* aShippingAddress,
                     const nsAString& aDetails,
@@ -82,18 +86,19 @@ public:
                     const nsAString& aPayerPhone);
   void RejectRetry(nsresult aRejectReason);
 
 protected:
   ~PaymentResponse();
 
   nsresult ValidatePaymentValidationErrors(const PaymentValidationErrors& aErrors);
 
+  nsresult DispatchUpdateEvent(const nsAString& aType);
+
 private:
-  nsCOMPtr<nsPIDOMWindowInner> mOwner;
   bool mCompleteCalled;
   PaymentRequest* mRequest;
   nsString mRequestId;
   nsString mMethodName;
   nsString mDetails;
   nsString mShippingOption;
   nsString mPayerName;
   nsString mPayerEmail;
--- a/dom/payments/ipc/PPaymentRequest.ipdl
+++ b/dom/payments/ipc/PPaymentRequest.ipdl
@@ -194,12 +194,16 @@ parent:
   async RequestPayment(IPCPaymentActionRequest aAction);
 
 child:
   async RespondPayment(IPCPaymentActionResponse aResponse);
   async ChangeShippingAddress(nsString aRequestId,
                               IPCPaymentAddress aAddress);
   async ChangeShippingOption(nsString aRequestId,
                              nsString aOption);
+  async ChangePayerDetail(nsString aRequestId,
+                          nsString aPayerName,
+                          nsString aPayerEmail,
+                          nsString aPayerPhone);
 };
 
 } // end of namespace dom
 } // end of namespace mozilla
--- a/dom/payments/ipc/PaymentRequestChild.cpp
+++ b/dom/payments/ipc/PaymentRequestChild.cpp
@@ -77,16 +77,35 @@ PaymentRequestChild::RecvChangeShippingO
   RefPtr<PaymentRequest> request(mRequest);
   nsresult rv = manager->ChangeShippingOption(request, aOption);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+PaymentRequestChild::RecvChangePayerDetail(const nsString& aRequestId,
+                                           const nsString& aPayerName,
+                                           const nsString& aPayerEmail,
+                                           const nsString& aPayerPhone)
+{
+  if (!mRequest) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
+  MOZ_ASSERT(manager);
+  RefPtr<PaymentRequest> request(mRequest);
+  nsresult rv = manager->ChangePayerDetail(request, aPayerName, aPayerEmail, aPayerPhone);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  return IPC_OK();
+}
+
 void
 PaymentRequestChild::ActorDestroy(ActorDestroyReason aWhy)
 {
   if (mRequest) {
     DetachFromRequest(true);
   }
 }
 
--- a/dom/payments/ipc/PaymentRequestChild.h
+++ b/dom/payments/ipc/PaymentRequestChild.h
@@ -30,16 +30,22 @@ protected:
   mozilla::ipc::IPCResult
   RecvChangeShippingAddress(const nsString& aRequestId,
                             const IPCPaymentAddress& aAddress) override;
 
   mozilla::ipc::IPCResult
   RecvChangeShippingOption(const nsString& aRequestId,
                            const nsString& aOption) override;
 
+  mozilla::ipc::IPCResult
+  RecvChangePayerDetail(const nsString& aRequestId,
+                        const nsString& aPayerName,
+                        const nsString& aPayerEmail,
+                        const nsString& aPayerPhone) override;
+
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
 private:
   ~PaymentRequestChild() = default;
 
   void DetachFromRequest(bool aCanBeInManager);
 
   PaymentRequest* MOZ_NON_OWNING_REF mRequest;
--- a/dom/payments/ipc/PaymentRequestParent.cpp
+++ b/dom/payments/ipc/PaymentRequestParent.cpp
@@ -294,16 +294,44 @@ PaymentRequestParent::ChangeShippingOpti
   nsAutoString requestId(aRequestId);
   nsAutoString option(aOption);
   if (!SendChangeShippingOption(requestId, option)) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
+nsresult
+PaymentRequestParent::ChangePayerDetail(const nsAString& aRequestId,
+                                        const nsAString& aPayerName,
+                                        const nsAString& aPayerEmail,
+                                        const nsAString& aPayerPhone)
+{
+  nsAutoString requestId(aRequestId);
+  nsAutoString payerName(aPayerName);
+  nsAutoString payerEmail(aPayerEmail);
+  nsAutoString payerPhone(aPayerPhone);
+  if (!NS_IsMainThread()) {
+    RefPtr<PaymentRequestParent> self = this;
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction("dom::PaymentRequestParent::ChangePayerDetail",
+                                                     [self, requestId, payerName, payerEmail, payerPhone] ()
+    {
+      self->ChangePayerDetail(requestId, payerName, payerEmail, payerPhone);
+    });
+    return NS_DispatchToMainThread(r);
+  }
+  if (!mActorAlive) {
+    return NS_ERROR_FAILURE;
+  }
+  if (!SendChangePayerDetail(requestId, payerName, payerEmail, payerPhone)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
 mozilla::ipc::IPCResult
 PaymentRequestParent::Recv__delete__()
 {
   mActorAlive = false;
   return IPC_OK();
 }
 
 void
--- a/dom/payments/ipc/PaymentRequestParent.h
+++ b/dom/payments/ipc/PaymentRequestParent.h
@@ -21,16 +21,20 @@ public:
   explicit PaymentRequestParent(uint64_t aTabId);
 
   uint64_t GetTabId();
   nsresult RespondPayment(nsIPaymentActionResponse* aResponse);
   nsresult ChangeShippingAddress(const nsAString& aRequestId,
                                  nsIPaymentAddress* aAddress);
   nsresult ChangeShippingOption(const nsAString& aRequestId,
                                 const nsAString& aOption);
+  nsresult ChangePayerDetail(const nsAString& aRequestId,
+                             const nsAString& aPayerName,
+                             const nsAString& aPayerEmail,
+                             const nsAString& aPayerPhone);
 
 protected:
   mozilla::ipc::IPCResult
   RecvRequestPayment(const IPCPaymentActionRequest& aRequest) override;
 
   mozilla::ipc::IPCResult Recv__delete__() override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
new file mode 100644
--- /dev/null
+++ b/dom/payments/test/PayerDetailsChromeScript.js
@@ -0,0 +1,80 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+const { XPCOMUtils } = ChromeUtils.import(
+  "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+const paymentSrv = Cc[
+  "@mozilla.org/dom/payments/payment-request-service;1"
+].getService(Ci.nsIPaymentRequestService);
+
+const TestingUIService = {
+  showPayment(requestId, name = "", email = "", phone = "") {
+    const showResponseData = Cc[
+      "@mozilla.org/dom/payments/general-response-data;1"
+    ].createInstance(Ci.nsIGeneralResponseData);
+    showResponseData.initData({});
+    const showResponse = Cc[
+      "@mozilla.org/dom/payments/payment-show-action-response;1"
+    ].createInstance(Ci.nsIPaymentShowActionResponse);
+    showResponse.init(
+      requestId,
+      Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED,
+      "testing-payment-method", // payment method
+      showResponseData, // payment method data
+      name,
+      email,
+      phone
+    );
+    paymentSrv.respondPayment(
+      showResponse.QueryInterface(Ci.nsIPaymentActionResponse)
+    );
+  },
+  // .retry({ payer }) and .updateWith({payerErrors}) both get routed here:
+  updatePayment(requestId) {
+    // Let's echo what was sent in by the error...
+    const request = paymentSrv.getPaymentRequestById(requestId);
+    const { name, email, phone } = request.paymentDetails.payer;
+    const { error } = request.paymentDetails;
+    // Let's use the .error as the switch
+    switch (error) {
+      case "retry-fire-payerdetaichangeevent": {
+        paymentSrv.changePayerDetail(requestId, name, email, phone);
+        break;
+      }
+      case "update-with": {
+        this.showPayment(requestId, name, email, phone);
+        break;
+      }
+      default:
+        const msg = `Expect details.error value: '${error}'`;
+        sendAsyncMessage("test-fail", msg);
+    }
+  },
+  completePayment(requestId) {
+    const request = paymentSrv.getPaymentRequestById(requestId);
+    const completeResponse = Cc[
+      "@mozilla.org/dom/payments/payment-complete-action-response;1"
+    ].createInstance(Ci.nsIPaymentCompleteActionResponse);
+    completeResponse.init(
+      requestId,
+      Ci.nsIPaymentActionResponse.COMPLETE_SUCCEEDED
+    );
+    paymentSrv.respondPayment(
+      completeResponse.QueryInterface(Ci.nsIPaymentActionResponse)
+    );
+  },
+  get QueryInterface() {
+    return ChromeUtils.generateQI([Ci.nsIPaymentUIService]);
+  },
+};
+
+paymentSrv.setTestingUIService(
+  TestingUIService.QueryInterface(Ci.nsIPaymentUIService)
+);
+
+addMessageListener("teardown", () => {
+  paymentSrv.setTestingUIService(null);
+  sendAsyncMessage("teardown-complete");
+});
--- a/dom/payments/test/mochitest.ini
+++ b/dom/payments/test/mochitest.ini
@@ -8,16 +8,17 @@ support-files =
   echo_payment_request.html
   BasiccardChromeScript.js
   Bug1490698ChromeScript.js
   ClosePaymentChromeScript.js
   ConstructorChromeScript.js
   CurrencyAmountValidationChromeScript.js
   DefaultData.js
   GeneralChromeScript.js
+  PayerDetailsChromeScript.js
   PMIValidationChromeScript.js
   RequestShippingChromeScript.js
   RetryPaymentChromeScript.js
   ShippingOptionsChromeScript.js
   ShowPaymentChromeScript.js
   UpdateErrorsChromeScript.js
 
 [test_abortPayment.html]
@@ -27,16 +28,17 @@ run-if = nightly_build # Bug 1390018: De
 skip-if = e10s # Bug 1408250: Don't expose PaymentRequest Constructor in non-e10s
 [test_bug1490698.html]
 [test_canMakePayment.html]
 run-if = nightly_build # Bug 1390737: Depends on the Nightly-only UI service
 [test_closePayment.html]
 [test_constructor.html]
 [test_currency_amount_validation.html]
 skip-if = (verify && debug)
+[test_payerDetails.html]
 [test_payment-request-in-iframe.html]
 [test_pmi_validation.html]
 skip-if = (verify && debug)
 [test_requestShipping.html]
 [test_retryPayment.html]
 [test_shippingOptions.html]
 [test_showPayment.html]
 [test_update_errors.html]
new file mode 100644
--- /dev/null
+++ b/dom/payments/test/test_payerDetails.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Test for PaymentResponse.prototype.onpayerdetailchange</title>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="./DefaultData.js"></script>
+<script>
+  SimpleTest.waitForExplicitFinish();
+  SimpleTest.requestLongerTimeout(1000);
+
+  const gUrl = SimpleTest.getTestFileURL("PayerDetailsChromeScript.js");
+  const gScript = SpecialPowers.loadChromeScript(gUrl);
+
+  function okTester(result) {
+    return message => ok(result, message);
+  }
+  const passListener = okTester(true);
+  const failListener = okTester(false);
+
+  gScript.addMessageListener("test-fail", failListener);
+  gScript.addMessageListener("test-pass", passListener);
+
+  function sendOnce(message) {
+    return data => {
+      return new Promise(resolve => {
+        const doneMsg = `${message}-complete`;
+        gScript.addMessageListener(doneMsg, function listener() {
+          gScript.removeMessageListener(doneMsg, listener);
+          resolve();
+        });
+        gScript.sendAsyncMessage(message, data);
+      });
+    };
+  }
+  const sendTearDown = sendOnce("teardown");
+
+  async function loopTest(iterations) {
+    const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(
+      true
+    );
+    const options = {
+      requestPayerName: true,
+      requestPayerEmail: true,
+      requestPayerPhone: true,
+    }
+    const request = new PaymentRequest(defaultMethods, defaultDetails, options);
+    const response = await request.show();
+    is(response.payerName, "", ".payerName must initially be ''");
+    is(response.payerEmail, "", ".payerEmail must initially be ''");
+    is(response.payerPhone, "", ".payerPhone must initially be ''");
+    for (let i = 0; i < iterations; i++) {
+      const payer = {
+        name: `test name ${i}`,
+        phone: `test phone ${i}`,
+        email: `test email ${i}`,
+      }
+
+      // Capture the event to firing
+      const eventPromise = new Promise(resolve => {
+        response.onpayerdetailchange = resolve;
+      });
+      const retryPromise = response.retry({
+        error: "retry-fire-payerdetaichangeevent",
+        payer
+      });
+      const event = await eventPromise;
+
+      // Check things got updated
+      is(response.payerName, payer.name, `.payerName must be "${payer.name}"`);
+      is(response.payerEmail, payer.email, `.payerEmail must be "${payer.email}"`);
+      is(response.payerPhone, payer.phone, `.payerPhone must be "${payer.phone}"`);
+
+      // Finally, let's do an updateWith()
+      event.updateWith({ error: "update-with", payerErrors: payer, ...defaultDetails });
+
+      await retryPromise;
+    }
+
+    await response.complete("success");
+    handler.destruct();
+  }
+
+  async function teardown() {
+    await sendTearDown();
+    gScript.removeMessageListener("test-fail", failListener);
+    gScript.removeMessageListener("test-pass", passListener);
+    gScript.destroy();
+    SimpleTest.finish();
+  }
+
+  async function runTests() {
+    try {
+      await loopTest(5); // lets go around 5 times
+    } catch (err) {
+      ok(false, `Unexpected error: ${err}.`);
+    } finally {
+      await teardown();
+    }
+  }
+
+  window.addEventListener("load", () => {
+    const prefs = [["dom.payments.request.enabled", true]];
+    SpecialPowers.pushPrefEnv({ set: prefs }, runTests);
+  });
+</script>
+
+<body>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1472026">Mozilla Bug 1472026</a>
\ No newline at end of file
--- a/dom/webidl/PaymentResponse.webidl
+++ b/dom/webidl/PaymentResponse.webidl
@@ -13,17 +13,17 @@
 enum PaymentComplete {
   "success",
   "fail",
   "unknown"
 };
 
 [SecureContext,
  Func="mozilla::dom::PaymentRequest::PrefEnabled"]
-interface PaymentResponse {
+interface PaymentResponse : EventTarget {
   [Default] object toJSON();
 
   readonly attribute DOMString       requestId;
   readonly attribute DOMString       methodName;
   readonly attribute object          details;
   readonly attribute PaymentAddress? shippingAddress;
   readonly attribute DOMString?      shippingOption;
   readonly attribute DOMString?      payerName;
@@ -31,9 +31,11 @@ interface PaymentResponse {
   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);
+
+  attribute EventHandler onpayerdetailchange;
 };
--- a/xpcom/ds/StaticAtoms.py
+++ b/xpcom/ds/StaticAtoms.py
@@ -797,16 +797,17 @@ STATIC_ATOMS = [
     Atom("ononline", "ononline"),
     Atom("onoffline", "onoffline"),
     Atom("onopen", "onopen"),
     Atom("onorientationchange", "onorientationchange"),
     Atom("onoverflow", "onoverflow"),
     Atom("onpagehide", "onpagehide"),
     Atom("onpageshow", "onpageshow"),
     Atom("onpaste", "onpaste"),
+    Atom("onpayerdetailchange", "onpayerdetailchange"),
     Atom("onpaymentmethodchange", "onpaymentmethodchange"),
     Atom("onpointerlockchange", "onpointerlockchange"),
     Atom("onpointerlockerror", "onpointerlockerror"),
     Atom("onpopuphidden", "onpopuphidden"),
     Atom("onpopuphiding", "onpopuphiding"),
     Atom("onpopuppositioned", "onpopuppositioned"),
     Atom("onpopupshowing", "onpopupshowing"),
     Atom("onpopupshown", "onpopupshown"),