[w3c/payment-request] Retrying a payment (#705)

@domenic, @aestes, @zkoch, @adrianba, @mnoorenberghe, @stpeter, 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 `PaymentRequest`s 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

```JS
/* see https://github.com/w3c/payment-request/issues/647#issuecomment-385852164 */
dictionary PaymentErrors {
  PayerErrors payerErrors;
  AddressErrors shippingAddressErrors;
}
// we could make this a mixin
partial interface PaymentResponse {
  attribute EventHandler onshippingaddresschange;
  attribute EventHandler onshippingoptionchange;
  // name, email, phone
  attribute EventHandler onpayerdetailschange;
  // https://github.com/w3c/payment-request/pull/695
  attribute EventHandler onpaymentmethodchange;
  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.

`retry()` can only be called once. It tells the browser: "let the user change the requested inputs" (i.e., what's in `PaymentOptions` and the ). Calling it again, rejects a `retryPromise` with `"InvalidState"` error.  

Empty `PaymentErrors` dictionary (i.e., `.retry({})`) means "unknown" error, meaning the user should check all their inputs. Otherwise, fix the `errors`.

Question: is there any reason `.retry()` should return a promise? I left it "fire and forget" like `.updateWith()`, because I couldn't think of any reason not to.  

## `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). 

```JS
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();
```

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/w3c/payment-request/issues/705

Received on Wednesday, 2 May 2018 08:02:54 UTC