Ship It Blog

Ship It Blog

Stripe SaaS Billing for Node.js

Introduction

If you have a web application that you want to sell online, you will first need to integrate a billing provider. Stripe is one of the most popular billing providers, in no small part due to their excellent developer experience. In this article, we will walk through how to integrate Stripe into your Node.js web application.

Ship It

As a shameless plug, Ship It let’s you bypass the complexities described in this article and focus on building, marketing, and iterating on your SaaS product. It also streamlines user authentication with Auth0 and HTTPS certificates, meaning you can productize your web application in minutes!

Stripe No Code Solutions

Stripe offers several solutions to implement portions of your billing flow without writing any code. This includes their Checkout and Billing Portal products. These are Stripe-hosted pages that allow users to enter their payment information and manage their subscriptions. You can customize the branding on these pages such as the logo and colors.

Even with these low code solutions, most applications will still need to implement some new endpoints:

  • An endpoint to create a checkout session for the user and redirect them to the Stripe-hosted page.
  • A callback endpoint to do bookkeeping when the user completes the checkout process.
  • A webhook endpoint to receive notifications from Stripe about changes to the user’s subscription (such as cancellations).
  • An endpoint to redirect the user to the Stripe-hosted billing portal.

There is also some nuance to using Stripe’s API efficiently and simply, which we will cover later in this article.

Billing Model

To use Stripe’s low code solutions, it’s important to choose a simple billing model. Complex options like metered billing may prevent you from using Stripe’s low code solutions and require additional code to implement.

A safe approach is to offer 1-3 products with a fixed monthly fee. You can offer discounts for annual billing, support discount codes, enable free trials, and allow adjustable quantities. As you can see, this is not a particularly limiting set of features, but things like metered billing are tempting or even necessary for some businesses. Keep in mind that customers may prefer the predictability, so it is not just the additional complexity that you are avoiding with a fixed rate.

Stripe No Code Configuration

To enable Stripe’s low code pages, you will need to do some configuration:

  • Under settings, select “Customer portal”. Enable it and customize the experience as desired.
  • Under settings, select “Branding” and customize logo and colors used for billing portal and checkout.
  • Under “Developers”, click “Webhooks” and configure the webhook endpoint for your application (described below). You will need to select the key events that you want to monitor. A good start would be customer.subscription.deleted and customer.subscription.updated.

You will want to store the Webhook secret from the last step in a secure place because it will be needed in your application.

Create a Stripe Customer in Node.js

First, install Stripe’s JavaScript client:

npm install --save stripe

Next, update your user creation endpoint to create a Stripe customer. This guide assumes that you already have a User`` model with a stripeCustomerId` field, which is added to the request object by your authentication middleware.

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.post('/users', async (req, res) => {
    const user = await User.create(req.body);
    
    const customer = await stripe.customers.create({
        email: user.email,
        name: user.name,
    });
    
    user.stripeCustomerId = customer.id;
    await user.save();
    
    res.json(user);
});

Stripe No Code Redirects in Node.js

Below is some code to redirect users to the default billing portal (the one created in the Stripe dashboard) in a Node.js route:

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.get('/billing', async (req, res) => {
    const session = await stripe.billingPortal.sessions.create({
        customer: req.user.stripeCustomerId,
        return_url: `${process.env.BASE_URL}/`,
    });
    
    res.redirect(session.url);
});

You will also need a redirect for the checkout page when a user wishes to subscribe. The checkout session accepts a variety of configurations which you should be sure to understand the implications of.

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.get('/checkout', async (req, res) => {
    const session = await stripe.checkout.sessions.create({
        customer: req.user.stripeCustomerId,
        payment_method_types: ['card'],
        line_items: [
            {
                price: process.env.STRIPE_PRICE_ID,
                quantity: 1,
            },
        ],
        mode: 'subscription',
        success_url: `${process.env.BASE_URL}/`,
        cancel_url: `${process.env.BASE_URL}/`,
    });
    
    res.redirect(session.url);
});

Stripe Callback in Node.js

When a user completes the checkout process, Stripe will redirect them to the success_url or cancel_url that you specified. You will need to implement a route to handle the success_url and update your user’s subscription status. Here we assume that the User model has a subscriptionStatus field.

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.get('/checkout/success', async (req, res) => {
    const session = await stripe.checkout.sessions.retrieve(req.query.session_id);
    const subscription = await stripe.subscriptions.retrieve(session.subscription);
    
    req.user.subscriptionStatus = subscription.status;
    await req.user.save();
    
    res.redirect('/');
});

Stripe Webhook in Node.js

Finally, you will need to implement a webhook endpoint to receive notifications from Stripe about changes to the user’s subscription. This is important because the user may cancel their subscription from the Stripe-hosted billing portal, and you will want to update your user’s subscription status accordingly. Be sure to use the Webhook secret that you stored from the Stripe dashboard to validate these requests.

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.post('/stripe/webhook', async (req, res) => {
    const sig = req.headers['stripe-signature'];
    const event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
    
    if (event.type === 'customer.subscription.deleted') {
        req.user.subscriptionStatus = 'canceled';
        await req.user.save();
    } else if (event.type === 'customer.subscription.updated') {
        req.user.subscriptionStatus = event.data.object.status;
        await req.user.save();
    } 
    
    res.sendStatus(200);
});

Conclusion

As you can see, Stripe makes it relatively easy to implement a billing flow for your Node.js, but there are still some complexities to be aware of. This isn’t really something you want to get wrong, and spending time on this means time not spent on your core product.

Ship It eliminates the need to implement new endpoints, and your user’s subscription status available via simple request headers. Ship It supports the streamlined billing model described above and still allows you to customize the billing flow in Stripe’s dashboard. Ship It also implements a no-code, branded thank you page and pricing table that supports existing Stripe customers (preventing duplicate customer entries), something that is not offered by Stripe. If you are looking for a faster solution to get your SaaS products to market quickly, check out Ship It.