The Payment Request API
Building a payment checkout form from scratch is tedious and error-prone. The Payment Request API gives you a standardized browser-native interface for collecting payment details, so you stop wrestling with credit card fields and let the browser handle the heavy lifting.
The API centers on a single object: PaymentRequest. You construct it with the payment methods you accept and the purchase details, then call .show() to surface the browser’s payment sheet.
Creating a Payment Request
The PaymentRequest constructor takes three arguments:
const request = new PaymentRequest(
methodData, // supported payment methods
details, // purchase amount and line items
options // optional: request name, email, shipping, etc.
);
methodData is an array describing what payment methods you accept. For basic card payments:
const methodData = [
{
supportedMethods: "basic-card",
data: {
supportedNetworks: ["visa", "mastercard", "amex"],
supportedTypes: ["credit", "debit"]
}
}
];
You can also use custom payment handler URLs:
const methodData = [
{ supportedMethods: "https://example.com/pay" }
];
details contains the purchase information:
const details = {
id: "order-abc-123",
displayItems: [
{
label: "Widget Pro",
amount: { currency: "USD", value: "29.99" }
},
{
label: "Shipping",
amount: { currency: "USD", value: "5.00" }
}
],
total: {
label: "Total",
amount: { currency: "USD", value: "34.99" }
}
};
The total field is what the user confirms. displayItems are optional line items that appear in the payment UI.
options is optional and controls what additional information you request:
const options = {
requestPayerName: true,
requestPayerEmail: true,
requestShipping: true,
shippingType: "shipping"
};
Showing the Payment Sheet
Call .show() to display the browser’s payment interface:
request.show().then((paymentResponse) => {
// User accepted — process the payment
const { methodName, details } = paymentResponse;
// Send details to your payment processor
processPayment(methodName, details).then(() => {
// Tell the browser the payment is done
paymentResponse.complete("success");
});
}).catch((err) => {
if (err.name === "NotSupportedError") {
// Browser doesn't support this payment method — fall back
window.location.href = "/legacy-checkout";
}
// Handle cancellation or other errors
});
.show() returns a promise. If the user accepts the payment, it resolves with a PaymentResponse object containing methodName (the payment method used) and details (method-specific response data from the payment handler).
Call .complete() when you are done — this dismisses the browser UI and tells it the payment succeeded or failed.
One critical constraint: you can only call .show() once per PaymentRequest instance. After the sheet closes (for any reason), you must create a fresh PaymentRequest for the next purchase.
Checking Payment Availability
Before showing a payment sheet on a live site, check whether the user’s browser can actually make a payment. .canMakePayment() returns a promise that resolves to a boolean:
const request = new PaymentRequest(methodData, {
total: { label: "Stub", amount: { currency: "USD", value: "0.01" } }
});
request.canMakePayment().then((canPay) => {
if (canPay) {
checkoutButton.textContent = "Pay now";
} else {
checkoutButton.textContent = "Set up payment";
}
});
Use this to customize your checkout button — “Pay now” when the user already has valid payment credentials, “Set up payment” when they need to add a card first. You can also use it as a gate: only use PaymentRequest if .canMakePayment() returns true, otherwise redirect to a legacy form.
Note that .canMakePayment() requires the same methodData structure between calls if you call it multiple times. For checking before you know all the prices, use a stub amount in details — the method data is what matters for the check.
Aborting a Payment
If you need to cancel an in-progress payment request:
request.abort().then(() => {
// Payment sheet closed
}).catch((err) => {
// Abort failed — sheet already closed or already complete
});
Feature Detection
Always check that the API exists before using it:
if (!window.PaymentRequest) {
// Redirect to legacy checkout
window.location.href = "/checkout/legacy";
}
The API has good cross-browser support (Chrome, Edge, Safari, Firefox), but it only works in secure contexts (HTTPS). It also does not work in iframes unless the iframe is same-origin and has the appropriate permissions policy.
Limitations
The Payment Request API handles the collection of payment information — it does not process the payment itself. After you get the PaymentResponse.details, you still need to send that data to your payment processor (Stripe, Square, PayPal, etc.) and handle the result.
Some browsers may not support all payment method identifiers. Always catch NotSupportedError and provide a fallback.
See Also
- javascript-service-workers — service workers that can act as web-based payment handlers
- javascript-indexeddb — storing transaction records and receipts locally after payment
- browser-storage — overview of browser storage for persisting checkout state