Recently I spent a weekend adding paid subscriptions to my project, so I'm writing this article to share the process and what I wish I had known before I started!
This tutorial will assume some experience with React and TypeScript. You'll also need a database to use, any one that's compatible with Prisma. I'll be using Postgres. You can follow my last tutorial (up to the REST API section) for a beginner's guide on setting up Postgres locally, and an intro to Prisma.
The Goal
In this tutorial, we'll create a Next.js site, set up a database, add user registration (via GitHub OAuth), and give customers the ability to sign up for a paid subscription using a Stripe Checkout hosted page. Many of the same concepts apply even if you're using a different OAuth provider, a custom payment form, or a different payment provider.
We're going to set up a system like this: when a user registers for an account on your site, we'll also create a customer in Stripe's system for the user, and we'll save the Stripe customer ID in our database with the user's data. Then, when a user wants to add a subscription to their account on our site, we can use that Stripe customer ID to easily mark the user as a paying user in our system, and then allow them access to our services. We'll also discuss next steps to allow users to cancel their subscriptions and more. The flow will look like this:
TLDR
- Set up a Next.js Project
- Add Prisma and set up a Database
- Add Next-Auth and configure account creation
- Create a Stripe Account
- On account-creation, use a Next-Auth event to create a Stripe Customer and link them
- Allow the frontend to request a Stripe Payment Link from the backend, pre-linked to their customer ID
- Use Stripe Webhooks to activate the customer's subscription in our database when they complete a checkout
- Test the flow
Set Up a Project
Follow the excellent official guide here to set up a Next.js project. I'm using TypeScript, which works especially well with Prisma.
npx create-next-app@latest --typescript
When that's finished, make sure you have typescript and React types installed using:
npm install --save-dev typescript @types/react
To do some cleanup, you can delete all of the content inside the <main>...</main>
section of index.tsx
.
Adding Prisma and Database Setup
One mistake I made was that I implemented my entire authentication system and database schema without accounting for payment-related fields. We'll fix that by creating our initial schema with both next-auth
and stripe
in mind.
Next-Auth and Stripe
Next-Auth is a great way of easily adding user registration and authentication to your Next.js projects. Its docs provide you everything you need to get started with a huge variety of authentication providers and databases. You can read more about it at https://next-auth.js.org/.
Stripe is one of the most popular payment systems existing today. It essentially allows you to build payment forms into your apps, websites, and servers, and it handles all of the complex logic behind communicating with credit card companies and banks to actually get you your payment. It supports a ton of use cases, including paid subscriptions, which is what we'll use it for. Read more about it at https://stripe.com/.
Setting up the Prisma Schema
First, we'll set up Prisma. If you get stuck on this part, check Prisma's documentation. Start by creating a new folder in your project folder called prisma
, and then a file called schema.prisma
inside the folder.
Next we need to determine what else to put in this schema file. The schema file determines the structure of the database and TypeScript types that Prisma will generate.
We need to connect Next-Auth to Prisma, so that it can actually save user accounts after they're created. To do that, we'll use the official Next-Auth Prisma Adapter. We'll install it later, but for now, copy the text from the schema file shown here and paste it into your schema file. These are the fields which the Next-Auth Prisma Adapter requires for its features to work. If you're not using Postgres, you'll need to change the database
part at the top of the file; check Prisma's documentation for more info on how to do that. You should also delete the shadowDatabaseURL
and previewFeatures
lines, unless you're using an old version of Prisma, which you shouldn't be doing :)
We'll also add a field for the Stripe customer ID. This will give us a method to link newly created subscriptions with existing customers in our database. And lastly, we'll add a boolean field isActive
to determine if a user should have access to our service. Add these lines inside the User model in the schema:
model User {
...
stripeCustomerId String?
isActive Boolean @default(false)
}
Lastly, depending on which Authentication provider you want to use, you may need to add some additional fields. Authentication provider refers to services we can use for our users to sign-in with, such as "Sign in with Google" or "Sign in with Facebook." Next-Auth has a long list of built-in providers. For this tutorial we'll use GitHub.
The GitHub provider requires one additional field, so add this to the Account model:
model Account {
...
refresh_token_expires_in Int?
}
Set Up your Environment Variables
Now that the schema is complete, we need to actually link Prisma to our database. First, add a line which says .env
to your .gitignore file. This is EXTREMELY important to make sure you don't actually commit your environment variables and accidentally push them to GitHub later.
Next, create a file called .env
in your project folder (not in the Prisma folder). The content to add will depend on your database. For a local Postgres database, you should write the following in your .env.local
: postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA
. To create a new database in psql
, run create database subscriptionstutorial
or swap out "subscriptionstutorial" for another name specific to your project.
Create the database and Prisma client!
Run npx prisma migrate dev --name init
to set up your database. If you hit any syntax issues with the schema, re-check the schema on the Prisma docs and the fields above. If you hit issues with the database connection, check your database through your CLI (for example, using psql
for Postgres) to make sure its online and you have the right Database URL.
What just happened?!
- Prisma checked your
.env
for the database URL. - Prisma created and ran SQL commands for you, automatically, to create database tables with columns in a structure that match your schema.
- Prisma created the Prisma Client, which contains fully-typed methods for interacting with your database, with the types corresponding to your schema.
Create a dev-safe Prisma Client instance
If we want to actually use Prisma Client to interact with the database, we need to create a client with new PrismaClient()
. However, in development mode, hot-reloading can cause the Prisma Client to regenerate too many times.
To fix that, we can use a shared, global Prisma Client in development. Create a file in the prisma folder called shared-client.ts
and add this content:
import { PrismaClient } from '@prisma/client';
import type { PrismaClient as PrismaClientType } from '@prisma/client';
let prisma: PrismaClientType;
if (process.env.NODE_ENV === 'production') {
prisma = new PrismaClient();
} else {
if (!global.prisma) {
global.prisma = new PrismaClient();
}
prisma = global.prisma;
}
export { prisma }
Set Up Next-Auth
Next, we'll add user account creation to our site. Since we're using Prisma for connecting Next-Auth to the database, and GitHub as our OAuth provider, we'll base the configuration off of the docs pages for the Prisma adapter and the GitHub provider.
First, do npm install next-auth @prisma/client @next-auth/prisma-adapter
. The GitHub provider is built-in to next-auth
, it doesn't require a separate package.
Delete the file /pages/api/hello.js
and add a new file pages/api/auth/[...nextauth].ts
.
In the file, add this content:
import NextAuth from "next-auth";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import GithubProvider from "next-auth/providers/github";
import { prisma } from "../../../prisma/shared-client";
export default NextAuth({
providers: [
GithubProvider({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
}),
],
adapter: PrismaAdapter(prisma),
}
To create the GitHub Client ID and Client Secret, go to https://github.com/settings/profile, Developer Settings
on the left-hand nav-bar, OAuth Apps
, New OAuth App
. Fill in a name and your localhost with port for the homepage URL. Copy the homepage URL and add /api/auth/callback/github
. This will allow the /api/auth/[...nextauth].ts
file to catch this callback URL and use it to create a user in the database. The form should look something like this:
After you create the OAuth app, add the Client ID, a Client Secret, and your local URL into your .env
like this:
GITHUB_CLIENT_ID="fill-in-value-from-github-xyz123"
GITHUB_CLIENT_SECRET="fill-in-value-from-github-abc123"
NEXTAUTH_URL="http://localhost:3000"
As an additional convenience, we'll extend the session
object to contain the user ID. Add a callbacks field with a session
callback function which returns an extended session like this:
export default NextAuth({
providers: ...,
adapter: ...,
callbacks: {
async session({ session, user }) {
session.user.id = user.id;
return session;
},
},
}
TypeScript users will also need to extend the session.user
type to add this field to it. In the project root, create a file called types.d.ts
and add this content there:
import type { DefaultUser } from 'next-auth';
declare module 'next-auth' {
interface Session {
user?: DefaultUser & {
id: string;
};
}
}
This is the basic Next-Auth setup - technically, we could now add the frontend sign-up form. But instead, before we get there, we should plan ahead for how we'll connect the user accounts with Stripe.
When we create a user, we'll also create a Stripe customer. This will allow us to easily link customers in our DB to the subscriptions and their payments when customers pay after creating an account.
Set up Stripe
Set up a Stripe Account
On Stripe's website, create a new account and a business. You don't need to enter all of your business information, especially if you don't have it yet! Just enter the minimum info to get started.
Add Stripe to the Project
The part of this tutorial that I spent the most time figuring out was how to connect Stripe customers to our site's accounts. This setup will allow for that.
Add Stripe's node.js SDK to the project with npm install stripe
.
Go to https://dashboard.stripe.com/test/apikeys, which should look like this:
On the "Secret Key" row, hit Reveal test key
and copy that key into your .env
like this:
STRIPE_SECRET_KEY="sk_test_abc123"
You don't need the Publishable key at the moment!
Create a Stripe Customer for Newly Registered Accounts
To accomplish this, we'll use the Next-Auth
event system. Events allow Next-Auth to do some custom action after certain user actions like creating a new account or signing in, without blocking the auth flow. Read more about the event system here.
In the [...nextauth].ts
file, add the events
field as an object with a createUser
function like this:
export default NextAuth({
providers: ...
adapter: ...,
callbacks: ...,
events: {
createUser: async ({ user }) => {
});
}
})
Next-Auth will call this function after a new user account is registered.
Inside of the function, we'll use the Stripe SDK to create a customer, and then add the Stripe customer ID to our saved record for the customer account:
createUser: async ({ user }) => {
// Create stripe API client using the secret key env variable
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, {
apiVersion: "2020-08-27",
});
// Create a stripe customer for the user with their email address
await stripe.customers
.create({
email: user.email!,
})
.then(async (customer) => {
// Use the Prisma Client to update the user in the database with their new Stripe customer ID
return prisma.user.update({
where: { id: user.id },
data: {
stripeCustomerId: customer.id,
},
});
});
},
Woohoo! If you're with me so far, we've finished the hardest part!
Front-end and Payment form
We're finally ready to build the frontend!
Sign Up Form
Rename pages/index.js
to pages/index.tsx
and then open that file.
Import the frontend parts of next-auth by adding this line to the top of the file:
import { signIn, signOut, useSession } from 'next-auth/react'
Next-Auth automatically manages and updates the state of the data returned by useSession
, so we can use that hook to track the customer's sign-in status and account.
In the exported Home page function, add:
const {data, status} = useSession()
In the tag, which should be empty, add the following content to decide what to render based on the user's status:
<main>
{status === 'loading' && <p>Loading...</p>}
{status === 'unauthenticated' && <button onClick={() => signIn()}>Sign In</button>}
{status === 'authenticated' && <button onClick={() => signOut()}>Sign Out</button>}
{data && <p>{JSON.stringify(data)}</p>}
</main>
Note: the signIn()
function handles both registering for a new account and signing in to an existing account.
We also need to add a global data provider for the useSession
hook to connect to. Set this up in _app.js
like this:
import "../styles/globals.css";
import { SessionProvider } from "next-auth/react";
function MyApp({ Component, pageProps }) {
return (
<SessionProvider session={pageProps.session}>
<Component {...pageProps} />
</SessionProvider>
);
}
export default MyApp;
Test Account Creation
Run the site with npm run dev
.
You should see a button which says Sign In
.
Click there, then Sign in With GitHub
, and follow the authorization flow.
If everything worked, you should be returned to your frontend with the button now reading "Sign Out" and your account data below. Also, if you go to your Stripe Dashboard and check the Customers tab, you should see a row with a new customer that has your GitHub account's email!
Use Stripe To Add Payments
The Approach
Most of our Stripe integration will be powered by a Stripe Checkout page, and Webhooks.
A Stripe Checkout page is a single page that Stripe automatically generates for us, with a payment form that full form functionality, accessibility, and more features. It's a great way to quickly add flexible payments to your site. The one challenge is that it's hosted on Stripe's site, not part of our codebase, so we need some way to send data from Stripe back to our system after a customer purchases a subscription on the Stripe Checkout Page.
To solve that problem, we'll use webhooks. A webhook is nothing super new - it's an API endpoint in OUR system that an EXTERNAL system can use to communicate with our system. In our case, the webhook API endpoint will allow Stripe to "hook" into our system by sending some data for our server to process and handle.
In summary: after creating an account, we'll redirect new users to the Stripe Checkout page for them to pay. Then Stripe will call our webhook to send some data back to our system, and we'll update the database based on that data.
Get the Stripe CLI
To watch all of the events that Stripe sends over webhooks in real time, we'll use the Stripe CLI so that Stripe can post its events to our local devices.
Follow the instructions here to install the Stripe CLI.
Next, follow Step 3 here to connect Stripe to your local server. Use the URL http://localhost:YOUR_PORT/api/webhooks/stripe which we will create in the next step. For example, mine is http://localhost:3000/api/webhooks/stripe
.
When you get the CLI installed and started, copy the "webhook signing secret" which the CLI will print into a temporary note.
Create the Webhook
Create a new file pages/api/webhooks/stripe.ts
.
Since we are using a public-facing webhook, we have a small problem: imagine if a hacker found this Stripe webhook and sent some fake data about a payment - they could trick our system into giving them access to the benefits of a paid subscription.
Therefore, before we can trust data from a Stripe webhook, we need to check if the request actually came from Stripe. After we verify the call is from Stripe, we can read the data and take some action.
This post by Max Karlsson explains the Stripe verification process in Next.js API Routes really well, so I won't go through it in detail. I'll just include my final webhook code here, which verifies the webhook data and then uses Prisma to update the user to isActive=true
when they've paid:
import type { NextApiRequest, NextApiResponse } from 'next';
import { buffer } from 'micro';
import Stripe from 'stripe';
import { prisma } from '../../../prisma/shared-client';
const endpointSecret = // YOUR ENDPOINT SECRET copied from the Stripe CLI start-up earlier, should look like 'whsec_xyz123...'
export const config = {
api: {
bodyParser: false, // don't parse body of incoming requests because we need it raw to verify signature
},
};
export default async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
try {
const requestBuffer = await buffer(req);
const sig = req.headers['stripe-signature'] as string;
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, {
apiVersion: '2020-08-27',
});
let event;
try {
// Use the Stripe SDK and request info to verify this Webhook request actually came from Stripe
event = stripe.webhooks.constructEvent(
requestBuffer.toString(), // Stringify the request for the Stripe library
sig,
endpointSecret
);
} catch (err: any) {
console.log(`⚠️ Webhook signature verification failed.`, err.message);
return res.status(400).send(`Webhook signature verification failed.`);
}
// Handle the event
switch (event.type) {
// Handle successful subscription creation
case 'customer.subscription.created': {
const subscription = event.data.object as Stripe.Subscription;
await prisma.user.update({
// Find the customer in our database with the Stripe customer ID linked to this purchase
where: {
stripeCustomerId: subscription.customer as string
},
// Update that customer so their status is now active
data: {
isActive: true
}
})
break;
}
// ... handle other event types
default:
console.log(`Unhandled event type ${event.type}`);
}
// Return a 200 response to acknowledge receipt of the event
res.status(200).json({ received: true });
} catch (err) {
// Return a 500 error
console.log(err);
res.status(500).end();
}
};
With me still? Just a few more steps 😃
Create your Subscription Plan in Stripe
In order for our customers to subscribe to a subscription, we need to actually create the payment plan in Stripe. Go to the Products tab in Stripe. Click "Add Product" in the top right and fill out the form with a name and any other info you want to add. For a subscription model, in the Price Information section, make sure to choose "Pricing Model: Standard", select "Recurring", choose your Billing period (how often the customer is charged, renewing the subscription) and enter a price. It should look something like this:
When you're done, press "Save Product". You're taken back to the product tab, where you should click on the row of the product you just added. Scroll to the "Pricing" section on the Product page, and copy the Price's "API ID" into a note file. It should look something like price_a1B23DefGh141
.
Add an Endpoint to Create Payment Pages for Users
Stripe will host the payments page, but we want to dynamically generate that page for each user, so that we can automatically link it to their pre-existing Stripe Customer ID, which is linked to their User Account in our database. (phew, that's a mouth-full).
Remember much earlier, when we added the User ID to the Session? That will become useful now so that we can link the Checkout Page to the user in the current session.
Add a file pages/api/stripe/create-checkout-session.ts
. Add this content to the file, which includes some error handling:
import type { NextApiRequest, NextApiResponse } from 'next';
import { getSession } from 'next-auth/react';
import Stripe from 'stripe';
export default async (req: NextApiRequest, res: NextApiResponse) => {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, {
apiVersion: '2020-08-27',
});
// This object will contain the user's data if the user is signed in
const session = await getSession({ req });
// Error handling
if (!session?.user) {
return res.status(401).json({
error: {
code: 'no-access',
message: 'You are not signed in.',
},
});
}
const checkoutSession = await stripe.checkout.sessions.create({
mode: 'subscription',
/* This is where the magic happens - this line will automatically link this Checkout page to the existing customer we created when the user signed-up, so that when the webhook is called our database can automatically be updated correctly.*/
customer: session.user.stripeCustomerId,
line_items: [
{
price: // THE PRICE ID YOU CREATED EARLIER,
quantity: 1,
},
],
// {CHECKOUT_SESSION_ID} is a string literal which the Stripe SDK will replace; do not manually change it or replace it with a variable!
success_url: `http://localhost:3000/?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: 'http://localhost:3000/?cancelledPayment=true',
subscription_data: {
metadata: {
// This isn't 100% required, but it helps to have so that we can manually check in Stripe for whether a customer has an active subscription later, or if our webhook integration breaks.
payingUserId: session.user.id,
},
},
});
if (!checkoutSession.url) {
return res
.status(500)
.json({ cpde: 'stripe-error', error: 'Could not create checkout session' });
}
// Return the newly-created checkoutSession URL and let the frontend render it
return res.status(200).json({ redirectUrl: checkoutSession.url });
};
Why don't we need signature verification here? The data is coming from our frontend, not Stripe. Ok, but do we need to verify the request is actually from our frontend? No, because this endpoint doesn't have any ability to update the customer status in the database. If a 3rd party managed to call this endpoint, all that they would get is a link to a payment page, which doesn't provide them any way around paying for our subscription.
Get a Checkout URL on the Homepage and Send the User There
Back in your frontend code, go back to the homepage in index.tsx
. We need to request a checkout URL to redirect users to.
Add this code into your homepage:
const [isCheckoutLoading, setIsCheckoutLoading] = useState(false);
const goToCheckout = async () => {
setIsCheckoutLoading(true);
const res = await fetch(`/api/stripe/create-checkout-session`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const { redirectUrl } = await res.json();
if (redirectUrl) {
window.location.assign(redirectUrl);
} else {
setIsCheckoutLoading(false);
console.log("Error creating checkout session");
}
};
Now to actually use it, we're going to rewrite what we show to signed-in users.
Find {data && <p>{JSON.stringify(data)}</p>}
in your homepage code, and change it to this:
{data && (
<div>
<p>{JSON.stringify(data)}</p>
<p>Add a payment method to start using this service!</p>
<button
onClick={() => {
if (isCheckoutLoading) return;
else goToCheckout();
}}
>
{isCheckoutLoading ? "Loading..." : "Add Payment Method"}
</button>
</div>
)}
Test it all out!
To check whether or not it works, we'll need isActive
to be included in the session. Follow these steps to implement it:
- add
isActive: boolean;
to the user type intypes.d.ts
- Update the
[...nextauth].ts
session callback to match the following: ```ts
callbacks: {
async session({ session, user }) {
session.user.id = user.id;
const dbUser = await prisma.user.findFirst({
where: {
id: user.id,
}
})
session.user.isActive = dbUser.isActive;
return session;
},
},
**Steps to Test the Full Integration:**
0. Check your Stripe CLI to ensure it's still running. If it isn't, re-run it and make sure the signing secret is up-to-date in your webhook file.
1. with the site running, go to the frontend. Press Sign In and you should see this page:
![Plain page with button that reads "sign in with github"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/itcyhponmnaaqj2iy91i.PNG)
2. Press the button and you should be taken to GitHub, where you should grant access to the OAuth app.
3. You should then be redirected to the homepage, where you'll see `isActive: false` in the user data, because we didn't add a payment method yet.
4. Press "Add Payment Method" and you should be taken to the Stripe Checkout page!
5. Confirm that the rate and billing interval is correct on the left side of the page. On the right side, enter `4242424242424242` as the credit card number, one of [Stripe's test numbers](https://stripe.com/docs/testing). Enter any Expiration month as long as it's in the future. Enter any CVC, Zip, and name, and press Subscribe.
6. After a brief loading period, you should be directed back to your homepage, with one major change: `isActive` is now true! 🎉🎊
## Debugging
If it didn't work, try these debugging tips:
1. Make sure all of your environment variables are correct.
2. In the `callback.session` function, `console.log` the user argument, the DB user found via Prisma, and the created-Stripe user. Check if any of them are missing fields.
3. Add `console.log` logging in the webhook and in the create-checkout-session endpoint until you figure out what the issue is.
4. If you need to re-test the flow, you will probably need to clear your database. You can do that with Prisma using `npx prisma migrate reset`.
## Conclusion + Next Steps
Congratulations! I hope you were able to successfully implement this complex integration. You now have a system for registering users and collecting recurring payments from them. That's basically a super-power in the web world 🦸♀️🦸♂️
There are a few more steps you would need to take before you can "go live" with this system:
1. You need to handle the Stripe events for users cancelling their subscriptions or failing to pay (credit card declined, for example). You can handle those cases in the `webhooks/stripe.ts` file, by adding more cases where we currently have the comment `// ... handle other event types`. Here, you should also handle the case when a payment fails after a subscription is created. See [this Stripe doc page](https://stripe.com/docs/billing/subscriptions/webhooks) for more details.
2. You need to host your site, so that you can connect Stripe to the hosted webhook instead of the localhost forwarded-webhook. You can add the deployed webhook URL here: [https://dashboard.stripe.com/test/webhooks](https://dashboard.stripe.com/test/webhooks).
3. For the redirection URLs to support both development and production, in the create-checkout-session endpoint, you can use a condition like `const isProd = process.env.NODE_ENV === 'production'` and then use the `isProd` variable to choose the redirection URL - either localhost or your deployed site.
4. Style the sign-in page. Right now it's pretty dark and bland :)
There are lots more customizations you can make here of course, such as including extra metadata in the Stripe objects, or connecting the payment plans to organizations instead of accounts, or adding multiple tiers of pricing and database fields to track that.
No matter where you go from here, you should now have a basic framework for the authentication and payment parts of your subscription services!
## Connect with Me
Thanks for reading! I hope this saved you some time and frustration from the process I went through to get this all set up.
Feel free to leave a comment if you have a question, or message me on [Twitter](https://twitter.com/ajones55555). I would also really appreciate if you could check out the project I'm working on which inspired this article, [Envious](https://envious.dev/) 🤩
Let me know what tutorial you'd like to see next!
Top comments (9)
Best article so far ❤️
Now it's time for me to use Mongodb instead of Postgres
Excellent article! 👏👏👏
I think there's one minor issue in the article after setting it up on my end. I think the stripeCustomerId value should be included in the next-auth session callback. Along those same lines, is there a reason why your querying the db for the database for the isActive value? I've got the following session callback (note: I'm using tiers instead of a single sub) and seems to be working fine so far 🤞 I might be missing something though. Thanks again for the awesome resource!
Thank you so much for this article!!
Some notes from 2023
Using appdir in NextJs13
In the schema, the customer id must be @unique to work with prisma.user.update({where....}) queries
in
The step3 url is broken :'/
update to the API route
For appDir, I placed this file in : ./app/api/stripe/create-checkout-session/route.ts
An example of a hook that will work with AppDir
github.dev/shadcn-ui/taxonomy/blob...
Idiot issue, in the create-checkout-session.ts file, I output the session in the response, but it returns 404 and null.
// This object will contain the user's data if the user is signed in
const session = await getSession({ req });
// Error handling
if (!session?.user) {
return res.status(401).json({
error: {
code: 'no-access',
message: 'You are not signed in.',
session: session // return null
},
});
}
But on the homepage, I output session data, it's normal.
const {data} = useSession();
console.log(data);
Can you give me any tips? Thanks!!!!
I have not tried your methods but have had a quick read-through. This is well written, and I recognise many steps from using next-auth in the past. I am looking forward to implementing this in my lastest SaaS app.
Hey Andrew, great article! Just one thing: how do you add components to conditionally render parts of your application? I mean, how do you accomplish something like this:
membershipType == Gold? :
Thank you!
Hey I've actually done just that in my application. I'm going to attempt to summarize as succintly as possible. To start, instead of a boolean value for isActive in the schema, I switched this to the subId from stripe (i.e. price_...):
Then I added a config file where I store info about the tiers and prices (which I've created on Stripe the same way as Andrew did above):
Note I've also got a yearly discount too, I like having all this in a config file so I can easily change these values in one spot and the entire app updates based on the values. Then I use the subType the user selects on the frontend to determine which subId to use in the checkout session:
Then I created a helper function to determine the pricing tier from the subId that I store in the database:
The webhook also needs to be updated:
Then finally add that to the user session in the next-auth session callback:
Hopefully this makes sense, let me know if you have any questions!
Thanks, amazing
Great article, did you explore using the pricing tables instead of creating your own UI with stripe subscription ?