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:
Where we want to navigate to is the page pictured above. To get there:
- 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).
- To the left of that toggle, click the "Developers" button.
- On the next page, in the left-hand navigation menu, select the "API keys" tab.
- Under the "Standard keys" block on this page, locate your "Secret key" and click the "Reveal test key" button.
- 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 req
uest, 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.