How to add User Accounts and Paid Subscriptions to your Next.js Website
Tags: javascript, nextjs, prisma, stripe
Published: 2022-01-28 19:01:35

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](https://dev.to/ajones_codes/create-an-entire-backend-without-writing-a-single-sql-query-intro-to-postgres-and-prisma-for-frontend-developers-1ffl) (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:

![Architecture Diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dhxeua6amg9k97x84k5g.png)

## TLDR

1. Set up a Next.js Project
2. Add Prisma and set up a Database
3. Add Next-Auth and configure account creation
4. Create a Stripe Account
4. On account-creation, use a Next-Auth event to create a Stripe Customer and link them
5. Allow the frontend to request a Stripe Payment Link from the backend, pre-linked to their customer ID
6. Use Stripe Webhooks to activate the customer's subscription in our database when they complete a checkout
7. Test the flow

## Set Up a Project

Follow the excellent official guide [here](https://nextjs.org/docs/getting-started) 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/](https://next-auth.js.org/).

![Next Auth logo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g01v1jrhrki6k2xjxgin.png)

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/](https://stripe.com/).

![Stripe logo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u1cyfnnj3ejs7iz96du8.png)

### Setting up the Prisma Schema
First, we'll set up Prisma. If you get stuck on this part, check [Prisma's documentation](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/connect-your-database-typescript-postgres). 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](https://next-auth.js.org/adapters/prisma#create-the-prisma-schema) 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](https://next-auth.js.org/providers/github) 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?!
1. Prisma checked your `.env` for the database URL.
2. Prisma created and ran SQL commands for you, automatically, to create database tables with columns in a structure that match your schema.
3. 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:

```ts
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](https://next-auth.js.org/adapters/prisma) and the [GitHub provider](https://next-auth.js.org/providers/github).

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:

```ts
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](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:

![GitHub App creation form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ambp1tqx7659u7bmmqhs.PNG)

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:
```ts
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:

```ts
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](https://dashboard.stripe.com/test/apikeys), which should look like this:

![Stripe account API keys page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/foq8oova03y5mvl3gdh5.PNG)

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](https://next-auth.js.org/configuration/events).

In the `[...nextauth].ts` file, add the `events` field as an object with a `createUser` function like this:

```ts
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:

```ts
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:

```js
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:

```js
const {data, status} = useSession()
```

In the <main> tag, which should be empty, add the following content to decide what to render based on the user's status:
```jsx
<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:

```jsx
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!

![Data shown after a successful sign-in](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/82qn3gatqroeeldxmvwd.PNG)

![Customers page in Stripe with new row](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/08vlk14uo3gfbyy5235z.PNG)

## 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](https://stripe.com/docs/stripe-cli) to install the Stripe CLI.

Next, follow [Step 3 here](https://stripe.com/docs/webhooks/test#listen-for-events) 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](https://maxkarlsson.dev/blog/verify-stripe-webhook-signature-in-next-js-api-routes) 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:

```ts
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](https://dashboard.stripe.com/test/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:

![Create Stripe product form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gtijcujl18lejdwvtowx.PNG)

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:

```ts
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:

```tsx

  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:

```jsx
{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:
1. add `isActive: boolean;` to the user type in `types.d.ts`
2. 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!