Payment Request API: browser-mediated checkout
In one line: The Payment Request API lets the browser mediate a checkout flow — the browser presents a native payment sheet using stored cards, digital wallets, or other payment methods the user has registered — so you get a streamlined checkout without building a custom payment UI or handling raw card data on the page.
Core concepts
Section titled “Core concepts”Payment methods
Section titled “Payment methods”A PaymentRequest is constructed with an array of payment method identifiers that
describe which payment methods are acceptable. Two kinds exist:
- Standardised methods: URL-based identifiers like
"basic-card"(deprecated in favour of processor-specific methods) and platform-specific methods such as"https://apple.com/apple-pay"or"https://google.com/pay". - Processor-specific methods: the most common approach today. Pass the payment
processor’s own method identifier (e.g. Stripe’s
"https://stripe.com/pay"). The processor’s Payment Handler handles the negotiation.
Payment details
Section titled “Payment details”The second constructor argument describes what the user is paying for:
const details = { total: { label: 'Total', amount: { currency: 'USD', value: '29.99' } }, displayItems: [ { label: 'Pro plan', amount: { currency: 'USD', value: '29.99' } }, ],};Basic flow
Section titled “Basic flow”// 1. Construct — does not show any UI yetconst request = new PaymentRequest( [{ supportedMethods: 'https://google.com/pay', data: { /* processor config */ } }], details, { requestPayerName: true, requestPayerEmail: true });
// 2. Check whether at least one method can be used (optional but recommended)const canMakePayment = await request.canMakePayment();
// 3. Show the browser-native payment sheet (must be from a user gesture)try { const response = await request.show(); // PaymentResponse // 4. Process on server, then complete await processOnServer(response.toJSON()); await response.complete('success');} catch (err) { if (err.name === 'AbortError') { // User dismissed the sheet — not an error } else { throw err; }}Secure context (HTTPS)
Section titled “Secure context (HTTPS)”The Payment Request API is only available in secure contexts — HTTPS or localhost.
On plain HTTP the PaymentRequest constructor is undefined.
User-gesture requirement
Section titled “User-gesture requirement”request.show() must be invoked from a user activation (a click, tap, or similar
transient event). Calling it from a timer or non-interactive async chain throws a
SecurityError or is rejected with NotAllowedError in some browsers.
canMakePayment()
Section titled “canMakePayment()”canMakePayment() checks — without showing any UI — whether at least one of the
requested payment methods is available on the user’s device. Use it to decide whether
to surface a payment-request button or fall back to a redirect-based checkout:
const available = await request.canMakePayment();if (!available) { // Redirect to hosted checkout page}Note: some browsers throttle calls to canMakePayment() to prevent abuse.
Shipping and contact fields
Section titled “Shipping and contact fields”Pass optional flags in the PaymentOptions object (third constructor argument) to
request extra information from the user:
requestShipping: true— surfaces a shipping-address selector; requires at least oneshippingOptionindetails.requestPayerName,requestPayerEmail,requestPayerPhone— requests contact fields from stored profiles.
Completing the payment
Section titled “Completing the payment”After receiving a PaymentResponse, always call response.complete() with 'success'
or 'fail' to signal to the browser that processing is finished and the sheet can close.
Not calling complete() leaves the sheet in a pending state.
Browser & ecosystem support
Section titled “Browser & ecosystem support”| Browser / Platform | Support | Since | Confidence | Source | Notes |
|---|---|---|---|---|---|
| Chrome (Android) | ✅ yes | 61 | high | ref | — |
| Chrome (Desktop) | ✅ yes | 61 | high | ref | — |
| Edge (Desktop) | ✅ yes | 79 | high | ref | — |
| Safari (iOS) | ✅ yes | 11.1 | high | ref | Backed by Apple Pay as the payment method. |
| Safari (macOS) | ✅ yes | 11.1 | high | ref | Backed by Apple Pay. |
| Firefox (Desktop) | ❌ no | — | medium | ref | Implementation shipped then disabled; not available by default. |
| Samsung Internet | ✅ yes | 7.0 | medium | ref | — |
Ecosystem & commercial policy
| Entity | Type | Context | Status | Sponsored | Notes |
|---|---|---|---|---|---|
| Apple Pay | payment_sdk | Safari / iOS | ✅ supported | No | Works in Safari via Payment Request; merchant-domain verification required. |
| Stripe | payment_sdk | Cross-browser | ✅ supported | No | Stripe wraps Payment Request as the Payment Request Button / Payment Element. |
| Google Play billing | store_policy | Google Play TWA | ❌ unsupported | No | TWAs distributing digital goods must use Play Billing, not Payment Request, per Play policy. |
See /compatibility/ for current per-browser data.
Decision framework
Section titled “Decision framework”| Decision question | Recommended action | Rationale |
|---|---|---|
| Want to offer native-wallet checkout? | Use PaymentRequest with your processor’s payment method identifier. |
Avoids building custom card-input UI; uses stored credentials. |
| Need to know if a native method is available before showing a button? | Call canMakePayment() and gate the button on the result. |
Prevents showing a button that opens an empty or unsupported sheet. |
| User must pick a shipping address? | Set requestShipping: true and populate shippingOptions. |
The browser collects the address; your shippingaddresschange listener updates totals. |
| Targeting browsers without Payment Request support? | Provide a standard redirect to a hosted checkout page as fallback. | Safari on iOS and Chrome on Android both support it, but desktop adoption is uneven. |
Practical checklist
Section titled “Practical checklist”- Only construct
PaymentRequestand callshow()over HTTPS. - Call
show()inside a user-gesture event handler. - Call
response.complete('success' | 'fail')after server-side processing. - Catch
AbortErrorfromshow()silently — the user dismissed the sheet. - Use
canMakePayment()to decide whether to show a native-pay button vs. fallback UI. - Never log or store raw
PaymentResponsedata — pass it to your backend immediately.