-
Notifications
You must be signed in to change notification settings - Fork 138
Description
@domenic, @aestes, @zkoch, @adrianba, @mnoorenberghe, @stpeter, @edenchuang, all, here is an attempt to solve the retry issue. It builds on the fine grained error recovery proposal at #647, and on payment method change event (#695).
Retrying a payment flow
I did a bunch of experimentation, and I think the most logical solution is to mixin the handlers from PaymentRequest into PaymentResponse (or just add them directly as below?... hi @domenic!), plus add a onpayerdetailschange and onpaymentmethodchange event handlers, and a .retry() method.
That means that we don't need to screw around with dead PaymentRequests by resetting their state machines.
The rationale for adding the new onpayerdetailschange being that PaymentRequest never gets things like payerName, payerEmail, etc. so there is no way to detect those changing. Similarly, onpaymentmethodchange reflects the full payment handler response, which is not available on PaymentRequest.
Thus... I'd like to propose:
IDL changes
/* see https://github.com/w3c/payment-request/issues/647#issuecomment-385852164 */
dictionary PaymentErrors {
PayerErrors payerErrors;
AddressErrors shippingAddressErrors;
}
partial interface PaymentResponse {
attribute EventHandler onpayerdetailschange;
attribute EventHandler onpaymentdetailschange;
Promise <void> retry(PaymentErrors errors);
}retry() method
The retry() method signals that something is wrong with the sheet.
What's actually wrong with the sheet is represented by the PaymentErrors errors argument.
It returns a promise that resolves when the user hits Pay again (after they hopefully fix errors), and gets rejected if the user aborts (e.g., they hit the "esc" key while the sheet is showing).
retry() can only be called multiple times, but only after each returned promise settles. It tells the browser: "let the user change the requested inputs" (i.e., what's in PaymentOptions and the ). Calling it prematurely, rejects a retryPromise with "InvalidStateError" .
Empty PaymentErrors dictionary (i.e., .retry({})) means "unknown" error, meaning the user should check all their inputs. Otherwise, fix the errors.
onpayerdetailschange event handler
The onpayerdetailschange fires when the user changes name, email, phone, depending on whether PaymentOptions requested them.
When this event fires, the merchant simply queries PaymentResponse for the thing they are interested in validating/checking. If value of attribute is invalid, the merchant calls ev.updateWith({ stuffToFix }) (see #647).
Example of usage
This shows how a merchant could use async validators to process a PaymentResponse.
(Mock code, untested... treat a pseudo code for illustrative purposes).
async function doPaymentRequest() {
const request = new PaymentRequest(methodData, details, options);
const response = await request.show();
const validator = new Validator(); // user code
// collect any errors from response
const {
shippingAddressErrors,
payerErrors,
paymentMethodErrors, // <- needs exploration.
} = await validator.validateResponse(response);
// Ok, we got bad input... let's get the user to fix those!
if (shippingAddressErrors || payerErrors || paymentMethodErrors) {
const promisesToFixThings = [];
// let's make sure the shipping address is fixed
if (shippingAddressErrors) {
const promiseToFixAddress = new Promise(resolve => {
// Browser keeps calling this until promise resolves.
response.onpaymentaddresschange = async ev => {
const promiseToValidate = validator.validateShippingAddress(response);
ev.updateWith(promiseToValidate);
// we could abort here via try/catch
const errors = await promiseToValidate;
if (!errors) {
resolve(); // yay! Address is fixed!
}
};
});
promisesToFixThings.push(promiseToFixAddress);
}
if (payerErrors) {
// As above for payer errors
}
response.retry({ shippingAddressErrors, payerErrors });
await Promise.all(promisesToFixThings);
}
await response.complete("success");
}
doPaymentRequest();