Read More on Telerik Blogs
May 02, 2025 Web
Get A Free Trial

Imagine trying to buy something online, maybe on your favorite ecommerce store. After surfing and searching, you finally find the perfect item. You add it to your cart and try to check out, but suddenly, you’re hit with a long and boring form requesting your billing address, contact information and, most importantly, card details. Without the convenience of auto-fills that we have come to expect, such an experience can be frustrating and clunky.

Now, imagine if your browser simplifies this process by securely pulling the payment details you saved, eliminating the need to fill out complex forms. That is exactly what the Payment Request API does.

It was designed with the user experience in mind, simplifying and speeding up the checkout process while providing a more secure, consistent and seamless payment experience across websites.

In this article, we’ll explain the Payment Request API, its benefits and how it can be used in modern applications.

Prerequisites

You only need to have a basic understanding of JavaScript.

Understanding the Payment Request API

When learning about the Payment Request API, the definition that stood out for me was from Zach Koch’s Google I/O 2017 talk on “The Future of Web Payments.” He described the API as “an open, built-into-the-web, designed-to-be-fast, ready-to-be-used-today API for transacting on the web.”

Now, let’s break this down:

  • An open API: Openness here means it is not only free to use but, more importantly, not tied to any specific browser or payment provider. Whether you accept credit card payments or platform-specific payments like Google Pay, Apple Pay or others, the API provides a unified way to handle them all.
  • Built into the web: The Payment Request API runs directly in the browser. Its implementation is straightforward and does not enforce the need for script embedding, SDKs or iframes.
  • Designed to be fast: Slow checkout flows often lead to users abandoning their purchases. The Payment Request API eliminates the hassle of manually entering payment details by allowing users to select presaved payment methods, completing the checkout process in just a few seconds.
  • Ready to use: The Payment Request API has been introduced for some time now. Therefore, it is production-ready and supported in major browsers across the web.

This enhances the checkout experience, making payments faster and less frustrating for users. Unlike some traditional approaches, it provides a more streamlined flow, allowing users to complete the payment process without leaving the checkout page.

The Payment Request API enables user agents such as the browser to act as intermediaries between the user, merchant and payment method in a transaction.

  • User (payer): The one who makes a purchase. The payer authorizes payment.
  • Merchant (payee): The owner of the platform requesting payment.
  • Payment Method Provider: The provider of the means that the payer uses to pay the payee.

What the Payment Request API Is Not

Before continuing the rest of the article, let’s clarify some misconceptions about the Payment Request API.

First and most importantly, the Payment Request API does not process payments. It collects payment details and then hands them over to the merchant’s integrated payment process, which could be Stripe, PayPal, etc.

Next, the Payment Request API does not validate payment information. When integrated, the browser prefills the user’s payment details (if stored) but does not validate those details. Actual verification is done via the merchant’s payment provider.

It is also not a security risk. The API does not expose sensitive data to websites. Instead, it handles the data securely, and merchants receive only what is necessary.

Lastly, the Payment Request API does not entirely replace payment forms. It eliminates the need to fill out the form manually. However, a fallback form-based checkout might be used when the user’s browser does not support the API (we will look at the API’s browser compatibility in a later section).

Benefits of the Payment Request API

In addition to the benefit of a faster checkout process, consistent user experience, etc., explained so far in this article, this section highlights a few more benefits that the Payment Request API offers.

  • Support for multiple payment methods: The API offers flexibility and great support for various payment methods such as credit/debit cards, Google Pay, Apple Pay and custom gateways. If you have built a payment gateway, you can integrate it with the Payment Request API.
  • Improved security: The API does not expose users’ data. It relies on the browser’s secure management of stored data.
  • Reduces errors: Users are more prone to mistakes when entering payment data manually. The API minimizes typographical errors by enabling users to prefill payment and other data, such as shipping or contact information.
  • Reduced development workload and time: Instead of building complex checkout forms, we can reduce the development workload and time while also maintaining efficiency by leveraging the Payment Request API.

What About Browser Compatibility?

Browser compatibility is an important factor to consider when using Web APIs. According to https://caniuse.com/payment-request, the Payment Request API is supported by recent versions of most modern browsers, such as Google Chrome, Edge, etc.

However, we can always run the window.PaymentRequest during implementation to check compatibility before using the API.

Here is a sample code snippet showing what this check might look like:

const handleCheckout = () => {
  if (window.PaymentRequest) {
    //proceed to checkout, trigger the PaymentRequest API
    checkout();
  } else {
    //case of incompatibility
    console.log("You can't use this feature!");
  }
};

How to Use the Web Payment API

Now that we have learned about the Web Payment API, let’s see how to use it.

The Payment Request API exposes a PaymentRequest interface, which serves as the primary entry point to the API. To use the API, a new PaymentRequest instance must be created, and the necessary parameters must also be passed to its constructor.

Here is the basic syntax for instantiating PaymentRequest:

const paymentRequest = new PaymentRequest(methodData, details, options);

The PaymentRequest constructor takes two required parameters (the payment methodData and details) and an optional options parameter.

Let’s take a look at each of these parameters individually.

The Payment methodData

The first required parameter to instantiate the PaymentRequest is an array of objects that defines the various supported payment methods. The methodData array holds information about the various enabled payment providers.

Individual objects in the payment methodData array relating to a specific payment provider must define two elements: a required supported methods identifier and an optional data object.

The supportedMethods can be for:

  • Basic card payments: The identifier here is strictly “basic card.” This covers traditional credit and debit cards such as Visa, Mastercard, etc.
  • Proprietary or custom payment methods: The identifier here is a URL-based string specific to a particular payment provider. For example, Google uses https://google.com/pay, Apple uses https://apple.com/apple-pay and so on. We can also use other third-party and custom payment providers by getting their specific identifiers.

On the other hand, the optional data object element can be used to define optional parameters allowed by the payment method provider. Note that the content of this data element is based on what gets defined in supportedMethods.

Here is a snippet that shows what the payment method data array would look like:

const methodData = [
  {
    supportedMethods: "basic-card",
    data: {
      supportedNetworks: ["visa", "mastercard"],
    },
  },
  {
    supportedMethods: "https://google.com/pay",
  },
];

This snippet shows a basic definition that supports both basic card and Google Pay as payment options.

The Payment Details

The second required parameter to pass when instantiating the PaymentRequest is an object that defines the details of a given transaction.

It consists of a total object that specifies the total amount to be paid by the user and an optional displayItems object that defines the item breakdown, including other fees such as shipping cost, tax, etc.

Here is a sample snippet:

const details = {
  total: {
    label: "Total Amount",
    amount: { currency: "USD", value: "25.00" },
  },
  displayItems: [
    {
      label: "Product 1",
      amount: { currency: "USD", value: "15.00" },
    },
    {
      label: "Product 2",
      amount: { currency: "USD", value: "10.00" },
    },
  ],
};

Here, we can see the total amount the user will pay and a list of the items contributing to the total cost.

Options Object

The third parameter for instantiating the PaymentRequest is the options object. Unlike the others, it is optional. It allows merchants to specify additional preferences, such as required shipping details, contact information and payment capabilities.

Here is a list of some of the supported options:

const options = {
  requestPayerName: true,
  requestPayerEmail: true,
  requestPayerPhone: true,
  requestShipping: true,
  requestBillingAddress: true,
};

From the property names, we can see that the various options can be used to request extra information from users, such as email, name, shipping information and so on.

Putting all of this together, this is what a complete PaymentRequest instantiation would look like:

//payment methods definition
const methodData = [
  {
    supportedMethods: "basic-card",
    data: {
      supportedNetworks: ["visa", "mastercard"],
    },
  },
  {
    supportedMethods: "https://google.com/pay",
  },
];

//details definition
const details = {
  total: {
    label: "Total Amount",
    amount: { currency: "USD", value: "25.00" },
  },
  displayItems: [
    {
      label: "Product 1",
      amount: { currency: "USD", value: "15.00" },
    },
    {
      label: "Product 2",
      amount: { currency: "USD", value: "10.00" },
    },
  ],
};

//options definition
const options = {
  requestPayerName: true,
  requestPayerEmail: true,
  requestPayerPhone: true,
  requestShipping: true,
  requestBillingAddress: true,
};

//instantiation
const paymentRequest = new PaymentRequest(methodData, details, options);

Here, we can see the complete payment methods, details, options definition and interface instantiation.

Check if Users Can Make Payment

The PaymentRequest instance exposes several methods and properties that help manage the payment flow. These include checking payment availability, showing the interface and handling user interactions.

Once the PaymentRequest instantiation is complete, we can call the canMakePayment method on the created instance to verify whether the user can proceed with the payment based on the construction of the payment object. It checks whether the user has at least one valid payment method available, which helps prevent issues if no supported payment methods exist.

Here is a snippet that shows a sample usage of the canMakePayment method:

//payment methods definition
const methodData = [
  //....
];

//details definition
const details = {
  //...
};

//options definition
const options = {
  //...
};

//instantiation
const paymentRequest = new PaymentRequest(methodData, details, options);

//check if the user can make a payment
async function checkPaymentAvailability() {
  try {
    const canPay = await paymentRequest.canMakePayment();
    if (canPay) {
      console.log("User can make a payment.");
    } else {
      console.log("No supported payment methods available.");
    }
  } catch (error) {
    console.error("Error checking payment availability:", error);
  }
}

checkPaymentAvailability();

If the definition is good, canMakePayment() resolves to true; otherwise, it returns false. This provides a better user experience and helps prevent unnecessary error prompts.

Display Payment Interface

The next step is to display the payment interface, and we can do this by calling the show method on the PaymentRequest instance.

The show method triggers the browser’s native payment interface. It returns a promise that resolves with a PaymentResponse object once the user completes or cancels the payment.

Here is a sample snippet that shows how it works:

async function displayPaymentUI() {
  try {
    //calls show() on the `PaymentRequest` instance
    const paymentResponse = await paymentRequest.show();

    // payment successful
    await paymentResponse.complete("success");
  } catch (error) {
    console.error("Payment was canceled or failed:", error);
  }
}

displayPaymentUI();

In the code above, request.show displays the payment interface. On success, a PaymentResponse object is returned. Otherwise, if something goes wrong or the user cancels the payment process, an error is returned which can be handled appropriately.

In addition to the canMakePayment and show methods that we have looked at in this section, the Payment Request API provides other valuable properties, methods and events on its instance. Some of them are abort, which can be used to cancel a pending request, and paymentMethodChange, which gets triggered whenever the user changes payment instruments, like switching from a credit card to a debit card.

You can review the complete list of properties, methods and events here.

Project Setup

Now that we’ve learned how to use the Payment Request API, we’re ready to see it in action. In this section, we will create a Next.js application for the demo.

Open your terminal and run the following command to create a new Next.js 14 project:

npx create-next-app@14

Enter a name for the demo project and select options for the other resulting prompts, as shown below.

Run the following command to navigate to the project directory:

cd paymentrequestapi-demo

Start the development server:

npm run dev

Building a Simple Demo Application

For the demo, we will build a simple cart page that shows a list of items, allows users to update the quantity of each item and includes a checkout button. The checkout button will trigger the Payment Request API, which will be integrated in the next section.

First, open the created project in your favorite code editor. Create a products.ts file in the src/ folder and add the following to it:

export const initialProducts = [
  {
    id: 1,
    name: "Brief Case",
    amount: 250,
    quantity: 1,
  },
  {
    id: 2,
    name: "Shoe",
    amount: 100,
    quantity: 1,
  },
];

Here, we created an array of dummy products called initialProducts.

Next, create a new components folder in the src/ folder and add a Cart.ts file to it:

"use client";

import React, { useState } from "react";
import { initialProducts } from "@/products";

export default function Cart() {
  const [products, setProducts] = useState(initialProducts);

  const updateQuantity = (id: number, value: number) => {
    setProducts((prevProducts) =>
      prevProducts.map((product) =>
        product.id === id
          ? { ...product, quantity: Math.max(1, product.quantity + value) }
          : product
      )
    );
  };

  const totalAmount = products.reduce(
    (acc, product) => acc + product.amount * product.quantity,
    0
  );

  const handleCheckout = () => {
    console.log("Checkout Triggered!!");
  };

  return (
    <section className="my-16 w-[95%] max-w-screen-sm border rounded p-4">
      <h1 className="font-semibold text-lg text-center">
        Static Cart - Payment Request API demo
      </h1>
      <div className="my-6 overflow-x-auto">
        <table className="w-full border-collapse border text-left">
          <thead>
            <tr className="border-b">
              <th className="p-2">Item</th>
              <th className="p-2">Quantity</th>
              <th className="p-2">Price</th>
            </tr>
          </thead>
          <tbody>
            {products.map((product) => (
              <tr key={product.id} className="border-b">
                <td className="py-4 px-2">{product.name}</td>
                <td className="py-4 px-2">
                  <div className="flex gap-2">
                    <button onClick={() => updateQuantity(product.id, -1)}>
                      -
                    </button>
                    <span>{product.quantity}</span>
                    <button onClick={() => updateQuantity(product.id, 1)}>
                      +
                    </button>
                  </div>
                </td>
                <td className="py-4 px-2">
                  ${product.amount * product.quantity}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
        <div className="flex justify-between items-center mt-8">
          <p className="font-semibold text-lg">Total: ${totalAmount}</p>
          <button
            onClick={handleCheckout}
            className="bg-green-700 py-2 px-6 text-white rounded font-semibold"
          >
            Checkout
          </button>
        </div>
      </div>
    </section>
  );
}

Now, let’s break down what is happening in the code step by step.

First, we created a client component and defined a products state variable. Then, we imported the initialProducts array created earlier and passed it as the initial value of the products state.

Next, we created a function called updateQuantity that allows users to increase or decrease the number of items in the cart. Since this is just a demo, it won’t allow the quantity to go below 1.

Next, we computed the total cost of all the items in the cart by adding their prices and multiplying them by their quantities.

Then, we render the items in the cart in table format. Users can increase or decrease quantities using the + and - buttons. We also added a button that triggers a handleCheckout function. For now, we log text to the console. From this point, we will trigger the Payment Request API in the next section.

Open the src/app/page.tsx file and update the code as shown below:

import Cart from "@/components/Cart";

export default function Home() {
  return (
    <div className="w-full flex justify-center">
      <Cart />
    </div>
  );
}

We replaced the template code in this file with the Cart component we just created.

Now you can open the application in the browser and review what we have.

Integrating the Web Payment API

Now, let’s integrate the Web Payment API into our demo application. Create a util.ts file in the src/ folder and add the following to it:

interface Product {
  id: number;
  name: string;
  quantity: number;
  amount: number;
}

export const checkout = async (products: Product[]) => {
  try {
    const methodData = [
      {
        supportedMethods: "https://google.com/pay",
        data: {
          environment: "TEST",
          apiVersion: 2,
          apiVersionMinor: 0,
          merchantInfo: {
            merchantName: "Example Merchant",
          },
          allowedPaymentMethods: [
            {
              type: "CARD",
              parameters: {
                allowedAuthMethods: ["PAN_ONLY", "CRYPTOGRAM_3DS"],
                allowedCardNetworks: ["MASTERCARD", "VISA"],
              },
              tokenizationSpecification: {
                type: "PAYMENT_GATEWAY",
                parameters: {
                  gateway: "example",
                  gatewayMerchantId: "exampleGatewayMerchantId",
                },
              },
            },
          ],
        },
      },
    ];

    const displayItems = products.map((product) => ({
      label: `${product.name} x${product.quantity}`,
      amount: {
        currency: "USD",
        value: (product.amount * product.quantity).toFixed(2),
      },
    }));

    const totalAmount = products.reduce(
      (acc, product) => acc + product.amount * product.quantity,
      0
    );

    const paymentDetails = {
      displayItems,
      total: {
        label: "Total due",
        amount: { currency: "USD", value: totalAmount.toFixed(2) },
      },
    };

    const paymentRequest = new PaymentRequest(methodData, paymentDetails, {});
    if (!(await paymentRequest.canMakePayment())) {
      console.log("Payment Request API not available.");
      return;
    }

    const paymentResponse = await paymentRequest.show();

    await paymentResponse.complete("success");

    alert("Payment successful!");
  } catch (error) {
    console.error("Payment failed:", error);
    alert("Payment failed. Please try again.");
  }
};

The code above creates a checkout utility function that helps integrate the Web Payment API for transaction processing. First, we defined the accepted payment method, methodData, which includes Google Pay. The definition used a test configuration since this is a demo application. You can always replace this with your live details.

The cart items are then formatted into displayItems, and the total amount is calculated. With this data, a PaymentRequest instance is created.

If everything goes well, the show method is called to display the payment interface. If the user completes the payment, the response is handled and a success message is shown. On the other hand, if anything goes wrong, an error message is displayed.

Now, let’s go back to the Cart component. Open the src/components/Cart.tsx file and update the code as shown below:

"use client";

import React, { useState } from "react";
import { initialProducts } from "@/products";
import { checkout } from "@/util";

export default function Cart() {
  const [products, setProducts] = useState(initialProducts);

  const updateQuantity = (id: number, value: number) => {
    //...
  };

  const totalAmount = products
    .reduce
    //...
    ();

  const handleCheckout = () => {
    if (window.PaymentRequest) {
      checkout(products);
    } else {
      alert("You can't use this feature!");
    }
  };

  return (
    <section className="my-16 w-[95%] max-w-screen-sm border rounded p-4">
      //...
    </section>
  );
}

Here, we imported the checkout utility function and called it in the handleCheckout function. Before making the call, we set the function to get triggered only when the Payment Request API feature is available.

Now, you can preview the changes in your browser.

Previewing Other Options

In the previous section, we created the PaymentRequest instance without initializing it with predefined options as the third and optional parameter. However, in this section, let’s examine one or two of the options before concluding.

Update the line where we instantiated PaymentRequest in the src/util.ts file as shown below:

const paymentRequest = new PaymentRequest(methodData, paymentDetails, {
  requestPayerEmail: true,
  requestShipping: true,
});

Here, we added the option to request the payer’s email and another to request the shipping information.

You can go ahead and hit the Checkout button again in your browser to see what changed.

Conclusion

The Payment Request API is an important API that has contributed positively to the way payments are made on the web by providing a smooth, secure and user-friendly checkout experience across different platforms and devices.

In this article, we looked at the Payment Request API, its benefits and its use. Then, we walked through its implementation, from setting up a cart system to integrating the payment feature.


About the Author

Christian Nwamba

Chris Nwamba is a Senior Developer Advocate at AWS focusing on AWS Amplify. He is also a teacher with years of experience building products and communities.

Related Posts