JavaScript Payment Request API: Seamless Web Payments
Master the Payment Request API to create streamlined checkout experiences. Learn to implement secure payment flows, handle multiple payment methods, and optimize conversions.
The Payment Request API provides a standardized way to collect payment information from users, supporting various payment methods while reducing checkout friction. It creates a consistent payment experience across different websites and platforms.
Understanding Payment Request API
The Payment Request API streamlines the checkout process by leveraging browser-stored payment information and providing a unified payment interface.
Basic Payment Request Setup
// Check if Payment Request is supported
if (window.PaymentRequest) {
console.log('Payment Request API is supported');
} else {
console.log('Payment Request API is not supported');
}
// Basic payment request
async function makePaymentRequest() {
// Supported payment methods
const supportedPaymentMethods = [
{
supportedMethods: 'basic-card',
data: {
supportedNetworks: ['visa', 'mastercard', 'amex', 'discover'],
supportedTypes: ['credit', 'debit', 'prepaid'],
},
},
];
// Payment details
const paymentDetails = {
total: {
label: 'Total',
amount: {
currency: 'USD',
value: '100.00',
},
},
displayItems: [
{
label: 'Original price',
amount: { currency: 'USD', value: '110.00' },
},
{
label: 'Discount',
amount: { currency: 'USD', value: '-10.00' },
},
],
};
// Payment options
const options = {
requestPayerName: true,
requestPayerEmail: true,
requestPayerPhone: true,
requestShipping: true,
shippingType: 'shipping', // 'shipping', 'delivery', or 'pickup'
};
try {
const request = new PaymentRequest(
supportedPaymentMethods,
paymentDetails,
options
);
// Show payment UI
const paymentResponse = await request.show();
// Process the payment
await processPayment(paymentResponse);
// Complete the payment
await paymentResponse.complete('success');
} catch (error) {
console.error('Payment failed:', error);
}
}
// Process payment response
async function processPayment(paymentResponse) {
// Extract payment details
const {
methodName,
details,
payerName,
payerEmail,
payerPhone,
shippingAddress,
shippingOption,
} = paymentResponse;
// Send to payment processor
const response = await fetch('/api/process-payment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
methodName,
details,
payerInfo: {
name: payerName,
email: payerEmail,
phone: payerPhone,
},
shipping: {
address: shippingAddress,
option: shippingOption,
},
}),
});
if (!response.ok) {
throw new Error('Payment processing failed');
}
return response.json();
}
Advanced Payment Configuration
// Complete payment implementation
class PaymentHandler {
constructor() {
this.supportedMethods = this.getSupportedMethods();
this.shippingOptions = this.getShippingOptions();
}
getSupportedMethods() {
return [
// Basic card payments
{
supportedMethods: 'basic-card',
data: {
supportedNetworks: ['visa', 'mastercard', 'amex', 'discover', 'jcb'],
supportedTypes: ['credit', 'debit', 'prepaid'],
},
},
// Google Pay
{
supportedMethods: 'https://google.com/pay',
data: {
environment: 'TEST',
apiVersion: 2,
apiVersionMinor: 0,
merchantInfo: {
merchantId: '12345678901234567890',
merchantName: 'Example Merchant',
},
allowedPaymentMethods: [
{
type: 'CARD',
parameters: {
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
allowedCardNetworks: ['MASTERCARD', 'VISA'],
},
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gateway: 'example',
gatewayMerchantId: 'exampleGatewayMerchantId',
},
},
},
],
},
},
// Apple Pay
{
supportedMethods: 'https://apple.com/apple-pay',
data: {
version: 3,
merchantIdentifier: 'merchant.com.example',
merchantCapabilities: [
'supports3DS',
'supportsCredit',
'supportsDebit',
],
supportedNetworks: ['amex', 'discover', 'masterCard', 'visa'],
countryCode: 'US',
},
},
];
}
getShippingOptions() {
return [
{
id: 'standard',
label: 'Standard Shipping (5-7 days)',
amount: { currency: 'USD', value: '5.00' },
selected: true,
},
{
id: 'express',
label: 'Express Shipping (2-3 days)',
amount: { currency: 'USD', value: '15.00' },
},
{
id: 'overnight',
label: 'Overnight Shipping',
amount: { currency: 'USD', value: '30.00' },
},
];
}
async canMakePayment() {
try {
const request = new PaymentRequest(this.supportedMethods, {
total: { label: 'Test', amount: { currency: 'USD', value: '0.01' } },
});
const canPay = await request.canMakePayment();
return canPay;
} catch (error) {
console.error('Error checking payment availability:', error);
return false;
}
}
createPaymentDetails(cart, shippingOptionId = 'standard') {
const subtotal = this.calculateSubtotal(cart);
const tax = this.calculateTax(subtotal);
const shippingOption = this.shippingOptions.find(
(opt) => opt.id === shippingOptionId
);
const shipping = parseFloat(shippingOption.amount.value);
const total = subtotal + tax + shipping;
return {
id: `order-${Date.now()}`,
displayItems: [
...cart.items.map((item) => ({
label: `${item.name} (×${item.quantity})`,
amount: {
currency: 'USD',
value: (item.price * item.quantity).toFixed(2),
},
})),
{
label: 'Subtotal',
amount: { currency: 'USD', value: subtotal.toFixed(2) },
},
{
label: 'Tax',
amount: { currency: 'USD', value: tax.toFixed(2) },
},
{
label: shippingOption.label,
amount: shippingOption.amount,
},
],
total: {
label: 'Total',
amount: {
currency: 'USD',
value: total.toFixed(2),
},
},
shippingOptions: this.shippingOptions,
};
}
calculateSubtotal(cart) {
return cart.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
}
calculateTax(subtotal, rate = 0.08) {
return subtotal * rate;
}
async requestPayment(cart, options = {}) {
const paymentDetails = this.createPaymentDetails(cart);
const paymentOptions = {
requestPayerName: true,
requestPayerEmail: true,
requestPayerPhone: options.requirePhone || false,
requestShipping: options.requireShipping !== false,
shippingType: options.shippingType || 'shipping',
...options,
};
const request = new PaymentRequest(
this.supportedMethods,
paymentDetails,
paymentOptions
);
// Handle shipping address changes
if (paymentOptions.requestShipping) {
request.addEventListener('shippingaddresschange', (event) => {
event.updateWith(this.updateShippingAddress(event, cart));
});
request.addEventListener('shippingoptionchange', (event) => {
event.updateWith(this.updateShippingOption(event, cart));
});
}
try {
const response = await request.show();
return response;
} catch (error) {
if (error.name === 'AbortError') {
console.log('Payment cancelled by user');
} else {
console.error('Payment error:', error);
}
throw error;
}
}
async updateShippingAddress(event, cart) {
const address = event.target.shippingAddress;
// Validate shipping address
const validation = await this.validateShippingAddress(address);
if (!validation.valid) {
return {
error: validation.error,
shippingOptions: [],
total: cart.total,
};
}
// Update shipping options based on address
const shippingOptions = await this.getShippingOptionsForAddress(address);
const selectedOption = shippingOptions[0];
// Recalculate total with new shipping
const details = this.createPaymentDetails(cart, selectedOption.id);
return {
...details,
shippingOptions,
};
}
async updateShippingOption(event, cart) {
const selectedOptionId = event.target.shippingOption;
const details = this.createPaymentDetails(cart, selectedOptionId);
return details;
}
async validateShippingAddress(address) {
// Check if we ship to this country
const supportedCountries = ['US', 'CA', 'GB', 'AU'];
if (!supportedCountries.includes(address.country)) {
return {
valid: false,
error: `Sorry, we don't ship to ${address.country}`,
};
}
// Additional validation
if (!address.postalCode || !address.city) {
return {
valid: false,
error: 'Please provide a complete address',
};
}
return { valid: true };
}
async getShippingOptionsForAddress(address) {
// Customize shipping options based on location
const options = [...this.shippingOptions];
// Remove overnight shipping for certain areas
if (address.country !== 'US') {
return options.filter((opt) => opt.id !== 'overnight');
}
return options;
}
}
Practical Applications
E-commerce Checkout
class EcommerceCheckout {
constructor() {
this.paymentHandler = new PaymentHandler();
this.cart = this.loadCart();
this.init();
}
init() {
this.checkPaymentAvailability();
this.setupEventListeners();
}
async checkPaymentAvailability() {
const canPay = await this.paymentHandler.canMakePayment();
if (canPay) {
this.showPaymentRequestButton();
} else {
this.showTraditionalCheckout();
}
}
showPaymentRequestButton() {
const button = document.getElementById('payment-request-button');
button.style.display = 'block';
button.addEventListener('click', () => this.handlePaymentRequest());
}
showTraditionalCheckout() {
document.getElementById('traditional-checkout').style.display = 'block';
}
async handlePaymentRequest() {
try {
// Show loading state
this.showLoading();
// Request payment
const paymentResponse = await this.paymentHandler.requestPayment(
this.cart,
{
requirePhone: true,
shippingType: 'delivery',
}
);
// Process payment
const result = await this.processPayment(paymentResponse);
if (result.success) {
await paymentResponse.complete('success');
this.onPaymentSuccess(result);
} else {
await paymentResponse.complete('fail');
this.onPaymentFailure(result.error);
}
} catch (error) {
this.hideLoading();
if (error.name !== 'AbortError') {
this.showError('Payment failed. Please try again.');
}
}
}
async processPayment(paymentResponse) {
try {
const orderData = this.prepareOrderData(paymentResponse);
const response = await fetch('/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(orderData),
});
if (!response.ok) {
throw new Error('Order processing failed');
}
const result = await response.json();
return result;
} catch (error) {
console.error('Payment processing error:', error);
return {
success: false,
error: error.message,
};
}
}
prepareOrderData(paymentResponse) {
return {
orderId: `ORDER-${Date.now()}`,
payment: {
method: paymentResponse.methodName,
details: paymentResponse.details,
},
customer: {
name: paymentResponse.payerName,
email: paymentResponse.payerEmail,
phone: paymentResponse.payerPhone,
},
shipping: {
address: this.formatAddress(paymentResponse.shippingAddress),
option: paymentResponse.shippingOption,
},
items: this.cart.items,
totals: this.calculateTotals(),
};
}
formatAddress(address) {
if (!address) return null;
return {
recipient: address.recipient,
addressLine: address.addressLine,
city: address.city,
region: address.region,
postalCode: address.postalCode,
country: address.country,
phone: address.phone,
};
}
calculateTotals() {
const subtotal = this.paymentHandler.calculateSubtotal(this.cart);
const tax = this.paymentHandler.calculateTax(subtotal);
const shipping = this.getSelectedShippingCost();
return {
subtotal,
tax,
shipping,
total: subtotal + tax + shipping,
};
}
getSelectedShippingCost() {
// Get from payment response or default
return 5.0;
}
onPaymentSuccess(result) {
this.clearCart();
this.hideLoading();
// Redirect to success page
window.location.href = `/order-confirmation/${result.orderId}`;
}
onPaymentFailure(error) {
this.hideLoading();
this.showError(error || 'Payment failed. Please try again.');
}
loadCart() {
// Load cart from storage or API
return {
items: [
{
id: '1',
name: 'Product 1',
price: 50.0,
quantity: 2,
},
{
id: '2',
name: 'Product 2',
price: 30.0,
quantity: 1,
},
],
};
}
clearCart() {
localStorage.removeItem('cart');
}
showLoading() {
document.getElementById('loading-overlay').style.display = 'flex';
}
hideLoading() {
document.getElementById('loading-overlay').style.display = 'none';
}
showError(message) {
const errorElement = document.getElementById('error-message');
errorElement.textContent = message;
errorElement.style.display = 'block';
setTimeout(() => {
errorElement.style.display = 'none';
}, 5000);
}
setupEventListeners() {
// Fallback checkout form
const form = document.getElementById('checkout-form');
if (form) {
form.addEventListener('submit', (e) => {
e.preventDefault();
this.handleTraditionalCheckout();
});
}
}
async handleTraditionalCheckout() {
// Fallback to traditional checkout flow
const formData = new FormData(document.getElementById('checkout-form'));
// Process traditional checkout...
}
}
Donation Platform
class DonationPayment {
constructor() {
this.presetAmounts = [10, 25, 50, 100];
this.init();
}
init() {
this.setupAmountButtons();
this.setupCustomAmount();
this.checkPaymentAvailability();
}
setupAmountButtons() {
const container = document.getElementById('amount-buttons');
this.presetAmounts.forEach((amount) => {
const button = document.createElement('button');
button.className = 'donation-amount';
button.textContent = `$${amount}`;
button.dataset.amount = amount;
button.addEventListener('click', () => this.selectAmount(amount));
container.appendChild(button);
});
}
setupCustomAmount() {
const input = document.getElementById('custom-amount');
const button = document.getElementById('custom-amount-button');
button.addEventListener('click', () => {
const amount = parseFloat(input.value);
if (amount > 0) {
this.selectAmount(amount);
}
});
}
selectAmount(amount) {
this.selectedAmount = amount;
this.updateUI(amount);
// Enable payment button
document.getElementById('donate-button').disabled = false;
}
updateUI(amount) {
// Update selected state
document.querySelectorAll('.donation-amount').forEach((btn) => {
btn.classList.toggle(
'selected',
parseFloat(btn.dataset.amount) === amount
);
});
// Update display
document.getElementById('selected-amount').textContent =
`$${amount.toFixed(2)}`;
}
async checkPaymentAvailability() {
const supportedMethods = [
{
supportedMethods: 'basic-card',
data: {
supportedNetworks: ['visa', 'mastercard', 'amex'],
supportedTypes: ['credit', 'debit'],
},
},
];
try {
const request = new PaymentRequest(supportedMethods, {
total: { label: 'Test', amount: { currency: 'USD', value: '0.01' } },
});
const canPay = await request.canMakePayment();
if (canPay) {
this.setupPaymentRequestButton();
}
} catch (error) {
console.log('Payment Request not available');
}
}
setupPaymentRequestButton() {
const button = document.getElementById('donate-button');
button.addEventListener('click', () => this.processDonation());
}
async processDonation() {
if (!this.selectedAmount) {
alert('Please select a donation amount');
return;
}
const supportedMethods = [
{
supportedMethods: 'basic-card',
data: {
supportedNetworks: ['visa', 'mastercard', 'amex'],
supportedTypes: ['credit', 'debit'],
},
},
];
const paymentDetails = {
id: `donation-${Date.now()}`,
displayItems: [
{
label: 'Donation',
amount: {
currency: 'USD',
value: this.selectedAmount.toFixed(2),
},
},
{
label: 'Processing Fee',
amount: {
currency: 'USD',
value: '0.00',
},
},
],
total: {
label: 'Total Donation',
amount: {
currency: 'USD',
value: this.selectedAmount.toFixed(2),
},
},
};
const options = {
requestPayerName: true,
requestPayerEmail: true,
};
try {
const request = new PaymentRequest(
supportedMethods,
paymentDetails,
options
);
const paymentResponse = await request.show();
// Process donation
const result = await this.submitDonation(paymentResponse);
if (result.success) {
await paymentResponse.complete('success');
this.showThankYou(paymentResponse.payerName, this.selectedAmount);
} else {
await paymentResponse.complete('fail');
this.showError('Donation failed. Please try again.');
}
} catch (error) {
if (error.name !== 'AbortError') {
this.showError('Payment failed. Please try again.');
}
}
}
async submitDonation(paymentResponse) {
try {
const response = await fetch('/api/donations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: this.selectedAmount,
currency: 'USD',
payment: {
method: paymentResponse.methodName,
details: paymentResponse.details,
},
donor: {
name: paymentResponse.payerName,
email: paymentResponse.payerEmail,
},
}),
});
return await response.json();
} catch (error) {
return { success: false, error: error.message };
}
}
showThankYou(name, amount) {
const modal = document.getElementById('thank-you-modal');
const message = modal.querySelector('.message');
message.textContent = `Thank you, ${name}! Your donation of $${amount.toFixed(2)} has been received.`;
modal.style.display = 'flex';
// Send confirmation email would happen server-side
}
showError(message) {
alert(message);
}
}
Subscription Management
class SubscriptionPayment {
constructor() {
this.plans = [
{
id: 'basic',
name: 'Basic',
price: 9.99,
features: ['Feature 1', 'Feature 2'],
},
{
id: 'pro',
name: 'Pro',
price: 19.99,
features: ['All Basic features', 'Feature 3', 'Feature 4'],
},
{
id: 'enterprise',
name: 'Enterprise',
price: 49.99,
features: ['All Pro features', 'Feature 5', 'Priority support'],
},
];
}
async subscribeToPlan(planId) {
const plan = this.plans.find((p) => p.id === planId);
if (!plan) return;
const supportedMethods = [
{
supportedMethods: 'basic-card',
data: {
supportedNetworks: ['visa', 'mastercard', 'amex'],
supportedTypes: ['credit', 'debit'],
},
},
];
const paymentDetails = {
id: `subscription-${planId}-${Date.now()}`,
displayItems: [
{
label: `${plan.name} Plan (Monthly)`,
amount: {
currency: 'USD',
value: plan.price.toFixed(2),
},
},
],
total: {
label: 'Monthly Subscription',
amount: {
currency: 'USD',
value: plan.price.toFixed(2),
},
},
modifiers: [
{
supportedMethods: 'basic-card',
total: {
label: 'Monthly Subscription',
amount: {
currency: 'USD',
value: plan.price.toFixed(2),
},
},
additionalDisplayItems: [
{
label: 'Recurring monthly charge',
amount: {
currency: 'USD',
value: plan.price.toFixed(2),
},
},
],
},
],
};
const options = {
requestPayerName: true,
requestPayerEmail: true,
requestBillingAddress: true,
};
try {
const request = new PaymentRequest(
supportedMethods,
paymentDetails,
options
);
// Add billing address change handler
request.addEventListener('paymentmethodchange', (event) => {
event.updateWith(this.handlePaymentMethodChange(event, plan));
});
const paymentResponse = await request.show();
// Create subscription
const result = await this.createSubscription(paymentResponse, plan);
if (result.success) {
await paymentResponse.complete('success');
this.onSubscriptionSuccess(result);
} else {
await paymentResponse.complete('fail');
this.onSubscriptionFailure(result.error);
}
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Subscription failed:', error);
this.showError('Subscription failed. Please try again.');
}
}
}
async handlePaymentMethodChange(event, plan) {
// Could offer discounts for certain payment methods
const methodName = event.methodName;
let discount = 0;
// Example: 5% discount for certain cards
if (
methodName === 'basic-card' &&
event.methodDetails?.cardNumber?.startsWith('4')
) {
discount = plan.price * 0.05;
}
const finalPrice = plan.price - discount;
const details = {
displayItems: [
{
label: `${plan.name} Plan (Monthly)`,
amount: { currency: 'USD', value: plan.price.toFixed(2) },
},
],
total: {
label: 'Monthly Subscription',
amount: { currency: 'USD', value: finalPrice.toFixed(2) },
},
};
if (discount > 0) {
details.displayItems.push({
label: 'Payment method discount',
amount: { currency: 'USD', value: `-${discount.toFixed(2)}` },
});
}
return details;
}
async createSubscription(paymentResponse, plan) {
try {
const response = await fetch('/api/subscriptions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
planId: plan.id,
payment: {
method: paymentResponse.methodName,
details: paymentResponse.details,
billingAddress: paymentResponse.billingAddress,
},
customer: {
name: paymentResponse.payerName,
email: paymentResponse.payerEmail,
},
}),
});
return await response.json();
} catch (error) {
return { success: false, error: error.message };
}
}
onSubscriptionSuccess(result) {
// Redirect to account page
window.location.href = '/account?subscription=success';
}
onSubscriptionFailure(error) {
this.showError(error || 'Subscription failed');
}
showError(message) {
// Show error UI
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.textContent = message;
document.body.appendChild(errorDiv);
setTimeout(() => errorDiv.remove(), 5000);
}
}
Payment Method Management
class PaymentMethodManager {
constructor() {
this.savedMethods = [];
this.init();
}
async init() {
await this.loadSavedMethods();
this.setupUI();
}
async loadSavedMethods() {
try {
const response = await fetch('/api/payment-methods');
this.savedMethods = await response.json();
} catch (error) {
console.error('Failed to load payment methods:', error);
}
}
async addPaymentMethod() {
const supportedMethods = [
{
supportedMethods: 'basic-card',
data: {
supportedNetworks: ['visa', 'mastercard', 'amex'],
supportedTypes: ['credit', 'debit'],
},
},
];
// Minimal payment request just to collect card details
const paymentDetails = {
total: {
label: 'Card Verification',
amount: { currency: 'USD', value: '0.00' },
},
};
const options = {
requestPayerName: true,
requestBillingAddress: true,
};
try {
const request = new PaymentRequest(
supportedMethods,
paymentDetails,
options
);
const paymentResponse = await request.show();
// Save payment method
const result = await this.savePaymentMethod(paymentResponse);
if (result.success) {
await paymentResponse.complete('success');
this.onMethodAdded(result.method);
} else {
await paymentResponse.complete('fail');
this.showError('Failed to save payment method');
}
} catch (error) {
if (error.name !== 'AbortError') {
this.showError('Failed to add payment method');
}
}
}
async savePaymentMethod(paymentResponse) {
try {
const response = await fetch('/api/payment-methods', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: paymentResponse.methodName,
details: {
// Only save non-sensitive data
cardType: paymentResponse.details.cardSecurityCode
? 'verified'
: 'unverified',
billingAddress: paymentResponse.billingAddress,
},
name: paymentResponse.payerName,
}),
});
const result = await response.json();
return result;
} catch (error) {
return { success: false, error: error.message };
}
}
onMethodAdded(method) {
this.savedMethods.push(method);
this.updateMethodsList();
this.showSuccess('Payment method added successfully');
}
updateMethodsList() {
const container = document.getElementById('payment-methods-list');
container.innerHTML = '';
this.savedMethods.forEach((method) => {
const methodElement = this.createMethodElement(method);
container.appendChild(methodElement);
});
}
createMethodElement(method) {
const div = document.createElement('div');
div.className = 'payment-method-item';
div.innerHTML = `
<div class="method-info">
<span class="method-type">${method.type}</span>
<span class="method-last4">•••• ${method.last4}</span>
<span class="method-expiry">${method.expiry}</span>
</div>
<div class="method-actions">
<button onclick="paymentMethodManager.setDefault('${method.id}')">
${method.isDefault ? 'Default' : 'Set as Default'}
</button>
<button onclick="paymentMethodManager.remove('${method.id}')">Remove</button>
</div>
`;
return div;
}
async setDefault(methodId) {
try {
const response = await fetch(`/api/payment-methods/${methodId}/default`, {
method: 'PUT',
});
if (response.ok) {
this.savedMethods.forEach((method) => {
method.isDefault = method.id === methodId;
});
this.updateMethodsList();
}
} catch (error) {
this.showError('Failed to update default payment method');
}
}
async remove(methodId) {
if (!confirm('Remove this payment method?')) return;
try {
const response = await fetch(`/api/payment-methods/${methodId}`, {
method: 'DELETE',
});
if (response.ok) {
this.savedMethods = this.savedMethods.filter((m) => m.id !== methodId);
this.updateMethodsList();
}
} catch (error) {
this.showError('Failed to remove payment method');
}
}
setupUI() {
const addButton = document.getElementById('add-payment-method');
addButton.addEventListener('click', () => this.addPaymentMethod());
this.updateMethodsList();
}
showError(message) {
this.showNotification(message, 'error');
}
showSuccess(message) {
this.showNotification(message, 'success');
}
showNotification(message, type) {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
}
}
Best Practices
- Always check for API support
function isPaymentRequestSupported() {
return window.PaymentRequest !== undefined;
}
async function checkPaymentAvailability() {
if (!isPaymentRequestSupported()) {
return false;
}
try {
const request = new PaymentRequest([{ supportedMethods: 'basic-card' }], {
total: { label: 'Test', amount: { currency: 'USD', value: '0.01' } },
});
return await request.canMakePayment();
} catch (error) {
return false;
}
}
- Provide fallback options
class PaymentFlow {
async init() {
const canUsePaymentRequest = await checkPaymentAvailability();
if (canUsePaymentRequest) {
this.showPaymentRequestButton();
} else {
this.showTraditionalForm();
}
// Always provide traditional option
this.setupFallbackLink();
}
showPaymentRequestButton() {
const button = document.getElementById('quick-pay');
button.style.display = 'block';
button.addEventListener('click', () => this.handlePaymentRequest());
}
showTraditionalForm() {
document.getElementById('payment-form').style.display = 'block';
}
setupFallbackLink() {
const link = document.getElementById('use-traditional');
link.addEventListener('click', (e) => {
e.preventDefault();
document.getElementById('quick-pay').style.display = 'none';
this.showTraditionalForm();
});
}
}
- Handle errors gracefully
async function safePaymentRequest(details, options) {
try {
const request = new PaymentRequest(
[{ supportedMethods: 'basic-card' }],
details,
options
);
const response = await request.show();
return { success: true, response };
} catch (error) {
if (error.name === 'AbortError') {
return { success: false, reason: 'cancelled' };
} else if (error.name === 'NotSupportedError') {
return { success: false, reason: 'not_supported' };
} else {
return { success: false, reason: 'error', error };
}
}
}
- Validate payment data
function validatePaymentDetails(details) {
// Validate total
if (!details.total || !details.total.amount || !details.total.amount.value) {
throw new Error('Invalid total amount');
}
const amount = parseFloat(details.total.amount.value);
if (isNaN(amount) || amount < 0) {
throw new Error('Invalid amount value');
}
// Validate currency
const validCurrencies = ['USD', 'EUR', 'GBP', 'CAD', 'AUD'];
if (!validCurrencies.includes(details.total.amount.currency)) {
throw new Error('Invalid currency');
}
// Validate display items
if (details.displayItems) {
details.displayItems.forEach((item) => {
if (!item.label || !item.amount || !item.amount.value) {
throw new Error('Invalid display item');
}
});
}
return true;
}
Conclusion
The Payment Request API revolutionizes online payments by providing a standardized, secure, and user-friendly checkout experience. By reducing friction in the payment process and supporting multiple payment methods, it can significantly improve conversion rates. While providing fallbacks for unsupported browsers is essential, implementing the Payment Request API creates a modern, efficient payment flow that benefits both users and businesses. As the API continues to evolve with support for more payment methods and features, it's becoming an essential tool for any e-commerce application.