11 min

Comprehensive Guide for Stripe Subscriptions with React & Nextjs 13+

Comprehensive Guide for Stripe Subscriptions with React & Nextjs 13+

This will be a comprehensive tutorial for working with Stripe Subscriptions in Nextjs 13+. By the end you will have everything you need to build and test a professional production Stripe subscription implementation.

Too keep this tutorial focused and concise we will only go over code relating to implementing subscriptions.

The code can be found here:
Source Code

By the end of this tutorial we will learn the following:

  • Implementing Stripe Subscriptions
  • Understanding the Stripe Subscription Flow
  • Signing up and Creating Products in Stripe Dash
  • Adding a Subscription with Stripe Hosted Checkout
  • Managing Subscription with Stripe Customer Portal
  • Working with Stripe Webhooks
  • Testing the Subscription implementation

Stripe setup subscription.

Subscription Flow

Generally the subscription process requires three main parts

  • Hosted Checkout to complete a purchase

  • Hosted Customer Portal to allow customers to update their subscription or payment information

  • Webhook to asynchronously update a database after subscription creation or subscription update

These three things are all that is required to build a subscription system.

Sign up and Create Products in Stripe Dash

The first thing we need to do to get started with stripe setup is to create products and prices in the stripe dashboard. Information on how to do this is found below.

Stripe Getting Started

Install Stripe Library

To start we can install the nodejs stripe SDK.

npm install stripe

ℹ We don't need @stripe/stripe-js. This is a client side library used with stripe elements to create a custom checkout flow. Since we are using the hosted checkout, we don't need to build the client side payment portal ourselves.

Add Stripe as env variables

Setup stripe dashboard and create product

Billing Quickstart

Initialize Stripe

1import Stripe from 'stripe';
3const stripe = new Stripe(process.env.PAYMENT_SECRET_KEY);
5export default stripe;

Adding a Subscription

To add a subscription we only need to return the checkout signed URL from the stripe API and redirect the user to it.

A user can complete the checkout process on the hosted stripe checkout portal. When they updated or cancel their subscription, we can catch that event with a webhook, which we can discuss in the next section.

1export const createCheckoutSession = async ({ price_id, user_id }) => {
2 try {
3 session = await stripe.checkout.sessions.create({
4 line_items: [
5 {
6 price,
7 quantity: 1
8 }
9 ],
10 mode: 'subscription',
11 success_url: `/billing/confirm`,
12 cancel_url: `/cancel_url`,
13 metadata: {
14 user_id
15 },
16 customer_email
17 subscription_data: {
18 trial_period_days: 14
19 }
20 });
21 } catch (err) {
22 throw err;
23 }
25return session.url;
28// component.tsx
29// redirect after fetching the stripe signed URL
31const handleSubscription = async (price_id: string) => {
32const redirectUrl = await createCheckoutSession({ price_id, user_id });

Managing Subscription

Once a user signs up for a subscription, they will also need to manage it. For example, update their subscription plan or cancel it.

Billing Portal settings

Go to billing portal setting in stripe dashboard.

Customer Management

Hosted Invoice

Limit customers to 1 subscription


Understanding The Stripe Subscription Flow

The webhooks will contain the meat of the code and business logic needed for subscription setup.


No need to listen to subscription.created event, a lot of events overlap and are duplicated.


What to save in the database?

Keep important data in own database to avoid stripe rate limits. Basically save info in your database that you plan to repeatedly use in your app.

For example if you want to display the customer billing cycle dates in your own app, its better to save this data and fetch it from your own database.

You should only ping the stripe api as needed and not use it as a secondary database.

When subscription data is updated on stripe, use a webhook to update it in your own database.

Stripe Rate Limit


In this section we will go over a few ways to test the stripe subscription flow.

Test Using the Webhook CLI

A simple way to test the webhook. Good for very base level testing on individual webhook events.

Not good for testing the full subscription flow because it creates simulated data rather than using data that is created and integrated into your app.

Stripe Webhook

Test Stripe Webhooks with ngrok

To test the full subscription from the app, it is best to use ngrok.

This is because stripe webhooks requires a public url endpoint to send webhooks events to, but this is something we don't have on local host.

ngrok solves this by giving us a public endpoint during local development, which allows us to receive real stripe webhook events during local development.

Create ngrok account. Run ngrok http 3000

Put ngrok endpoint as public stripe webhook endpoint

Add checkout complete and subscription updated events .

Substitute the signing
Setup Webhook public endpoint Add url, Add correct events to webhook endpoint.

Webhook Payment Events

Test with a dev environment

An alternative to this would be to simply test in a dev environment with a public url.

For example you can deploy your app to vercel and set the vercel url to the stripe webhook endpoint. This can be helpful and it doesn't require having to run ngrok and setting the ngrok url to the stripe webhook endpoint every time as the vercel url will be a fixed public url.

Test subscriptions over time

Another thing we may want to test is stripe subscriptions over time such as what happens after 14 days when the trial ends and how the subscription will work for 3 months of billing cycles.

The obvious challenge of this is waiting all that time.

Luckily stripe offers a solution to this in the form of testing clocks that allow you to test your subscription over a long period of time.

Test Subscription using test Clocks

Testing a failed payment

The following event can be used to test a failed payment.

Test attached card but failed payment

Error Handling

stripe offers several default error types. These can be used to customize the error reporting. Error Handling Github

Error Events Stripe

Other Stripe Settings and Setup

There are other stripe settings we can go over. These are usually not necessary for local development but should generally be setup before going live.

Checkout Portal

Customize Stripe Customer Portal with your Plans

Some settings we can set on the customer portal are Add the subscription products, canceling at the end of billing period and selecting the tax id.

Setting up trials
Trials Compliance

Smart Retries

Stripe Emails
Use Stripe to send transactional emails

Unit tests for stripe?

Since in our implementation we are basically just fetching a URL from the stripe API and redirecting the user, the usefulness of doing unit tests is very questionable.

It is far better to use a e2e test on the webhook with a library such as playwright as it gives us more tools to work with to test our integration in a more effective way.

Automated tests for stripe

For automated we will use unit testing, on the webhook handler.

Pass in a mock subscription webhook event object into the webhook handler, then assert whether your database was updated with the required data.

Generally it is considered bad practice to test third party libraries and sites. So the automated tests should not touch the stripe hosted portals.

For example we would not run input and submit events on the stripe hosted checkout to see if the hosted checkout page works correctly.

We would isolate our tests to only our responsibility which is only the webhook.