Capabilities · API
Payment Request API
Published
PaymentRequest hands the checkout sheet to the browser: the page lists the payment methods it accepts, the line items and total, and which contact fields it needs, and the browser collects a credential from a stored card, Apple Pay, Google Pay, or an installed payment handler and returns it as a PaymentResponse. The page never renders a card form and never sees raw card data unless the chosen method returns it.
Chrome 60 on desktop and Chrome 53 on Android shipped the constructor, Edge 15 and Safari 11.1 followed, and Android WebView added it in 136 (BCD api.PaymentRequest). Safari implements it for one method only, https://apple.com/apple-pay. Firefox 55 shipped an implementation behind dom.payments.request.enabled and dom.payments.request.supportedRegions and has not enabled it by default in any release.
Syntax
Section titled “Syntax”new PaymentRequest(methodData, details)new PaymentRequest(methodData, details, options)
request.canMakePayment()request.show()request.show(detailsPromise)request.abort()
response.complete()response.complete(result)response.retry(errorFields)canMakePayment() returns a Promise<boolean> without showing UI. show() returns a Promise<PaymentResponse> that fulfils when the user authorises payment; abort() and complete() return Promise<undefined>, and retry() returns a promise that fulfils when the user has corrected the flagged fields. The constructor is [SecureContext] and requires the payment Permissions Policy, whose default allowlist is 'self'; a cross-origin iframe needs allow="payment". A request object is single-use: after show() has been called once its [[state]] leaves "created", and a second show() or a later canMakePayment() rejects.
Parameters
Section titled “Parameters”The constructor takes two required dictionaries and one optional one.
| Parameter | Type | Required | Description |
|---|---|---|---|
methodData |
sequence<PaymentMethodData> |
Yes | One entry per accepted method. supportedMethods is a payment method identifier, either a URL such as https://google.com/pay or https://apple.com/apple-pay or a standardised string such as secure-payment-confirmation; data is an object the method’s own specification defines (merchant ids, allowed networks). The list must not be empty or contain the same identifier twice. |
details |
PaymentDetailsInit |
Yes | total (PaymentItem: label, amount) is required; amount is a PaymentCurrencyAmount with an ISO 4217 currency code and a decimal string value such as "29.99". Optional members: id (generated as a UUID when omitted), displayItems, shippingOptions (id, label, amount, selected), and modifiers (supportedMethods, total, additionalDisplayItems, data) that change the totals for one method. |
options |
PaymentOptions |
No | requestPayerName, requestPayerEmail, requestPayerPhone, requestShipping (booleans, default false) and shippingType ("shipping", "delivery", or "pickup", default "shipping"), which only changes the label the sheet shows. |
show(detailsPromise) accepts an optional Promise<PaymentDetailsUpdate> so the sheet can open before the server has finished pricing the cart. complete(result) takes "success", "fail", or "unknown" (the default). retry(errorFields) takes a PaymentValidationErrors dictionary with error, payer (name, email, phone), shippingAddress (an AddressErrors record keyed by address field), and paymentMethod.
Exceptions
Section titled “Exceptions”The specification defines the following failures. Where Chrome attaches a fixed message it is quoted from components/payments/core/error_strings.cc.
| Exception | Condition |
|---|---|
SecurityError |
Constructor: the document is not allowed to use the payment Permissions Policy. show(): the browser requires transient activation and finds none, or rate-limits the call. |
TypeError |
Constructor: methodData is empty; details.total is missing; an amount.value is not a valid decimal monetary string or the total is negative; two shipping options share an id. Chrome adds limits of 1024 shipping options, 1024 modifiers, and 1024 characters for details.id. |
RangeError |
Constructor: a payment method identifier is not a valid URL or standardised string, the same identifier appears twice, or a currency is not a well-formed ISO 4217 code. |
InvalidStateError |
show(): the document is not fully active, or [[state]] is not "created" (Chrome: Already called show() once). abort(): no show() or retry() is in progress, an abort() is already pending, or the browser cannot abort the current interaction. canMakePayment(): [[state]] is not "created" or the document is not fully active. complete() and retry(): the response is already complete or a retry() is pending. |
AbortError |
show(): the document is not visible, another payment sheet is already showing (Chrome: Another PaymentRequest UI is already showing in a different tab or window.), the user dismisses the sheet (Chrome: User closed the Payment Request UI.), or the page calls abort() (Chrome: The website has aborted the payment). complete(): the document stopped being fully active while the sheet was open. |
NotSupportedError |
show(): no listed method is supported. Chrome also rejects on origins that are neither localhost, file://, nor a cryptographic scheme with Only localhost, file://, and cryptographic scheme origins allowed. |
NotAllowedError |
canMakePayment(): the browser applies an anti-fingerprinting quota to repeated queries with varying method lists. |
OperationError |
show(): the selected payment handler reports an internal error, for example the OS terminated it. |
canMakePayment() resolving false is not an exception: it means no listed method is available on this device, which is the signal to show a hosted checkout instead.
Examples
Section titled “Examples”The examples use Google Pay as the method identifier; swap in your processor’s identifier and data object. Each one checks for the constructor first and falls back to a hosted checkout page when it is missing or no method is available.
Showing a wallet button only when canMakePayment() resolves true
Section titled “Showing a wallet button only when canMakePayment() resolves true”Build the request at page load, ask whether any listed method is available, and reveal the native button only on true. A false result or a missing constructor leaves the ordinary “Continue to checkout” link in place, which navigates to the processor’s hosted page.
const methodData = [{ supportedMethods: "https://google.com/pay", data: { environment: "PRODUCTION", apiVersion: 2, apiVersionMinor: 0 /* merchant config */ },}];const details = { total: { label: "Total", amount: { currency: "USD", value: "29.99" } },};
async function prepareCheckout() { const hosted = document.querySelector("#hosted-checkout"); const wallet = document.querySelector("#wallet-button");
if (!("PaymentRequest" in window)) { hosted.hidden = false; return; }
const request = new PaymentRequest(methodData, details); let available = false; try { available = await request.canMakePayment(); } catch (err) { console.warn(`${err.name}: ${err.message}`); // rejected when the browser's query quota is exceeded }
wallet.hidden = !available; hosted.hidden = available;}
prepareCheckout();Keep the request object you tested with: canMakePayment() does not consume it, so the click handler in the next example can call show() on the same instance.
Capturing on the server, then closing the sheet with complete()
Section titled “Capturing on the server, then closing the sheet with complete()”show() must run inside the click handler so transient activation is present. Send response.toJSON() to the server, and call complete("success") or complete("fail") as soon as the server answers; the sheet stays open until you do, and Chrome closes it on its own after a timeout as if complete() had been called with no argument.
const wallet = document.querySelector("#wallet-button");
wallet.addEventListener("click", async () => { const request = new PaymentRequest(methodData, details); let response; try { response = await request.show(); } catch (err) { if (err.name === "AbortError") return; // user closed the sheet location.assign("/checkout/hosted"); // any other rejection: fall back to the hosted page return; }
const result = await fetch("/api/charge", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(response.toJSON()), });
await response.complete(result.ok ? "success" : "fail"); if (result.ok) location.assign("/checkout/thanks");});complete("fail") shows the browser’s failure state and closes the sheet; the page is then responsible for telling the user what to do next.
Repricing when the shipping address changes
Section titled “Repricing when the shipping address changes”With requestShipping: true the sheet lets the user pick an address and fires shippingaddresschange on the request. Call event.updateWith() synchronously inside the listener with a promise for the new details; the sheet shows a spinner until it settles. The specification lets the browser redact parts of the address until the user authorises, so compute shipping from country, region, and postalCode rather than the street lines.
const request = new PaymentRequest(methodData, { total: { label: "Total", amount: { currency: "USD", value: "29.99" } }, shippingOptions: [ { id: "ground", label: "Ground (5 days)", amount: { currency: "USD", value: "0.00" }, selected: true }, ],}, { requestShipping: true });
request.addEventListener("shippingaddresschange", (event) => { const { country, postalCode } = event.target.shippingAddress; event.updateWith( fetch(`/api/shipping?country=${country}&postal=${postalCode}`) .then((r) => r.json()) .then((quote) => ({ total: { label: "Total", amount: { currency: "USD", value: quote.total } }, shippingOptions: quote.options, })) );});Returning shippingOptions: [] from the quote tells the sheet that nothing can be shipped to that address; add error: "We do not ship to this region" to the update so the user sees why.
See also
Section titled “See also”- Digital Goods API, the Play Billing method that plugs into
PaymentRequestinside a Trusted Web Activity - Trusted Web Activity (TWA)
- Monetize a PWA
- Payment Request API: show() method (w3.org)
- Payment Request API: constructor (w3.org)
- Introducing the Payment Request API for Apple Pay (webkit.org)
- How the Payment Request API works (web.dev)
- Chromium payments error strings (chromium.googlesource.com)
Specifications
| Specification | Status |
|---|---|
| Payment Request API | W3C |
| Payment Request API: constructor | W3C |
| Payment Request API: show() method | W3C |
| Payment Request API: canMakePayment() method | W3C |
- Legend
- Yes
- Partial
- Flag
- No
- Unknown
| Browser / Platform | Support | Versions | Confidence | Source | Notes |
|---|---|---|---|---|---|
| Chrome (Android) | Yes | 61 | medium | source | — |
| Chrome (Desktop) | Yes | 61 | medium | source | — |
| Edge (Desktop) | Yes | 79 | medium | source | — |
| Safari (iOS) | Yes | 11.1 | medium | source | 1 |
| Safari (macOS) | Yes | 11.1 | medium | source | 2 |
| Firefox (Desktop) | No | — | medium | source | 3 |
| Samsung Internet | Yes | 7.0 | medium | source | — |
- Backed by Apple Pay as the payment method.
- Backed by Apple Pay.
- Implementation shipped then disabled; not available by default.
Ecosystem & commercial policy
| Entity | Type | Context | Status | Sponsored | Notes |
|---|---|---|---|---|---|
| Apple Pay | payment_sdk | Safari / iOS | Yes | No | Works in Safari via Payment Request; merchant-domain verification required. |
| Stripe | payment_sdk | Cross-browser | Yes | No | Stripe wraps Payment Request as the Payment Request Button / Payment Element. |
| Google Play billing | store_policy | Google Play TWA | No | No | TWAs distributing digital goods must use Play Billing, not Payment Request, per Play policy. |