Glossary

Payment Request API

Payment Request API is a W3C browser API that lets a merchant page ask the browser to run the payment step, showing a native sheet instead of a checkout form. It is exposed as the PaymentRequest constructor in secure contexts. The browser sits between the payee, the payer and the payment method, and returns the result to the page as a PaymentResponse. The spec is a W3C Candidate Recommendation Draft dated 22 June 2026, and the API does not move money itself.

How it works

You build a request from three arguments: methodData, details and an optional options object. Each methodData entry has a supportedMethods identifier and optional data. The identifier is either a standardized one or a URL controlled by the payment method provider. Duplicate identifiers throw a RangeError.

The details object holds the id, the displayItems and a required total. Every price is a PaymentCurrencyAmount, a pair of currency and value. The value must be a string that matches ^-?[0-9]+(\.[0-9]+)?$, and the total can never be negative. The spec does not rewrite "55" into "55.00", so format values yourself.

The options object asks for extra data with requestPayerName, requestPayerEmail, requestPayerPhone and requestShipping.

Then the flow is:

  • Call request.show() from a click or tap. It needs transient activation, otherwise the browser may reject with SecurityError.
  • Optionally call canMakePayment() first. A true result does not mean the user has a ready card or wallet.
  • Update the sheet from the shippingaddresschange, shippingoptionchange and paymentmethodchange events with event.updateWith().
  • When show() resolves, validate the PaymentResponse on your server, then call response.complete() with "success", "fail" or "unknown".

Only one payment sheet can be shown per browser tab. A show() while the document is not visible rejects with AbortError, and an empty list of matching payment handlers rejects with NotSupportedError. Cross-origin iframes need allow="payment".

const MONEY = /^-?[0-9]+(\.[0-9]+)?$/;
function checkTotal(amount) {
  if (!/^[A-Za-z]{3}$/.test(amount.currency)) throw new RangeError("invalid currency " + amount.currency);
  if (!MONEY.test(amount.value)) throw new TypeError("invalid amount " + JSON.stringify(amount.value));
  if (amount.value.startsWith("-")) throw new TypeError("total cannot be negative");
  return amount;
}
for (const a of [{ currency: "OMR", value: "1.234" }, { currency: "USD", value: "55" }, { currency: "USD", value: "$5.00" }, { currency: "USD", value: "1,000.00" }, { currency: "USD", value: "-5.00" }, { currency: "US", value: "5.00" }]) {
  try { checkTotal(a); console.log("ok   ", JSON.stringify(a)); }
  catch (e) { console.log(e.name + ":", e.message); }
}
console.log("sum 55.00 + 5.00 =", (55.00 + 5.00).toFixed(2), "| 0.1 + 0.2 =", 0.1 + 0.2);

Output:

ok    {"currency":"OMR","value":"1.234"}
ok    {"currency":"USD","value":"55"}
TypeError: invalid amount "$5.00"
TypeError: invalid amount "1,000.00"
TypeError: total cannot be negative
RangeError: invalid currency US
sum 55.00 + 5.00 = 60.00 | 0.1 + 0.2 = 0.30000000000000004

The currency check above is a simplification of the spec rule, which calls IsWellFormedCurrencyCode.

Which browsers support the Payment Request API?

MDN compatibility data lists Chrome 60, Edge 15 and Safari 11.1 as the first versions with PaymentRequest. Firefox lists version 55 only behind the dom.payments.request.enabled preference. Check "PaymentRequest" in window and keep a normal checkout form as the fallback.

Does the Payment Request API process payments?

No, the API only collects the user's choice and passes details to your code. Response details come from the chosen payment method, so the shape of the details object differs per method. Your server still sends the data to a payment processor and confirms the charge.

Common pitfalls

  • Passing numbers as amounts: value must be a string. Floating point sums like 0.1 + 0.2 print 0.30000000000000004, so build strings with a fixed decimal step or integer cents.
  • Calling show() outside a user gesture: the browser can reject with SecurityError. Wire it to a button.
  • Opening two sheets: a second show() in the same tab is not allowed. Disable the pay button while one request is pending.
  • Forgetting the shipping total: if a shipping option is preselected, your total must already include its cost, because no event fires until the user changes it.
  • Trusting the response in the browser: treat details as untrusted input and verify it server side.
  • Embedding the checkout in an iframe: a cross-origin frame needs the allow="payment" attribute.

Related terms

  • HTTPS — the secure context the API requires
  • Permissions-Policy header — the payment feature can be allowed or blocked per frame
  • DOM — the event model that carries shipping and method change events
  • Promise — show(), canMakePayment() and complete() all return promises
  • Async/await — the usual way to write the payment flow

See also

  • Term: Promise — the result type of show(), canMakePayment() and complete()