diff --git a/dom/interfaces/payments/nsIPaymentRequest.idl b/dom/interfaces/payments/nsIPaymentRequest.idl index 1c8b229ddf1b..78a10c1d08a9 100644 --- a/dom/interfaces/payments/nsIPaymentRequest.idl +++ b/dom/interfaces/payments/nsIPaymentRequest.idl @@ -63,6 +63,10 @@ interface nsIPaymentDetails : nsISupports 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)] diff --git a/dom/payments/PaymentRequest.cpp b/dom/payments/PaymentRequest.cpp index 68ec4beaaa92..e2992a92bca9 100644 --- a/dom/payments/PaymentRequest.cpp +++ b/dom/payments/PaymentRequest.cpp @@ -745,10 +745,14 @@ PaymentRequest::Show(const Optional>& aDetailsPromise, 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; } @@ -761,7 +765,7 @@ PaymentRequest::RespondShowPayment(const nsAString& aMethodName, const nsAString& aPayerPhone, nsresult aRv) { - MOZ_ASSERT(mAcceptPromise); + MOZ_ASSERT(mAcceptPromise || mResponse); MOZ_ASSERT(mState == eInteractive); if (NS_FAILED(aRv)) { @@ -773,12 +777,17 @@ PaymentRequest::RespondShowPayment(const nsAString& aMethodName, mShippingAddress.swap(mFullShippingAddress); mFullShippingAddress = nullptr; - RefPtr 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 = + new PaymentResponse(GetOwner(), this, mId, aMethodName, + mShippingOption, mShippingAddress, aDetails, + aPayerName, aPayerEmail, aPayerPhone); + mResponse = paymentResponse; + mAcceptPromise->MaybeResolve(paymentResponse); + } mState = eClosed; mAcceptPromise = nullptr; @@ -903,6 +912,22 @@ PaymentRequest::AbortUpdate(nsresult aRv, bool aDeferredShow) mUpdateError = aRv; } +nsresult +PaymentRequest::RetryPayment(JSContext* aCx, const PaymentValidationErrors& aErrors) +{ + if (mState == eInteractive) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + RefPtr 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 { diff --git a/dom/payments/PaymentRequest.h b/dom/payments/PaymentRequest.h index 950b3c8dbd55..b1cd7b02e178 100644 --- a/dom/payments/PaymentRequest.h +++ b/dom/payments/PaymentRequest.h @@ -109,6 +109,8 @@ public: already_AddRefed 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); diff --git a/dom/payments/PaymentRequestData.cpp b/dom/payments/PaymentRequestData.cpp index fe43b9b4ea11..b8ba31a40f01 100644 --- a/dom/payments/PaymentRequestData.cpp +++ b/dom/payments/PaymentRequestData.cpp @@ -483,6 +483,31 @@ PaymentDetails::GetShippingAddressErrors(JSContext* aCx, JS::MutableHandleValue 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) { @@ -547,6 +572,19 @@ 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, @@ -727,6 +765,20 @@ 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(mPaymentDetails.get()); + MOZ_ASSERT(rowDetails); + return rowDetails->UpdateErrors(aError, + aPayerErrors, + aPaymentMethodErrors, + aShippingAddressErrors); +} + NS_IMETHODIMP PaymentRequest::GetCompleteStatus(nsAString& aCompleteStatus) { diff --git a/dom/payments/PaymentRequestData.h b/dom/payments/PaymentRequestData.h index d4d0096ebd5c..ec715270ed3c 100644 --- a/dom/payments/PaymentRequestData.h +++ b/dom/payments/PaymentRequestData.h @@ -133,6 +133,11 @@ public: 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, @@ -151,6 +156,8 @@ private: nsCOMPtr mModifiers; nsString mError; nsString mShippingAddressErrors; + nsString mPayerErrors; + nsString mPaymentMethodErrors; }; class PaymentOptions final : public nsIPaymentOptions @@ -208,6 +215,12 @@ public: void SetCompleteStatus(const nsAString& aCompleteStatus); + nsresult + UpdateErrors(const nsAString& aError, + const nsAString& aPayerErrors, + const nsAString& aPaymentMethodErrors, + const nsAString& aShippingAddressErrors); + private: ~PaymentRequest() = default; diff --git a/dom/payments/PaymentRequestManager.cpp b/dom/payments/PaymentRequestManager.cpp index 015bf83f96cc..cf4e1a77bb8b 100644 --- a/dom/payments/PaymentRequestManager.cpp +++ b/dom/payments/PaymentRequestManager.cpp @@ -539,6 +539,44 @@ PaymentRequestManager::ClosePayment(PaymentRequest* aRequest) 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) diff --git a/dom/payments/PaymentRequestManager.h b/dom/payments/PaymentRequestManager.h index 89e2b644547d..be394bf0aee1 100644 --- a/dom/payments/PaymentRequestManager.h +++ b/dom/payments/PaymentRequestManager.h @@ -58,6 +58,9 @@ public: bool aRequestShipping, bool aDeferredShow); nsresult ClosePayment(PaymentRequest* aRequest); + nsresult RetryPayment(JSContext* aCx, + PaymentRequest* aRequest, + const PaymentValidationErrors& aErrors); nsresult RespondPayment(PaymentRequest* aRequest, const IPCPaymentActionResponse& aResponse); diff --git a/dom/payments/PaymentRequestService.cpp b/dom/payments/PaymentRequestService.cpp index 866cb2b25e4f..0e253beb4d03 100644 --- a/dom/payments/PaymentRequestService.cpp +++ b/dom/payments/PaymentRequestService.cpp @@ -378,6 +378,26 @@ PaymentRequestService::RequestPayment(const nsAString& aRequestId, mRequestQueue.RemoveElement(payment); break; } + case IPCPaymentActionRequest::TIPCPaymentRetryActionRequest: { + const IPCPaymentRetryActionRequest& action = aAction; + nsCOMPtr payment; + rv = GetPaymentRequestById(aRequestId, getter_AddRefs(payment)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(payment); + payments::PaymentRequest* rowPayment = + static_cast(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; } diff --git a/dom/payments/PaymentResponse.cpp b/dom/payments/PaymentResponse.cpp index 1f8d5aaabd37..487647534031 100644 --- a/dom/payments/PaymentResponse.cpp +++ b/dom/payments/PaymentResponse.cpp @@ -31,7 +31,7 @@ PaymentResponse::PaymentResponse(nsPIDOMWindowInner* aWindow, const nsAString& aRequestId, const nsAString& aMethodName, const nsAString& aShippingOption, - RefPtr aShippingAddress, + PaymentAddress* aShippingAddress, const nsAString& aDetails, const nsAString& aPayerName, const nsAString& aPayerEmail, @@ -183,6 +183,156 @@ PaymentResponse::RespondComplete() } } +already_AddRefed +PaymentResponse::Retry(JSContext* aCx, + const PaymentValidationErrors& aErrors, + ErrorResult& aRv) +{ + nsIGlobalObject* global = mOwner->AsGlobal(); + ErrorResult errResult; + RefPtr 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) { diff --git a/dom/payments/PaymentResponse.h b/dom/payments/PaymentResponse.h index 9bba1d80c8a5..4a5725a34965 100644 --- a/dom/payments/PaymentResponse.h +++ b/dom/payments/PaymentResponse.h @@ -33,7 +33,7 @@ public: const nsAString& aRequestId, const nsAString& aMethodName, const nsAString& aShippingOption, - RefPtr aShippingAddress, + PaymentAddress* aShippingAddress, const nsAString& aDetails, const nsAString& aPayerName, const nsAString& aPayerEmail, @@ -69,9 +69,24 @@ public: void RespondComplete(); + already_AddRefed 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 mOwner; bool mCompleteCalled; @@ -89,6 +104,8 @@ private: // Timer for timing out if the page doesn't call // complete() nsCOMPtr mTimer; + // Promise for "PaymentResponse::Retry" + RefPtr mRetryPromise; }; } // namespace dom diff --git a/dom/payments/ipc/PPaymentRequest.ipdl b/dom/payments/ipc/PPaymentRequest.ipdl index 74d54b01a27f..461ec553c962 100644 --- a/dom/payments/ipc/PPaymentRequest.ipdl +++ b/dom/payments/ipc/PPaymentRequest.ipdl @@ -110,6 +110,15 @@ struct IPCPaymentCloseActionRequest nsString requestId; }; +struct IPCPaymentRetryActionRequest +{ + nsString requestId; + nsString error; + nsString payerErrors; + nsString paymentMethodErrors; + nsString shippingAddressErrors; +}; + union IPCPaymentActionRequest { IPCPaymentCreateActionRequest; @@ -119,6 +128,7 @@ union IPCPaymentActionRequest IPCPaymentCompleteActionRequest; IPCPaymentUpdateActionRequest; IPCPaymentCloseActionRequest; + IPCPaymentRetryActionRequest; }; struct IPCPaymentCanMakeActionResponse diff --git a/dom/payments/ipc/PaymentRequestParent.cpp b/dom/payments/ipc/PaymentRequestParent.cpp index 916ae4c9a842..9731adce6a8c 100644 --- a/dom/payments/ipc/PaymentRequestParent.cpp +++ b/dom/payments/ipc/PaymentRequestParent.cpp @@ -66,6 +66,11 @@ PaymentRequestParent::RecvRequestPayment(const IPCPaymentActionRequest& 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"); } diff --git a/dom/webidl/PaymentRequest.webidl b/dom/webidl/PaymentRequest.webidl index ed9ea0b0c9fa..13245c29955f 100644 --- a/dom/webidl/PaymentRequest.webidl +++ b/dom/webidl/PaymentRequest.webidl @@ -68,6 +68,19 @@ dictionary AddressErrors { 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; diff --git a/dom/webidl/PaymentResponse.webidl b/dom/webidl/PaymentResponse.webidl index 112699b0ede3..f45e7131f212 100644 --- a/dom/webidl/PaymentResponse.webidl +++ b/dom/webidl/PaymentResponse.webidl @@ -29,4 +29,8 @@ interface PaymentResponse { [NewObject] Promise complete(optional PaymentComplete result = "unknown"); + + // If the dictionary argument has no required members, it must be optional. + [NewObject] + Promise retry(optional PaymentValidationErrors errorFields); };