The web's checkout problem

Shopping cart abandonment on mobile is notoriously bad—in some studies, as high as 97%. Many of those abandoned carts belong to shoppers who were just browsing, but poor checkout UX is a major factor for the rest. The web has no standard mechanism for reusing payment data across sites, so users are forced to either tap out their card details on a phone keyboard or give up entirely.

The usual solutions have serious downsides. A site can store payment details itself, which works but raises trust concerns and gives every merchant another database of card numbers to protect. Or the site can integrate with a platform payment system, which locks it into that platform and requires the user to already have an account there. Neither approach works broadly across the web.

A boring API with a big job

Next to WebGL and WebRTC, the requestAutocomplete API is unglamorous. It's a small, simple addition to HTML forms that lets the browser fill in payment and address data on the user's behalf, rather than forcing the site to integrate with any particular payment provider. Chrome's current implementation also hooks into Google Wallet for US users, and the browser stores data locally for users who aren't signed in.

The core mechanism is a single method on form elements: form.requestAutocomplete(). The browser then shows a permission dialog, letting the user review and choose what to share. Calls are restricted to user interaction handlers such as click, keyup, mouseup and touch events, a deliberate security measure to prevent background pages from invoking the flow.

Including the form on your page is enough to enable the call:

var form = document.querySelector('#billing-form');
form.requestAutocomplete();

Decorating your form for autofill

The autocomplete attribute has been around since Internet Explorer 5, where it only supported turning browser suggestions on or off. The HTML spec has since extended it to describe the expected content of a field without depending on the field's name attribute. requestAutocomplete relies on this extended vocabulary to map user data to form controls.

<input name="card" autocomplete="cc-number">

Although the API itself is not payments-specific, Chrome's current implementation focuses on payment and address data. In the future, browsers may support other categories such as login details and password generators, passport information or avatar uploads.

Payment fields

Chrome currently recognizes these payment tokens:

  • email
  • cc-name — name on card
  • cc-number — card number
  • cc-exp-month — expiry month, two digits
  • cc-exp-year — expiry year, four digits
  • cc-csc — three- or four-digit security code
<form id="billing-form">
  <input name="email" type="email" autocomplete="email">
  <input name="card" type="text" autocomplete="cc-number">
  <input name="name" type="text" autocomplete="cc-name">
  <input name="expiry-month" type="text" autocomplete="cc-exp-month">
  <input name="expiry-year" type="text" autocomplete="cc-exp-year">
  <input name="csc" type="text" autocomplete="cc-csc">
</form>

The name attributes in these examples are arbitrary, only the autocomplete tokens matter. Fields don't have to be input elements either—a <select> works fine for expiry months and years. For users in browsers without requestAutocomplete, the same form should still carry labels, layout and HTML5 validation to serve as a regular fallback form.

Address fields

Address data uses these recognized tokens, combined with a billing or shipping prefix.

  • name — full name in a single field, which avoids the Western bias of separate first-name/last-name fields
  • tel — full number including country code, or split as tel-country-code and tel-national
  • street-address — full address, components comma-separated; or split as address-line1 and address-line2
  • locality — city or town
  • region — state code, county or canton
  • postal-code — postal or ZIP code
  • country
<input name="name" type="text" autocomplete="shipping name">
<input name="postcode" type="text" autocomplete="shipping postal-code">

Again, name values are just examples. Include a shipping address only when one makes sense—nobody needs to specify a delivery destination for a hotel room.

Calling requestAutocomplete at the right moment

The ideal experience skips the checkout form entirely. Rather than navigating the user to a payment details page, present the checkout button, then call requestAutocomplete on a hidden billing form that lives on the current page. If the operation succeeds, submit the collected data and move on. Users in unsupported browsers fall back to the full form page.

A common pattern starts with the cart page. In the document head, hide the checkout button from users without JavaScript:

<script>document.documentElement.className = "js";</script>
.js #checkout-button {
  visibility: hidden;
}

The billing form can be placed anywhere on the cart page—the CSS keeps it out of view:

<form id="billing-form">
  <input name="email" type="email" autocomplete="email">
  <input name="card" type="text" autocomplete="cc-number">
  <input name="name" type="text" autocomplete="cc-name">
  <input name="expiry-month" type="text" autocomplete="cc-exp-month">
  <input name="expiry-year" type="text" autocomplete="cc-exp-year">
  <input name="csc" type="text" autocomplete="cc-csc">
  <input id="checkout-button" type="submit" value="Checkout">
</form>

Then the JavaScript wires up the button and form, only for browsers that support the feature:

if ('requestAutocomplete' in document.createElement('form')) {
  var button = document.querySelector('#checkout-button');
  var form = document.querySelector('#billing-form');
  if (button && form) {
    form.addEventListener('submit', function(e) {
      e.preventDefault();
      form.requestAutocomplete();
    });
    button.disabled = false;
  }
}

For a cleaner approach, load the form HTML via XHR inside this enhancement function. The form only appears for supporting browsers, and you don't need to include it on every page that might trigger checkout. Serving the cart page over SSL is also required to avoid the "Skeletor" mixed-content warning.

Handling the result

requestAutocomplete is asynchronous and returns immediately. Two new events report the outcome:

form.addEventListener('autocomplete', function(e) {
  // Fetch the data from the form
  form.submit();
});

form.addEventListener('autocompleteerror', function(e) {
  // Deal with failure or cancellation
});

If the operation succeeds, the simplest approach is to submit the form and let your server-side validation handle the data, then show a confirmation page with delivery costs. If the user cancels or the browser doesn't support the feature, send them to the regular form flow.

form.addEventListener('autocomplete', function(e) {
  form.submit();
});
form.addEventListener('autocompleteerror', function(e) {
  window.location = '/checkout';
});

Where the data lives

The spec deliberately does not dictate storage, leaving room for browser innovation. In Chrome, users who are logged in can store details in Google Wallet, which also means requestAutocomplete never exposes the real card number to a site, only an ID that Wallet resolves—a useful layer of indirection. Users who are not logged in, or who decline Wallet, can have details stored locally in the browser. Other providers may be supported in future.

One step further: multi-page forms

It's preferable to collect all needed data in a single requestAutocomplete call. If your server can't receive it all at once, extract the data into a plain object and submit it in whatever shape your backend needs. A helper function can read all supported fields from the form into an object keyed by token name, ready for transformation and posting as needed.

requestAutocomplete is a quiet standard that removes a familiar pain point: users shouldn't have to retype payment details on every site, and sites shouldn't have to accumulate card numbers to provide a fast checkout. With support from the browser, one-click payment can work across the open web without locking in to a platform or a single payment provider.