The payment flow at a glance

Web Payments APIs bring payment functionality natively into the browser, simplifying how merchants integrate with payment apps while giving customers a more streamlined and secure checkout experience. A payment transaction on a merchant site proceeds through six stages:

  1. The merchant initiates the transaction.
  2. The merchant shows a payment button.
  3. The customer presses the payment button.
  4. The browser launches the payment app.
  5. The merchant updates the transaction in response to customer changes.
  6. The merchant validates the payment and finishes.
Eiji Kitamura

Initiating the transaction

When a customer decides to buy, the merchant creates a PaymentRequest object that carries the full transactional context. The constructor accepts three arguments: the acceptable payment methods and any associated data, the transaction details including the mandatory total price and item list, and options that let the merchant request shipping information, billing address, or the payer's name, email, and phone number.

Merchants can also include a shipping type—shipping, delivery, or pickup—to give the payment app a hint about which labels to render in its interface.

const request = new PaymentRequest([{
  supportedMethods: 'https://bobpay.xyz/pay',
  data: {
    transactionId: '****'
  }
}], {
  displayItems: [{
    label: 'Anvil L/S Crew Neck - Grey M x1',
    amount: { currency: 'USD', value: '22.15' }
  }],
  total: {
    label: 'Total due',
    amount: { currency: 'USD', value : '22.15' }
  }
}, {
  requestShipping: true,
  requestBillingAddress: true,
  requestPayerEmail: true,
  requestPayerPhone: true,
  requestPayerName: true,
  shippingType: 'delivery'
});

Some payment handlers expect a transaction ID that they issued earlier. In those integrations, the merchant and payment handler's server typically coordinate in advance to reserve the total, which prevents a malicious customer from tampering with the price before validation. The ID is passed in the data property of the PaymentMethodData object.

With the transaction information in hand, the browser performs a discovery pass over the payment methods specified in the request to locate a matching payment app. That way, the app is already resolved before the merchant calls show().

Displaying a usable payment button

A merchant may support many payment methods, but showing a button for one the customer cannot use just adds friction. The canMakePayment() method on the PaymentRequest instance tells the merchant whether a suitable payment app is available—either an installed platform-specific app or a web-based payment app that is ready to be registered through just-in-time installation. The merchant can then hide unusable buttons or surface a fallback.

const canMakePayment = await request.canMakePayment();
if (!canMakePayment) {
  // Fallback to other means of payment or hide the button.
}

Launching the payment UI

When the customer taps the payment button, the merchant calls show() on the PaymentRequest. The browser immediately begins launching the payment UI.

When the final total isn't known until a server call returns, the merchant can defer the UI by passing a promise to show(). The browser shows a loading spinner until the promise resolves and the transaction can start.

const getTotalAmount = async () => {
  // Fetch the total amount from the server, etc.
};

try {
  const result = await request.show(getTotalAmount());
  // Process the result…
} catch(e) {
  handleError(e);
}

Inside the payment app

The browser may launch either a platform-specific or a web-based payment app. While the app's implementation is up to the developer, the events flowing between the app and the merchant, and the data those events carry, are standardized.

At launch the app receives the original transaction data: payment method information, the total, and the payment options. It uses these to label its user interface accordingly.

Handling customer changes

During checkout, a customer may change the payment method, shipping address, or shipping option, and the merchant must respond to those changes by recomputing the totals. Four event types cover these adjustments.

Payment method change

If a payment app supports multiple methods and a merchant wants to offer a discount for certain ones, the payment method change event supplies the new method and lets the merchant adjust the total accordingly.

request.addEventListener('paymentmethodchange', e => {
  e.updateWith({
    // Add discount etc.
  });
});

Shipping address change

Payment apps can supply a saved shipping address, sparing customers from re-entering it on every merchant site. If the address changes mid-transaction, the merchant receives a 'shippingaddresschange' event, recalculates shipping and the total, and returns the updated details. When an address cannot be serviced, the merchant can attach an error message to the returned details.

request.addEventListener('shippingaddresschange', e => {
  e.updateWith({
    // Update the details
  });
});

Shipping option change

When a merchant offers a choice of shipping services, such as standard or express, those options surface in the payment app. Changing the selection sends a 'shippingoptionchange' event so the merchant can update the total. Merchants can also adapt the available options dynamically—for instance, offering a different set to domestic versus international customers based on the shipping address.

request.addEventListener('shippingoptionchange', e => {
  e.updateWith({
    // Update the details
  });
});

Merchant validation

A payment app may run its own merchant validation before the flow proceeds. The merchant validation event simply tells the merchant which URL to use for that self-validation.

request.addEventListener('merchantvalidation', e => {
  e.updateWith({
    // Use `e.validateURL` to validate
  });
});

Completing or retrying

Once the customer authorizes payment, the promise returned by show() resolves to a PaymentResponse that contains the result details, shipping address, shipping option, and contact information. The browser UI keeps a spinner visible until the merchant confirms the outcome; the transaction is not yet done.

If the payment app terminates—for example, on a failure—the promise rejects and the transaction is aborted.

The details field holds the credential object from the payment app, which the merchant uses to process or validate the payment against their own systems. That step is entirely at the merchant's discretion.

After determining success or failure, the merchant calls .complete() on the PaymentResponse to close the flow and clear the loader, or retry() to give the customer another chance.

async function doPaymentRequest() {
  try {
    const request = new PaymentRequest(methodData, details, options);
    const response = await request.show();
    await validateResponse(response);
  } catch (err) {
    // AbortError, SecurityError
    console.error(err);
  }
}

async function validateResponse(response) {
  try {
    const errors = await checkAllValuesAreGood(response);
    if (errors.length) {
      await response.retry(errors);
      return validateResponse(response);
    }
    await response.complete("success");
  } catch (err) {
    // Something went wrong…
    await response.complete("fail");
  }
}
// Must be called as a result of a click
// or some explicit user action.
doPaymentRequest();