Building a custom Checkout integration with Express

    Checkout is a payment form that users can embed in their websites. It provides a pre-built user interface for securely accepting payment methods, with features like validation and a responsive mobile layout. This recipe demonstrates how to build a custom Checkout integration that works with the Node.js Express framework.

    A conventional Checkout integration sends a simple POST request with form-encoded parameters to the user’s server. Typically, Checkout uses the form action attribute to determine where it should send the information. In some cases, however, the user may want to create a custom Checkout integration that allows them exercise more programmatic control over how the application sends the payment token to the server.

    This recipe shows how to build a custom Checkout integration that uses the W3C Fetch API to send the token to an Express server. This example sends the token in a POST request with a JSON body instead of using conventional form encoding. There are four steps:

    1. Install and configure dependencies
    2. Create the Express routes
    3. Create the HTML front end
    4. Run the application

    Step 1: Install and configure dependencies

    To follow along, you need a Node.js environment with version 6.x or higher. Use npm to install Express, body parser, and the Stripe library:

    npm install stripe express body-parser

    Create a file named app.js and populate it with the necessary imports and configuration values:

    const keyPublishable = process.env.PUBLISHABLE_KEY;
    const keySecret = process.env.SECRET_KEY;
    const express = require("express");
    const stripe = require("stripe")(keySecret);
    const bodyParser = require("body-parser");
    const app = express();
    app.use(bodyParser.urlencoded({extended: false}));

    The file defines two constants, keyPublishable and keySecret. These keys identify your account when you communicate with Stripe. In this example, the application extracts the values from local environment variables to cleanly separate configuration from code. Avoid hard-coding API access keys and other sensitive data in your application code.

    After setting up the constants, import the Stripe and Express modules. The Stripe module accepts a single parameter, the secret key associated with your account. Next, initialize Express and configure the middleware. This example uses the static middleware to serve static files from a directory named public. It also uses the body-parser module to handle JSON request bodies.

    Step 2: Create the Express routes

    The Express server exposes a POST route that receives the payment token and creates the charge. Add the route handler to the app.js file:"/charge", (req, res) => {
      let amount = 500;
      .then(customer =>
          description: "Sample Charge",
          currency: "usd",
      .then(charge => res.send(charge))
      .catch(err => {
        console.log("Error:", err);
        res.status(500).send({error: "Purchase Failed"});

    The charge route handler retrieves the email address and card token from the body. It uses those parameters to create a Customer object in your Stripe account. Next, it invokes the stripe.charges.create method, providing the Customer object as an option. The Stripe Node.js client library performs those operations asynchronously, which means that you need to use either callbacks or promises to handle the results and ensure that the functions are invoked in the correct order. If any operation in the promise chain fails, the catch method displays an error message in the console and sends a 500 error in response to the user’s request.

    Creating the Customer object is optional, but it allows you to perform future charges for the user without collecting their credit card information every time.

    In this example, the application charges the user $5. Stripe expects the developer to provide the value of the charge in cents, so compute the value of the amount parameter by multiplying the desired number of dollars by one hundred. Stripe charges also take an optional description parameter, which is “Sample Charge” in this case.

    Step 3: Create the HTML front end

    To create a custom Checkout integration, use the Checkout API to manually display the payment form. When the user fills in their payment information and proceeds with the purchase, Checkout triggers a callback function that provides the token as a parameter. In this sample application, the callback uses the W3C Fetch API to send the token to the server.

    Create a file named public/index.html:

        <title>Stripe Checkout Integration</title>
        <script src=""></script>
        <h2>Stripe Checkout Example</h2>
        <div id="shop">
          <button id="buttonCheckout">Checkout</button>

    The client-side logic goes in the empty <script> tag at the base of the body. Start by configuring the Checkout library:

    var checkoutHandler = StripeCheckout.configure({
      key: "pk_test_TYooMQauvdEDq54NiTphI7jx",
      locale: "auto"

    Next, attach an event listener to the button to handle click events. In the button click callback, use Checkout’s open method to display the payment form:

    var button = document.getElementById("buttonCheckout");
    button.addEventListener("click", function(ev) {{
        name: "Sample Store",
        description: "Example Purchase",
        token: handleToken

    The token property is the callback that Checkout triggers when the user completes their purchase. In that callback, create a JSON payload with the token and use fetch to send it to the application server:

    function handleToken(token) {
      fetch("/charge", {
        method: "POST",
        headers: {"Content-Type": "application/json"},
        body: JSON.stringify(token)
      .then(output => {
        if (output.status === "succeeded")
          document.getElementById("shop").innerHTML = "<p>Purchase complete!</p>";

    When the operation completes successfully, the application replaces the purchase button with a message to indicate that the purchase is complete. In a real-world application, you might want to add error handling and disable the purchase button while the operation is pending.

    The W3C Fetch API returns a promise that is fulfilled when the underlying HTTP operation is complete. It’s worth noting that the promise still resolves successfully even if the server returns an error code. The application is responsible for appropriately detecting and handling common 4xx and 5xx errors.

    The response object returned by the Fetch API has a boolean ok property that you can use to determine if the HTTP operation was successful. If you want to use a catch method to handle requests that return errors as well as requests that fail to complete, you can use the following pattern:

    fetch("/charge", {
      method: "POST",
      headers: {"Content-Type": "application/json"},
      body: JSON.stringify(token)
    .then(response => {
      if (!response.ok)
        throw response;
      return response.json();
    .then(output => {
      console.log("Purchase succeeded:", output);
    .catch(err => {
      console.log("Purchase failed:", err);

    In the response handler, the conditional statement throws an exception when the server responds with an error code, ensuring that the catch handler executes as expected.

    Step 4: Run the application

    Run the application from the command line:

    PUBLISHABLE_KEY=pk_test_TYooMQauvdEDq54NiTphI7jx SECRET_KEY=sk_test_4eC39HqLyjWDarjtT1zdp7dc node app.js

    Specify values for the publishable and secret key environment variables.

    Navigate to the running application in your browser and click the button to launch the payment form. If you’re using Stripe test keys, you can test it with some dummy data. Enter the test number 4242 4242 4242 4242, a three digit CVC, and a future expiry date. Submit the form and see if the application correctly displays the successful charge page.

    Was this page helpful?

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.

    On this page