tutorial // Oct 08, 2021

How to Charge a Credit Card with Stripe in Node.js

How to use the Stripe NPM package to communicate with the Stripe API and charge a credit card in Node.js.

How to Charge a Credit Card with Stripe in Node.js

Getting Started

For this tutorial, we're going to use the CheatCode Node.js Boilerplate as a starting point for our work. To start, let's clone a copy from Github:

Terminal

git clone https://github.com/cheatcode/nodejs-server-boilerplate

Next, cd into the project and install its dependencies:

Terminal

cd nodejs-server-boilerplate && npm install

Next, we need to install one additional dependency, stripe:

Terminal

npm i stripe

Finally, go ahead and start up the development server:

Terminal

npm run dev

With that, we're ready to get started.

Getting a Card Token

In order to process a charge via the Stripe API, we'll need to get access to a Stripe token. For this tutorial, we'll only be focused on the back-end, but it's recommended that you check out our tutorial on How to Build a Credit Card Form Using Stripe.js to learn how to build a user interface to retrieve a Stripe card token.

Once you have a means for getting a card token, we can dig into processing a charge.

Accessing our Stripe API keys

Before we dig into the code, for this tutorial, we're going to need access to a Stripe account. Head over to the signup page on their site and create an account if you haven't already.

Once you have an account, login to the dashboard. It should look something like this:

O66I0fD25fe8zKhG/qIARNw0m7H7jbEhc.0
View your API keys in the Stripe dashboard.

Where we want to navigate to is the page pictured above. To get there:

  1. In the top-right corner, make sure that you've toggled the "Test mode" toggle so that it's lit up (as of writing this will turn orange when activated).
  2. To the left of that toggle, click the "Developers" button.
  3. On the next page, in the left-hand navigation menu, select the "API keys" tab.
  4. Under the "Standard keys" block on this page, locate your "Secret key" and click the "Reveal test key" button.
  5. Copy this key (keep it safe as this is used to perform transactions with your Stripe account).

Next, once we have our secret key, we need to open up the project we just cloned and navigate to the /settings-development.json file:

/settings-development.json

const settings = {
  "authentication": { ... },
  "databases": { ... },
  "smtp": { ... },
  "stripe": {
    "secretKey": "<Paste your secret key here>"
  },
  "support": { ... },
  "urls": { ... }
};

export default settings;

In this file, alphabetically near the bottom of the exported settings object, we want to add a new property stripe and set it to an object with a single property: secretKey. For the value of this property, we want to paste in the secret key you copied from the Stripe dashboard above. Paste it in and then save this file.

In the boilerplate we cloned above, there's a built-in mechanism for loading our settings based on the value of process.env.NODE_ENV (e.g., development, staging, or production). Here, we assume we're developing on our local machine and add our keys to the settings-development.json file. If we were deploying to production, we would make this change in our settings-production.json file, using our live secret key as opposed to our test secret key like we see above.

Next, in order to access the Stripe API, we need to set up an instance of Stripe via the stripe NPM package.

Wiring Up Access to Stripe

With our secret key set up, now, we need to get access to the Stripe API. Fortunately, the folks at Stripe offer a Node.js package for their API (we installed this earlier), so all we need to do is set up a connection to it.

/lib/stripe.js

import Stripe from 'stripe';
import settings from "./settings";

const stripe = Stripe(settings.stripe.secretKey);

export default stripe;

Inside of our /lib folder, we want to create a file stripe.js where we'll load in the stripe package from NPM and initialize it with our secretKey from Stripe that we just added to our settings file.

Here, we import the appropriate settings based on our environment. We're assuming the current environment is development, so settings here will contain the contents of our settings-development.json file.

On that object, we expect a property stripe to be defined as an object with its own property secretKey. Above, we first import Stripe from the stripe NPM package we installed earlier and then call that imported value as a function, passing in our secretKey from our settings file.

In return, we expect to get back an instance of the Stripe API, which we store in a variable stripe and then export as the default value from this file.

With this, now, whenever we want to communicate with Stripe, we only need to import this one file as opposed to writing all of this code in all of the files where we want to call to Stripe.

Wiring Up an Endpoint for Charges

Next, we're going to wire up an HTTP POST endpoint using Express.js (built-in and pre-configured in the boilerplate we're using). We'll use this endpoint to demonstrate creating the charge via Stripe. It's important to note: you can call the Stripe code we'll see below from anywhere within Node.js. We're just using an Express route as an example.

/api/index.js

import graphql from "./graphql/server";
import stripe from "../lib/stripe";

export default (app) => {
  graphql(app);
  app.post("/checkout", (req, res) => {
    // We'll wire up the charge here...
  });
};

Inside of the /api/index.js file already included in our boilerplate, we add a new route /checkout by calling to the .post() method on the app argument passed into the function exported from this file. Here, app represents the Express.js app that we get in return when calling to express() (you can see the setup for this in the /index.js file at the root of the boilerplate—the api() function we call there is the one we see being exported above).

Here, we use the .post() method to create an Express route that only accepts HTTP POST requests. As we'll see, we'll send an HTTP POST request later to test this out.

/api/index.js

import graphql from "./graphql/server";
import stripe from "../lib/stripe";

export default (app) => {
  graphql(app);
  app.post("/checkout", (req, res) => {
    const items = [
      { _id: "water-jug", amount: 9999, name: "Water Jug" },
      { _id: "coffee-cup", amount: 2999, name: "Coffee Cup" },
      { _id: "ham-sandwich", amount: 2999, name: "Ham Sandwich" },
    ];

    const item = items.find(({ _id }) => _id === req?.body?.itemId);
    const source = req?.body?.source;

    if (item && source) {
      // We'll process the charge here...
    }

    res
      .status(400)
      .send(
        "Must pass an itemId and source in the request body in order to process a charge."
      );
  });
};

Inside of the callback for our route, before we handle the request, we set up an array of items to act as a mock database for real items that a customer might purchase from us.

This is important. The reason we show this here instead of passing an amount from the client is that we should never trust the client. For example, if a user figured out that we just pass the amount from the client to the server, they could change an order for $1,000 to $0.01 and the charge would process.

To mitigate this, we track the prices we're going to charge on the server and use a unique ID to tell us which item to get the price for when we receive a charge request.

Here, we do that by saying "this array of items are for sale with these prices." We expect that the req.body object we receive will have two properties: an itemId and a source. Here, itemId should match one of the _id fields on an item if the purchase is valid (in practice, we'd load the same list of items into our UI from the database so the IDs were consistent).

To check, we use items.find(), looking for an item with an _id property—inside of our .find() callback we use JavaScript object destructuring to "pluck" this property off of each item we loop over—that's equal to the req.body.itemId we received from the client.

If we do find a matching item, we know the purchase is valid. Next, we also get the source—this is the term Stripe uses to refer to the payment source—from the req.body.

Assuming that both item and source are defined, we want to attempt a charge. If they are not defined, we want to respond with an HTTP 400 status code which stands for a "Bad Request" and send back a message with instructions on how to resolve the problem.

/api/index.js

import graphql from "./graphql/server";
import stripe from "../lib/stripe";

export default (app) => {
  graphql(app);
  app.post("/checkout", (req, res) => {
    const items = [
      { _id: "water-jug", amount: 9999, name: "Water Jug" },
      { _id: "coffee-cup", amount: 2999, name: "Coffee Cup" },
      { _id: "ham-sandwich", amount: 2999, name: "Ham Sandwich" },
    ];

    const item = items.find(({ _id }) => _id === req?.body?.itemId);
    const source = req?.body?.source;

    if (item && source) {
      return stripe.charges
        .create({
          amount: item.amount,
          currency: "usd",
          source,
          description: item.name,
          metadata: {
            ...item,
          },
        })
        .then((charge) => {
          res.status(200).send(charge);
        })
        .catch((error) => {
          res.status(402).send(error);
        });
    }

    res
      .status(400)
      .send(
        "Must pass an itemId and source in the request body in order to process a charge."
      );
  });
};

Now we're ready to send our charge request to Stripe. To do it, we're going to call to the stripe.charges.create() method from the stripe API instance we set up in the file we imported earlier. Calling that function, we pass an object with the appropriate options for our charge (see what's available in the Stripe documentation here).

For our needs, we want to pass the two required fields amount (an integer representing the charge in cents—e.g., $5.00 would be 500) and currency. We also pass our source (this will be the Stripe token that we retrieve on the client), the name of our item as a description, and also include all of the data about our charge in the metadata field as an example of passing miscellaneous data alongside our charge (a convenience option for developers who need to store additional, custom, charge-related data like an internal user ID).

Finally, as we expect all of the methods in the stripe API instance to return a JavaScript Promise, we chain on a .then() callback function to handle our success state and a .catch() callback function to handle an error state.

If the charge is successful, we respond to the original req with a status code of 200 (the HTTP status code for signaling a successful request) and pass the response we receive from Stripe (an object containing the details of the charge processed).

If the charge fails, we send an HTTP status code 402 (which stands for "Payment Required") and send back the error object received from Stripe.

That's it! Let's fire up the client to get our Stripe token and then process the request via an HTTP app (I'm using the MacOS app Paw to test our endpoint).

Wrapping Up

In this tutorial, we learned how to charge a credit card using the stripe API in Node.js. We learned how to create an instance of the Stripe API via their stripe node package, creating a reusable module for communicating with stripe, and then we learned how to set up an HTTP POST route via Express.js where we could send a charge request to Stripe.

Written By
Ryan Glover

Ryan Glover

CEO/CTO @ CheatCode