Skip to content

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.

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.

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' } },
],
};
// 1. Construct — does not show any UI yet
const 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;
}
}

The Payment Request API is only available in secure contexts — HTTPS or localhost. On plain HTTP the PaymentRequest constructor is undefined.

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() 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.

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 one shippingOption in details.
  • requestPayerName, requestPayerEmail, requestPayerPhone — requests contact fields from stored profiles.

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 / PlatformSupportSinceConfidenceSourceNotes
Chrome (Android)✅ yes61highref
Chrome (Desktop)✅ yes61highref
Edge (Desktop)✅ yes79highref
Safari (iOS)✅ yes11.1highrefBacked by Apple Pay as the payment method.
Safari (macOS)✅ yes11.1highrefBacked by Apple Pay.
Firefox (Desktop)❌ nomediumrefImplementation shipped then disabled; not available by default.
Samsung Internet✅ yes7.0mediumref

Ecosystem & commercial policy

EntityTypeContextStatusSponsoredNotes
Apple Paypayment_sdkSafari / iOS✅ supportedNoWorks in Safari via Payment Request; merchant-domain verification required.
Stripepayment_sdkCross-browser✅ supportedNoStripe wraps Payment Request as the Payment Request Button / Payment Element.
Google Play billingstore_policyGoogle Play TWA❌ unsupportedNoTWAs distributing digital goods must use Play Billing, not Payment Request, per Play policy.

Source: spec · MDN · Last verified 2026-06-24 · Confidence: high

See /compatibility/ for current per-browser data.

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.
  • Only construct PaymentRequest and call show() over HTTPS.
  • Call show() inside a user-gesture event handler.
  • Call response.complete('success' | 'fail') after server-side processing.
  • Catch AbortError from show() 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 PaymentResponse data — pass it to your backend immediately.