JavaScript APIs

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.

By JavaScript Document Team
payment-apiweb-apisecommercesecuritypayments

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

  1. 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;
  }
}
  1. 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();
    });
  }
}
  1. 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 };
    }
  }
}
  1. 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.