Want to process recurring SaaS billing subscriptions using Stripe for your Node.js application? We've got you covered in this extensive 10-step guide.

While Stripe has made SaaS billing a dream come true for developers, setting up a recurring billing system still requires a fair amount development work.

This guide will walk you through the process of setting up your Stripe account and writing the code required to charge recurring subscriptions. We'll integrate multiple payment plans and also build some error handling for failed payments.


This guide is aimed at intermediate Node.js developers. You will also need to have an SSL certificate in place for when you want to deploy your live application.

Enough rambling, let's get started!

1. Create Your Stripe Account

The first thing you'll need to do is create a Stripe account. Setup is easy and should only take a few moments of your time.

Once you've registered and signed in, you'll see your Stripe dashboard.

Stripe Dashboard 

Stripe comes with two development modes: Test and Live – this is a great feature that enables you to build your payment engine without using real data – you can even use test credit card numbers to test different scenarios and error messages.

Go ahead and toggle the switch in the left sidebar menu to start working in test mode.

Create a  Product

Next, you need to create a product (you can sell multiple products from your Stripe account).

Billing > Products > New

Creating a new product in Stripe

Enter a product (your application) name and click Create Product.

Add Your Pricing Plans

The next screen will prompt you to add a pricing plan. Each product can have multiple pricing plans, so if you have three different pricing plans, you can configure them all here.

Create a Stripe pricing plan

Give your plan a nickname for your reference and give it an ID. I recommend using something easy to identify, like plan_startup instead of using Stripe's auto-generated ID.

This will make your life easier when coding the back-end of your application.

You can also set the currency, billing interval and a trial period here. For SaaS products, keep the pricing set to Recurring quantity and set the interval to monthly or annual. You can leave "Does this pricing plan have multiple price tiers based on quantity?" set to no.

Great job! That was easy, right?

Locate Your Stripe API Keys

Before you can jump into the fun part of writing the code that will make you some sweet, sweet dollar, you need to locate your API keys.

Stripe has two API keys: a publishable key for the front-end and a secret key which is used server-side.

There are a different set of keys for working with live and test data. To build and test your application, you can use the test keys, but when you deploy your application – ensure you switch to the live keys.

Click on Developers > API Keys

Locating your Stripe API keys

If you're in test mode, then the keys you see here will be the test API keys, the tokens will start with pk_test and sk_test when viewing the test keys. In live mode, these tokens will begin with pk_live and sk_live.

Make a note of these API keys somewhere safe, as you'll be using them soon to connect your Node.js application to Stripe.

Using the Test Card Numbers

Pro-tip: while you're building your application over the next steps, it will make sense to be able to execute some test payments to ensure that your code is working.

To make your life super-easy, Stripe provides a list of card numbers for testing different scenarios like successful or failed payments.

2. Building a SaaS Billing Payment Form

Okay – now for the fun part!

Let's start by creating a payment form. Thanks to Stripe, this is super-simple to set up, and you don't have to worry about security or where the card details are stored – it's all handled for you on Stripe's server.

To process a payment, you only need to collect the customer's email address and credit card details, but you can collect as much information here as you like for your use in your application.

For this tutorial, we're going to create a payment form with:

• First name
• Email address
• Password
• Pricing Plan
• Card Details

So go ahead and whip up a simple HTML form with inputs for each of these fields. Stripe will automatically generate the credit card input in the next step, so create an empty div with a new class name, like .signup-creditcard

Here's an example of the payment form from Gravity if you need some inspiration.

3. Add Stripe Elements

Now, let's make your payment form come alive!

When your payment page loads, you need to initialise Stripe to show the credit card input field and handle any card errors. For this, we're going to use the new Stripe Elements – which makes this process simple.

Let's create our own initialiser function to handle this, and then call this when the document loads (how you do this will depend on which front-end framework you're using). For example, if you're using jQuery then you can do this simply by:


  // call custom Stripe initialiser function


Next, we'll create the initStripe() function.

function initStripe(){

  stripe = Stripe(YOUR_PUBLIC_KEY);

  elements = stripe.elements();
  card = elements.create("card");


  // handle card errors
  card.addEventListener('change', function(event) {

    if (event.error){
      showFormError($(".signup-creditcard"), event.error.message);
    } else {


On line 3, we create a new stripe object, and pass your public API key (use the test key for now, you will need to replace this later).

On line 5-6 we create a new Stripe elements object and a new card object.

On line 8 we mount the card to the empty div we created in the previous step.

This will automagically replace the empty div with a beautiful card input box.

The input box also comes with its own inline validation and error handling process, so that's another thing you don't need to worry about.

You can simply add a new event listener to the card object on line 11 to handle any errors when the input changes.

You will need to create your own error handler functions for this depending on how you want to display the error. If you're using Gravity, which comes with an in-built validation framework – then the error message will be automatically applied to the credit card input field.

However, you could also choose to display a notification bar at the top of your webpage. The error message returned from Stripe is stored in:


4. Processing The Payment

Your payment form should be looking pretty sweet by this point, but it still can't do the most crucial part – process payments!

Make sure you have a button included in your payment form markup, and then create a signup() function to handle the payment and register the user.

$(".btn-signup").on("click", signup);

function signup(e){


  // validate
  if (validateForm(".signup-form")){


    // user details are valid, now validate credit card

      if (result.error) {
        // show the error message with: result.error.message
      else {

        // card is valid, create user

          type: "POST",
          url: "/user/create",
          data: {

            name: $(".signup-name").val(),
            email: $(".signup-email").val(),
            password: $(".signup-password").val(),
            stripe_id: result.source.id,
            plan: $(".signup-plan option:selected").val()

          success: function(res){

           // redirect to dashboard
           if (res.success){
             window.location = "/dashboard";
           else {

             // handle error server error

First, disable the default action of this button which is to submit the form (line 5).

You will want to perform some form validation here to make sure the name, email and password have been entered correctly (line 8)

I also toggle a loading animation on line 10 for the payment button to provide feedback to the user – we don't want the user to think that nothing is happening for a few seconds while the payment is processing.

One line 13 we create a new Stripe source (the source is the customer’s payment instrument).

On line 15 we use an if statement to test for an error from Stripe and display an error message if the payment failed, which is stored in:


If no errors occurred, then the else statement is triggered and we pass the data to the back-end of the Node.js application to create the user and the Stripe subscription.

In the example, I'm passing through the values of name, email, password for storing in the database, but most importantly:

  1. The plan (which we'll use to create the subscription)
  2. The source ID is located in

In the success function of the AJAX call, you want to direct the user straight to your application. If a problem occurred on the server-side, then show an error message.

OK, breathe! Let's take a short-break from coding and configure the database before we dive into the back-end.

5. Create the SaaS Billing Database

You should structure your database to have a user table similar to the following:

• user_id
• user_name
• user_email
• stripe_customer_id (varchar 18)
• stripe_subscription_id (varchar 18)
• plan
• active

You can, of course, add as many additional fields to your user table as you like.

6. Create The Back End in Node.js

Still with me?

Now for the tricky part, you need to take the data from the front-end and process it in your Node.js application.

First, you'll need to install the Stripe framework for Node. Open a new terminal window and cd to your application directory. Then, run:

nmp install stripe

Create a user.js file in your application and add the following to the top of the file:

const stripe = require("stripe")(YOUR_SECRET_API_KEY);

On the front-end in our signup() function, we created an AJAX POST call to:


The process for how you handle this route this will vary depending on which framework you're using. With Express.js, you simply create an endpoint and link it to a handler function:

api.post("/user/create", userController.create);

I'm using an MVC pattern for this, so this route will be handled by a controller function, which will pass the data through to the user model.

Your controller function should look similar to this:

exports.create = function(req, res){


    req.body.name, req.body.email, req.body.password,
    req.body.stripe_id, req.body.plan






On line 5-6 we're passing through the value of the inputs from the front-end which are stored in the req.body object.

As user.create (covered next) returns a promise, this is handled on lines 8-16. If the promise resolves, we return msg to the front-end or err if there is a problem. Both of these objects can then be accessed in the success() AJAX handler on the front-end (refer to section 4).

7. Create the Stripe Customer

We'll create a user.create method that will perform a range of tasks, like encrypting the password and storing this in the database; but for the objective of this tutorial, let's just focus on creating the Stripe subscription. Add the following function to your user.js file.

exports.create = function(name, email, password, stripe_id, plan){

  return new Promise(function(resolve, reject){
  	// code to check if user already exists
    // plus any other custom code here
    createStripeCustomer(email, stripe_id).then(function(customerId){

      // add the user to a stipe subscription plan
      return subscribeToPlan(customerId, plan);


		// add the user to the database
        // set permissions
        // send a welcome email


	  // handle error and return to front-end via controller


Add any custom code you need on lines 5-6, for example: checking if a user already exists and then storing them in the database.

When processing a subscription payment on Stripe, you first need to create a new customer and then add the customer to a plan (remember you set up your plans on the Stripe dashboard in step 1).

Let's look at both of these Stripe functions  in more detail.

function createStripeCustomer(email, stripe_id){

  return new Promise(function(resolve, reject){

    // create stripe customer
    const customer = stripe.customers.create({

      email: email,
      source: stripe_id

    }, function(err, customer){

      if (err)


This function returns a promise because we're going to use an async function from the Stripe API, so we don't want to return until the request has completed.

On line 6, we use the Stripe object to create a new customer and assign the response to a constant called customer.

All you need to do to create the customer on Stripe is pass the email and the source_id (created on the front-end in Step 4).

In the callback function, you simply test the err object for an error and reject the promise with an error message.

If there's no error, then resolve the promise and return customer.id to the caller function (user.create), which will then pass it to the next function in the promise chain: subscribeToPlan()

8. Subscribe the Customer to a Plan

Finally, let's add the customer to one of the subscription plans you created in step 1.

function subscribeToPlan(customerId, plan){

  return new Promise(function(resolve, reject){

    // add customer to plan
    const subscription = stripe.subscriptions.create({

      customer: customerId,
      items: [{ plan: plan }]

    }, function(err, subscription){

      // handle stripe API error
      if (err) 
      	reject(err.message );
        // save subscription.customer & subscription.id to database


You're going to pass two parameters to this function:

customerId - returned from the previous function: createStripeCustomer()
plan (passed from the front-end)

First, we use the subscriptions.create method of the stripe object and assign the result to a constant called subscription.

You need to pass the customerId and the plan to assign the customer to the plan.

Important: you need to pass the plan ID, not the plan nickname. This is why I recommended creating your own plan ID at the beginning.

Finally, we test the err object for any problems and reject the promise with the error message, which can then be displayed to the user on the front-end.

If there's no errors, then you should add your own code to update your user table and save:

• subscription.customer
• subscription.id

These will be required in future if you ever need to amend or change a subscription.

9. Creating The Webhook

The code you've written so far will enable you to process your first payment, but that's just the start – you'll be processing payments for each customer on a monthly or annual basis, so what happens if a payment fails because a customer's card has expired (or for any other reason)?

Stripe lets you create webhooks, which will be called any time there is an issue with processing a payment.

To add a Webhook to your application, visit your Stripe dashboard and navigate to:

Developers > Webhooks > Add Endpoint

Creating a Stripe webhook for your Node.js application

The webhooks that we're interested in are:

• invoice.payment_failed
• invoice.payment_succeeded

You will need to specify an URL end-point in your application, for example:


You'll then need to set up a route for this in your Node.js application, like so:

api.post("/stripe", userController.stripe);

The controller function for this route, should look like this:

exports.stripe = function(req, res){






We simply call our custom function – handleStripeRequest() and then send a 200 or 303 status back to Stripe. You must ensure you're returning a response to Stripe for your webhook to work correctly.

Now, let's take a look at the handleStripeRequest() function.

function handleStripeRequest(req){

  return new Promise(function(resolve, reject){

    var stripe = req.body;
    var subscriptionId = stripe.data.object.subscription;

    // payment failed
    if (stripe.type === "invoice.payment_failed")
      return handleFailedStripePayment(subscriptionId);
    // payment successful
    if (stripe.type === "invoice.payment_succeeded")
      return handleSuccessfulStripePayment(subscriptionId);

On line 5, we take the request.body and assign it to a variable called stripe for clarity.

Next, we test the type of the stripe object to determine which event has been sent to the webhook.

We're interested in these two events:

• invoice.payment_failed
• invoice.payment_succeeded

We can test both of these using stripe.type within an if statement and then handle them separately.

How you handle these events is entirely dependant on your own business processes – you may provide a grace period for your customer to resolve the issue, or you may block access to their account immediately.

You can configure re-try attempts in your Stripe dashboard:

Billing > Settings

Configuring card payment retries in Stripe

We recommend doing the following:

Payment failure: send the user an email and notify them that you will attempt to charge their card again in 1 day – this gives them time to update their card details or correct whatever issue caused their card to fail. Block access to your application with a notification that their subscription payment has failed.

Payment success: send a courtesy email to let the customer know that you've charged their card. You can do this automatically within Stripe.

10. Ship Your SaaS Application

Woohoo! Finally, if you've made it this far you're ready to deploy your new application and start making money! Soon you'll be working from your laptop in the tropics ;-)

Remember that you'll need to swap out both of the test API keys for the live keys before you deploy your application.

Publishable key: located in the initStripe() function on the front-end.

Secret key: located at the top of your user.js file on the back-end where you create the Stripe object.

You will also need to re-create the product and pricing plans that you created in test mode, in live mode (on your Stripe dashboard).

Too Much Hassle?

Despite Stripe being an awesome product that takes the pain out of payments – processing SaaS subscriptions is actually a lot of work to set up, and we've only just touched the surface!

There are a lot of other things that you need to consider:

• How does a customer upgrade or downgrade to a different plan?
• How does a customer cancel their subscription?
• How does a customer update their credit card details?

If you don't want to spend weeks of time building all this functionality, then the good news is that we've created a product that removes all this headache for you.

Gravity is a full SaaS framework for Node.js that contains a complete Stripe SaaS subscription engine - you register your Stripe account, add your API keys to Gravity and all of this tedious work is taken care of for you – so you can focus on building features that will delight your customers and generate more sales.