Subscription PaymentsSaasNode.jsStripeMongodbPayments

Building a Subscription System using Stripe, Node.js, and MongoDB - Part 1: Recurring Payments

We will create a subscription system that will let our users pay for a monthly plan using Stripe Checkout.

Featured Image
  • Languages: Node, HTML
  • Tools Used: Stripe
  • Time Saved: 3 weeks -> 40 mins
  • Live Demo
  • Source code on Github

This is Part 1 in the series of guides on adding, managing, and keeping track of subscription payments using Stripe and Mongo for your SaaS app.


Subscription Payments are the bread and butter of a SaaS application; it's how you start generating revenue. In a well-implemented system, users need to first be able to change or cancel their plans, and second, undergo a trial phase to see if they would even like to pay for premium features. Typically, it's a hassle to set these up. In this set of guides, we will go through all the steps required to build a complete Subscription Payments system for your SaaS app.

In Part 1, we will:

  1. Create a basic sign-in flow to keep track of users
  2. Let users pay for a subscription plan: $10/month for a Basic Plan or $12/month for a Pro Plan
  3. Present users with a Stripe Checkout screen where they can pay using their credit card
  4. Add a 2 weeks trial period before billing the user

Step 1: Setup the project

Let's start by creating a new project folder. Add a package.json

{ "name": "stripe-subscriptions-nodejs", "version": "1.0.0", "author": "sssaini", "description": "Add Stripe Subscriptions Payments using Node.js", "main": "app.js", "scripts": { "start": "node app.js" }, "license": "ISC", "dependencies": { "body-parser": "^1.19.0", "cookie-parser": "^1.4.5", "ejs": "^3.1.5", "express": "^4.17.1", "express-session": "^1.17.1", "mongoose": "^5.10.11", "stripe": "^8.114.0", "memorystore": "^1.6.4" } }

Install dependencies by running,

npm install

Step 2: Set up a basic Express Server

Create a new file named app.js

const bodyParser = require("body-parser"); const express = require("express"); const app = express(); app.use(bodyParser.json()); app.use( bodyParser.urlencoded({ extended: true, }) ); app.use(express.static("public")); app.engine("html", require("ejs").renderFile); app.get("/", async function (req, res, next) { res.send("Hello World!"); }); const port = 4242; app.listen(port, () => console.log(`Listening on port ${port}!`));

We will use the public folder to serve any static files like images, CSS, and JS. We are also using a templating engine called EJS so we can generate a dynamic HTML page by rendering information sent by the server.

Launch the app by running,

npm start

The application should be live at http://localhost:4242.

All the basic setup is now done! Let's move on to the meat of the application.

Step 3: Building the UI

First things first. We need a UI to guide our users through buying a subscription. Our UI will include:

  1. A login screen where the customer can enter in their email address
  2. An account screen that lets the customer buy a subscription

Step 4: UI - Login Screen

Add a / endpoint by editing the app.js

// ... app.get("/", async function (req, res, next) { res.status(200).render("login.ejs"); }); // ...

Let's now work on the view by creating a new file,  views/login.ejs

<html lang="en"> <body class="text-center"> <form class="form-login" action="/login" method="post"> <h1>Log in</h1> <input type="email" name="email" placeholder="Email address" required /> <button type="submit">Sign in</button> </form> <script src="" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous" ></script> </body> </html>

It looks pretty bare-bones, doesn't it? Fear not, our lovely team at SaaSBase already has you covered.

Grab the associated HTML and the CSS needed from the Github project. Basic login screen that will create a Stripe Customer using the provided email address

To run the project,

npm start

A fancy login page should be available at http://localhost:4242.

Step 5: Create a Stripe Customer

Start by creating a new Stripe account here.

On the Stripe Dashboard, we have the option to manually create a Customer. But we want to do it through the API so that when a new customer logs into our app, a matching Stripe Customer Profile is created.

A Customer is one of the core resources in Stripe. Once we create a Customer using Stripe API, we get back the associated Customer API ID. This ID can then be used by our application to identify and track future transactions pertaining to that customer.

So what do we need to register the customer in Stripe? Just their email address, that's it.

Create a new file called src/connect/stripe.js

const stripe = require('stripe'); const STRIPE_SECRET_KEY = 'sk_test_xxx'; const Stripe = stripe(STRIPE_SECRET_KEY, { apiVersion: '2020-08-27' }); const addNewCustomer = async (email) => { const customer = await Stripe.customers.create({ email, description: 'New Customer' }) return customer } const getCustomerByID = async (id) => { const customer = await Stripe.customers.retrieve(id) return customer } module.exports = { addNewCustomer, getCustomerByID }

Copy in your Stripe Secret Key by going to the Stripe Dashboard > Developers > API keys or by clicking here.

We can now add the customer to Stripe when they enter their email address with a /login POST endpoint. In app.js:

const Stripe = require("./src/connect/stripe");"/login", async (req, res) => { const { email } = req.body; const customer = await Stripe.addNewCustomer(email); res.send("Customer created: " + JSON.stringify(customer)); });

Let's test it out. Run the application using npm start. Sign in using an email address and if the Customer was successfully created in Stripe, we will get a successful response back like so:

{ "id": "cus_IaFpg44TGYsJNT", "object": "customer", "address": null, "balance": 0, "created": 1608145534, "currency": null, "default_source": null, "delinquent": false, "description": "SaaSBase Customer", "discount": null, "email": "", "invoice_prefix": "756F8AF0", "invoice_settings": { "custom_fields": null, "default_payment_method": null, "footer": null }, "livemode": false, "metadata": {}, "name": null, "next_invoice_sequence": 1, "phone": null, "preferred_locales": [], "shipping": null, "tax_exempt": "none" }

The id is the unique ID for a Stripe Customer. From now on, we can add products, get invoices, etc. all by referring to this ID on behalf of our customer.

We can also see that our newly added Customer is now available on the Dashboard. A Customer object is successfully created on the Stripe Dashboard Perfect! We have a Customer.

Step 6: Save the user session

We need to somehow persist the Customer ID so that it's available to us when the customer wants to make a purchase. The easiest way to do that is by using sessions.

Add sessions support with the express-session package In app.js

const session = require('express-session'); const MemoryStore = require('memorystore')(session); const UserService = require('./src/user'); app.use(session({ saveUninitialized: false, cookie: { maxAge: 86400000 }, store: new MemoryStore({ checkPeriod: 86400000 }), resave: false, secret: 'keyboard cat' }));'/login', async (req, res) => { // .. req.session.customerID = customer res.send('customer created:' + JSON.stringify(customer)); });

The package saves the cookie on the client's browser as connect.sid. Learn more here.

We can verify this on Google Chrome by opening-up Developer Tools and going to Application tab > Cookies. The account is stored in a cookie on the client

Step 7: Add products to the Stripe Dashboard

We are now ready to offer our subscription plans - Basic for $10 and Pro for $12.

To sell a subscription plan, we need a Product.

On the Stripe Dashboard, click on Products > Add product to add a new product.

  1. Add a Product - Basic with $10 Price and Recurring
  2. Add a Product - Pro with $12 Price and Recurring

A new Product is created on Stripe Each Product has an associated Price API ID. Save it, we will need it in the next step. Creating a Product successfully returns the associated API key Let's copy the Price API ID for both our products and add them in our app.js .

const productToPriceMap = { BASIC: "price_xxx", PRO: "price_xxx", };

Step 8: Create the Checkout Screen and add a Trial Period

We have a customer and we have our products. We are now ready to initiate a checkout screen where customers can enter in their credit card information and pay for the selected plan type.

We will create a Stripe checkout session on the server and send the associated Session ID back to the client. The client can use this ID to redirect to a secure checkout screen pre-filled with customer information, like their email address.

Create two radio buttons to select the plan type and a Buy Now button in views/account.ejs

<html> <head></head> <body> <input type="radio" id="basic" name="product" value="basic" /> <label for="basic">Basic for $10</label> <br /> <input type="radio" id="pro" name="product" value="pro" /> <label for="pro">Pro for $12</label> <br /> <button class="btn btn-primary" id="checkout-button" type="submit"> Buy now </button> <script src="" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous" ></script> <script type="text/javascript" src=""></script> <script type="text/javascript" src="./js/account.js"></script> </body> <html></html> </html>

Add the script public/js/account.js:

$(document).ready(function () { const PUBLISHABLE_KEY = "pk_test_xxx"; const stripe = Stripe(PUBLISHABLE_KEY); const checkoutButton = $("#checkout-button"); () { const product = $('input[name="product"]:checked').val(); fetch("/checkout", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ product, }), }) .then((result) => result.json()) .then(({ sessionId }) => stripe.redirectToCheckout({ sessionId, }) ); }); });

The PUBLISHABLE_KEY can be found on the Stripe Dashboard > Developers or by clicking here.

We can create a matching /account GET endpoint to serve account.ejs view by modifying app.js:

app.get("/account", async function (req, res) { res.render("account.ejs"); });

Let's modify our /login POST endpoint so that it redirects to Account view on successful login."/login", async function (req, res) { // .. = email; res.redirect("/account"); });

In src/connect/stripe.js:

// .. const createCheckoutSession = async (customer, price) => { const session = await Stripe.checkout.sessions.create({ mode: "subscription", payment_method_types: ["card"], customer, line_items: [ { price, quantity: 1, }, ], subscription_data: { trial_period_days: 14, }, success_url: `http://localhost:4242/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `http://localhost:4242/failed`, }); return session; }; module.exports = { addNewCustomer, createCheckoutSession, };

Notice that we can also specify the number of trial days as well.

Clicking the Checkout Button makes a POST request to /checkout the endpoint in app.js:"/checkout", async (req, res) => { const { customer } = req.session; const session = await Stripe.createCheckoutSession( customer, productToPriceMap.BASIC ); console.log(session); res.send({ sessionId:, }); });

Voila! Clicking the **Buy Button **opens up a secure Stripe Checkout screen. Stripe Checkout screen for subscription payment complete with a 14-day trial

Step 9: Add Success and Fail endpoints

We need to show feedback to the customer when a purchase is made. It can be as simple as a quick message or a colorful UI screen to show them how special they are. Either works.

In app.js

app.get("/success", (req, res) => { res.send("Payment successful"); }); app.get("/failed", (req, res) => { res.send("Payment failed"); });

Test out the app by running,

npm start
  1. Head on over to localhost:4242.
  2. Enter your email address. A Customer record should be made with that email address.
  3. Select a plan to buy. Complete the purchase with a test credit card. Enter 4242 4242 4242 4242, any expiry date, and CVV.
  4. Profit!

Next Steps

Congratulations, we just built a usable checkout flow for our SaaS app! We learned how to:

  1. Create a basic sign-in flow to keep track of users
  2. Let users pay for a subscription plan: $10/month for Basic Plan or $12/month for Pro Plan
  3. Present users with a Stripe Checkout screen where they can pay using their credit card
  4. Add a 2 weeks trial period before billing the user

There's still room for improvement. Right now, our application doesn't recognize if the user already has purchased a subscription or not so that's not useful. In the next part, we will save our Customer and their purchased subscription plan information to MongoDB so that we can show different views based on plan type.