Checkout API - w3c/webpayments GitHub Wiki

Checkout API

This is a preliminary proposal for a new Browser API, the "Checkout API".

Overview

After some discussion about differences between the Web Payments Browser API and the Payment Request API proposals, a potential realization was made. The former spec is attempting to focus on a low-level API that takes in a payment request message and outputs an acknowledgement. Its focus is very narrow and its functionality limited. The latter spec is attempting to focus on general improvements to a larger checkout process that also happens to include payment. This means that perhaps the specs should not be seen as competing proposals, but proposals that do different things -- and that the latter could instead leverage the former.

This is a proposal that shows how a browser "checkout" API that leverages the former spec could work -- it also takes into consideration arguments over technical architecture decisions like how control should be relinquished or regained by the merchant sites that desire a fully-in-browser checkout experience.

Required Low-level API

To start, imagine that a low-level payment request API exists that looks like this:

// promise resolves to a payment "acknowledgement"
var promise = navigator.payment.request(paymentRequest);

This API will send a payment request message to the browser. If no in-browser UI has started (this is important for later), one will be auto-started and the user will be asked to pick their payment app (with prices shown next to payment apps). Control is passed to the payment app until it responds with a payment acknowledgement which is returned to the merchant site in the resolved promise.

This low-level API can be used by merchants that want more control over their checkout process, but want to delegate control over the payment itself.

High-level Checkout API

Now, let's talk about a higher-level API that builds on this idea to help improve the entire checkout flow. This "checkout" API can be used by merchants that want to delegate the entire checkout flow to the browser.

This API allows merchants to collect the information they need to create a payment request message but keeps the users in the same seamless, in-browser checkout process. It will let a merchant site specify a line item estimate (if necessary) and request a number of pieces of information, such as a shipping address, that it needs before it can prepare the payment request message. Once the merchant site has specified the information it needs, it tells the browser to start the checkout process and waits for a promise to resolve.

The browser then opens a UI to take the user through the checkout process. Once the user has made all of their selections, the user (for example) clicks a button to see their total. Upon clicking, the promise the merchant was waiting on resolves with the requested information and the in-browser UI will wait for the merchant site to call "finish" with the payment request message (which can be generated asynchronously if desired). The merchant can also call "cancel" instead.

Once finish is called, it internally calls navigator.payment.request(paymentRequest) and returns a promise, which will resolve to an error or a payment acknowledgement. This causes the in-browser UI to show the price and payment app selection screen, just like it would have if called independently. If the user selects a payment app and completes their payment, the merchant site will receive a payment acknowledgement via the resolved promise, otherwise, it will receive an error.

Note: If a "back" feature for the in-browser UI is desirable at this point, one option is to return a promise in the error that can be waited on, again, to resolve once the user is ready for the merchant site to generate the payment request message based on their changes.

This API can be used to give merchant sites a full, in-browser, checkout experience, but builds on top of the navigator.payment.request API. It could also be modified (internally) to build on top of other future APIs that expose simple, low-level mechanisms for independently requesting things like shipping address or other "identity credentials/verifiable claims" (this is aimed at supporting The Extensible Web Manifesto approach). These APIs are not yet exposed as low-level APIs, but this approach is an attempt to do some future proofing should they be standardized at some point.

This API is also designed so that the flow occurs in a straightforward, deterministic way. It intentionally avoids events and exposing state management to the merchant site. The merchant site does not have to deal with multiple events coming from the in-browser UI and the subsequent management of any asynchronous work it generates. Rather, once the user has made their selections and chosen all of the necessary information, it will be informed. The in-browser UI will not allow further action until the merchant site responds with a payment request message.

Example Code

// create checkout
var checkout = new navigator.payment.Checkout();
checkout
  .send(lineItemEstimate)      // will send estimate to UI
  .request('shippingAddress')  // requests UI collect shippingAddress
  .request('somethingElse')    // requests UI collect something else
  .start()                     // start the checkout UI
  .then(finishCheckout);       // checkout UI has collected the info

function finishCheckout(data) {
  // got requested checkout data, eg:
  // data.shippingAddress

  // if desired, instead call checkout.cancel();

  // do some async stuff to build a payment request
  // note that the merchant site writes this
  // 'buildPaymentRequest' method
  buildPaymentRequest(data).then(function(paymentRequest) {
    // complete, the checkout.finish() call invokes 
    // `navigator.payment.request(paymentRequest)` internally
    return checkout.finish(paymentRequest);
  }).catch(function(err) {
    // user did not complete checkout
    // potentially check `err` for another promise if they didn't fully cancel
    if(err.retryPromise) { // bikeshed name
      return err.retryPromise.then(finishCheckout);
    }
    throw err;
  }).then(function(acknowledgement) {
    // handle acknowledgement from payment app
  });

Next Steps

If this approach is desirable, one potential next step is to start adapting the Payment Request API to use this approach (and optionally rename it to a Checkout API) -- and to make that spec build on top of the Web Payments Request API -- which will be limited to a low-level API.