# The complete guide to Stripe and Next.js

> Learn everything you need to start collecting payments for your Next.js application with Stripe Checkout

*Published: 2022-08-04*
*Canonical: https://makerkit.dev/blog/tutorials/guide-nextjs-stripe*

---

Stripe is a payment processor that allows you to accept credit card payments and manage subscriptions: it's one of the most loved services by developers because it makes it incredibly simple to **implement a payment processor in your application simply and securely** and start collecting payments from your users.

In this tutorial, we'll learn how to use Stripe and Next.js to collect payments for your SaaS. This tutorial will be **the only resource you will ever need to add Stripe to your Next.js applications**, similar to how our SaaS template for Next.js does.

Here are the topics we will cover:

1. Creating Products and Prices from the Stripe Console
2. Adding Stripe to a Next.js application
3. Stripe Domain Model
4. Creating a Stripe Checkout Session
5. Writing Webhooks for handling asynchronous events from Stripe
6. Managing the Stripe Checkout Portal after the user subscribed to a plan
7. Writing a Permissions System based on the user's Subscription

This article assumes that:

1. You have created a Next.js application
2. You signed up for Stripe (it's free)
3. You are comfortable using React and Typescript

## How does Stripe Checkout works?

Stripe Checkout's strongest advantage is that it handles a lot of server-side and client-side logic for us, making it the ideal choice for handling user payments.

But how does it work? Let's see.

1. **Plan Selection**: The flow starts from the client-side: the user picks a plan and gets redirected to the Stripe Portal, where they will be asked to enter a payment method
2. **Payment**: Once in the Stripe Portal, the user can choose to finalize the payment or go back to the client.
3. **After Payment**: When the transaction is executed, due to the asynchronous nature of payments, we have to wait and handle the results using **webhooks**, i.e. messages that Stripe sends to our servers asynchronously.
4. **Webhooks**: Using webhooks, we will listen to Stripe's events and will need to handle them; most often, this involves updating our database. For example, when the transaction succeeds, we will save the subscription in our database; when users cancel their subscription, we will delete it, and so on.
5. **Real-Time client updates**: (Optional) The client will listen for changes using a real-time database, for example, Firestore, and will display the data as it arrives.

{% img src="/assets/images/posts/stripe-checkout-flow.webp" width="1603" height="895" /%}

The image above does not consider the updates made after the user subscribes to a plan using the Stripe Checkout Portal; for example, when the plan is cancelled or updated. 

With that said, **the flow does not change**: we will receive webhooks to our API that we need to handle and update the database accordingly.

## 1) Creating products and prices from the Stripe Console

If you have already signed up for Stripe, you have access to the Stripe console from which you can add products and prices, which are fundamental for what we're going to do.

### What's the difference between a Product and a Price?

1. **Stripe Products** can be described as two different plans of your service, for example, a `Basic Plan` and a `Pro Plan`. They each have different features, and as, such we create two different products.
2. **Stripe Prices** belong to products. A *Price* defines the amount, billing period, and whether the price is a one-time payment or a subscription.

When choosing how to build your Stripe Products and Prices, you have two think:

1. How to separate your SaaS features as *products*
2. How much money to charge and the details regarding billing. For example, your yearly plan will be cheaper than your monthly plan: that's two separate *Prices* for the same *Product*

### Creating a Stripe Product with a Price

To start creating our Pricing, visit the [Stripe Products Page](https://dashboard.stripe.com/test/products?active=true).

Before continuing, make sure you activated "Test Mode" from the Stripe website:

{% img src="/assets/images/posts/stripe-test-button.webp" width="946" height="288" /%}

Then, from the Console, let's create two *Products*: a Basic Plan and Pro Plan. 

To do so, click on the "Add product" button, which will take you to the form to make a Product, as you can see below:

{% img src="/assets/images/posts/stripe-create-product-form.webp" width="1278" height="868" /%}

Below, you can also create the prices for your product:

{% img src="/assets/images/posts/stripe-create-price.webp" width="1124" height="1160" /%}


As you can see from the images above:
- we are creating a *Product* named "Basic Plan"
- The Product contains one *Price* defined as a monthly subscription of $10 USD per month

Assuming we changed "Recurring" to "One time," it would not be a recurring monthly subscription but a single payment. However, for SaaS, this is usually not what you want. Therefore, we stick with "Recurring".

Save and, optionally, repeat the process for creating a `Pro Plan`.

## 2) Adding Stripe to a Next.js application

Now that we created our *Products* and *Prices* from the Stripe Console, we have finished this article's business part.

It's now time to install Stripe in our application that we assumed you have already created with Next.js.

### Install the Stripe packages from NPM

To install the required Stripe packages, run the following command:

```
npm i --save stripe
npm i -D stripe-cli
```

### Add the Stripe keys as Environment Variables

We now have to go back to your Stripe Dashboard to retrieve some keys we have to add to our configuration so that our application can securely communicate with the Stripe servers.

1. So, the first step is to go back to the [Stripe Developers Page](https://dashboard.stripe.com/test/apikeys), where we find the Stripe keys for our Organization
2. The second step is to copy the keys and add the following environment variables to your `.env` file.

#### Adding Environment Variables to your Next.js application

If you visited the Stripe Developer page linked above, you should see something similar to the below:

{% img src="/assets/images/posts/stripe-keys.webp" width="1190" height="502" /%}

To add the Stripe keys as Environment Variables to your Next.js application, we have to create a `.env.local` file and add the following environment variables based on the keys you see on your Developer Page (see image above):

```
STRIPE_PUBLISHABLE_KEY=***
STRIPE_SECRET_KEY=***
```

Of course, you have to replace `***` with your actual keys. To make the changes live, remember to **restart your Next.js server**.

NB: do not push this file into git; it's only local to your environment.

### Add Stripe Products to your Local Configuration

To create a Plan Selector in our UI, we have to add some details from the Stripe Dashboard to our project, such as the Stripe Price IDs the user can choose.

Before continuing, go back to your [Stripe Products Page](https://dashboard.stripe.com/test/products?active=true) to retrieve the Stripe Price IDs to use in our configuration.

Click on a product, and you will find each price from which you can copy the relative IDs:

{% img src="/assets/images/posts/stripe-price-id.webp" width="1406" height="350" /%}

In [Makerkit](/ai-saas-starter), we create a global `configuration.ts` file in which we store all the application's configuration and shared properties.

In this configuration file, we also add the plans we made in Stripe. First, let's add a data structure similar to the below, and paste the correct Price IDs you copied in the previous step in the `stripePriceId` properties:

```tsx
{
  plans: [
    {
      name: 'Basic',
      description: 'Basic Plan yo!',
      price: '$10/month',
      stripePriceId: 'price_123',
    },
    {
      name: 'Pro',
      description: 'Pro Plan yo!',
      price: '$49/month',
      stripePriceId: 'price_456',
    },
  ],
}
```

### Run the Stripe CLI for executing the [Stripe Webhooks](/docs/next-fire/how-to/payments/stripe-webhooks-locally) locally

To redirect the Stripe webhooks toward our local environment, we have to use the Stripe CLI package we have installed. 

Let's add the following script to our `package.json` to start the Stripe CLI server:

```json
{
  "scripts": {
    "stripe:listen": "stripe listen --forward-to localhost:3000/api/stripe/webhook"
  }
}
```

Now it's possible to run the server with the following command:

```
npm run stripe:listen
```

When this command starts running, **it will expose a Webhook Secret Key that you will need to verify the messages coming from Stripe**. 

We will need to add this secret key as another Environment Variable to the `.env.local` file:

```
STRIPE_WEBHOOKS_SECRET=***
```

NB: this command needs to be running when using the Stripe webhooks during development.

## 3) Stripe Domain Model

In this section, we will describe the entities of a SaaS and the domain model we will use for this article.

### Who is subscribing: User or Organization?

The entities are highly dependent on your domain and application, but in a SaaS, you would typically use an **individual user** or a **group of users** (such as a team, project, or organization).

Because it's more common for a SaaS to use a group of users, we will assign a subscription to an entity named `Organization`.

In the Makerkit SaaS starter, we use the following interface for an organization:

```tsx
interface Organization {
  name: string;
  timezone?: string;
  logoURL?: string | null;
  subscription?: OrganizationSubscription;
  customerId?: string;
}
```

The `customerId` property is an ID assigned by Stripe when adding a new customer: why is it optional? It is because when the organization is created, the organization is not subscribed to any plan. So, it's totally possible not to make the `customerId` property optional, but you will have to add the organization as a customer when it's created.

Additionally, we have a `subscription` field that represents the organization's Stripe subscription, with the following shape `OrganizationSubscription`:

```tsx
enum OrganizationPlanStatus {
  AwaitingPayment = 'awaitingPayment',
  Paid = 'paid',
}

interface OrganizationSubscription {
  id: string;
  priceId: string;
  status: OrganizationPlanStatus;
  currency: string | null;
  interval: string | null;
  intervalCount: number | null;
  createdAt: UnixTimestamp;

  periodStartsAt: UnixTimestamp;
  periodEndsAt: UnixTimestamp;

  trialStartsAt: UnixTimestamp | null;
  trialEndsAt: UnixTimestamp | null;
}
```

The `OrganizationSubscription` interface represents an organization subscription object and contains the bare minimum we need to know about it. 

I am aware that the data above looks very minimal, but it's by design. In fact, should you need to access further information about your clients, you can use the Stripe API, which means **storing and managing fewer data in your database**.

In terms of data structure lifecycle, we have 3 scenarios.

#### 1) The organization never subscribed to a plan

When the organization never subscribed to a plan, the properties `customerId` and `subscription` are not defined.

#### 2) The organization subscribed to a plan

When the organization subscribes to a plan, the organization is identified in Stripe via a `customerId` property and a `subscription` object is added to the organization.

#### 3) The organization unsubscribes from a plan

When the organization unsubscribes from a plan, the `subscription` property is set to `undefined`. 

As we have already identified the organization in Stripe with the ID `customerId`, this is going to remain attached to the organization. 

Furthermore, we will use the `customerId` property to access the Stripe Portal, from which the organization will be able to visualize and download the invoices issued.

##### Organizations can only access the Stripe Portal after subscribing

Accessing the Stripe Portal **is only possible after the organization creates a subscription for the first time** because it needs a `customerId` assigned, and this is created when the organization creates a subscription.

Alternatively, you can choose to create a Stripe Customer as soon as the organization is created, so to always have a `customerId` assigned to an organization.

## 4) Creating a Stripe Checkout Session

The configuration part of this tutorial is finished. Now comes the programming part, where we will build the UI and API to connect our application to Stripe.

Our UI will be extremely basic, but here's what we're going to build:
1. a `PlanSelectionForm` component that lists the available plans, and allows the user to select a plan with a `radio` input
2. a `checkout button` which will redirect the users to the API handler in charge of creating the Stripe Checkout session and redirecting the user's to Stripe, where the payment will take place

### Creating a Component to select a Stripe plan

First, we need to create a Component to choose a Stripe Product: we call this a Plan Selector Component.

We will populate this component using the plans we have added to the configuration file.

 ```tsx {% title="PlanSelectionForm.tsx" %}
const PlanSelectionForm: React.FC<{
  onChange: (stripePriceId: string) => void;
}> = () => {
  const plans = configuration.plans;

  return (
    <div className='flex flex-col space-y-2'>
      { 
        plans.map(plan => {
          return (
            <input 
              type='radio' 
              value={plan.stripePriceId} 
              name='plan'
              onChange={e => onChange(e.target.value)}
            />
          );
        }) 
      }
    </>
  );
};
```

#### Stripe Checkout Button

The checkout button is responsible for creating a form that will call the API handler with the correct selection.

The above form needs to include four properties:
- The selected organization's ID
- The URL to redirect the user back
- The ID of the *Price* selected
- The organization's `customerID` property, if this exists

 ```tsx {% title="CheckoutRedirectButton.tsx" %}
// this is the path to the API endpoint for creating
// a checkout session. For example /api/stripe/checkout
const CHECKOUT_SESSION_API_ENDPOINT = configuration.paths.api.checkout;

const CheckoutRedirectButton: React.FCC<{
  disabled?: boolean;
  priceId: string;
  organizationId: string;
  customerId: string | undefined;
}> = ({ children, ...props }) => {
  return (
    <form
      action={CHECKOUT_SESSION_API_ENDPOINT}
      method="POST"
    >
      <CheckoutFormData
        customerId={props.customerId}
        organizationId={props.organizationId}
        priceId={props.priceId}
      />

      <button type="submit" disabled={props.disabled}>
        {children}
      </button>
    </form>
  );
};
```

When the user clicks the button, the `form` will make a POST request to the API endpoint defined at `configuration.paths.api.checkout`, for example `/api/stripe/checkout`.

The `POST` request will send along as `body` the data we defined within `CheckoutFormData` as hidden inputs, such as the below:

 ```tsx {% title="CheckoutFormData.tsx" %}
function CheckoutFormData(
  props: React.PropsWithChildren<{
    organizationId: string;
    priceId: string;
    customerId: string | undefined;
  }>
) {
  return (
    <>
      <input
        type="hidden"
        name={'organizationId'}
        defaultValue={props.organizationId}
      />

      <input 
        type="hidden" 
        name={'returnUrl'} 
        defaultValue={getReturnUrl()}
      />

      <input 
        type="hidden" 
        name={'priceId'} 
        defaultValue={props.priceId}
      />

      <input
        type="hidden"
        name={'customerId'}
        defaultValue={props.customerId}
      />
    </>
  );
}

function getReturnUrl() {
  return isBrowser()
    ? [
        window.location.origin, 
        window.location.pathname
    ].join('')
    : undefined;
}
```

Our API endpoint will need to handle the `POST` request at `/api/stripe/checkout` with the body defined as:

```tsx
{
  customerId: string | undefined;
  priceId: string;
  organizationId: string;
  returnUrl: string;
}
```

#### Form Container

Now, it's time to put everything together!

We add the `PlanSelectionForm` component to a container component `PlanSelectionContainer`, responsible for displaying either the Checkout Form or the button to redirect to the Billing page in case they're already subscribed.

The below is a very simplified version but gives you an idea of what the subscription page should look like:

 ```tsx {% title="PlanSelectionContainer.tsx" %}
const PlanSelectionContainer = () => {
  const [selectedPrice, setSelectedPrice] = useState();

  // inject your selected organization
  const organization 
    = useCurrentOrganization();

  const subscription = organization.subscription;
  const customerId = organization.customerId;

  // when the user is already subscribed
  // show the "Manage Subscription" button
  // which redirects users to the Stripe Billing Portal
  if (subscription) {
    return <BillingPortalRedirectButton customerId={customerId} />;
  }

  // otherwise, show the form to pick a plan
  // and the button to redirect them to the Stripe Checkout
  return (
    <>
      <PlanSelectionForm onChange={setSelectedPrice} />

      <CheckoutRedirectButton 
        priceId={stripePriceId}
        organizationId={organization.id}
        customerId={customerId}
      />

      {
        customerId ? 
          <BillingPortalRedirectButton customerId=customerId />
           : null
      }
    </>
  );
};
```

Borrowing an example from the Makerkit's Next.js SaaS Starter, the above will look like the below:

{% Video src="/assets/images/docs/subscribe-plan-2.mp4" /%}

### Redirecting to the Stripe Checkout Portal

When the user submits the form defined within `CheckoutRedirectButton`, as we've said, we make a `POST` request to the endpoint we are going to define in the next paragraph.

#### Creating a function to create a Checkout Session

In the snippet below, we will
- create a `Stripe` instance using the secret keys we defined in the beginning
- build a `LineItem` with the selected price ID
- define the `success` and `cancel` return URLs, which we use to redirect the users in case the user completes the checkout or cancels the session: The UI needs to handle both these situations.

```tsx
interface CreateCheckoutParams {
  returnUrl: string;
  organizationId: string;
  priceId: string;
  customerId?: string;
}

function getStripeInstance() {
  const Stripe = await import('stripe');
  const key = process.env.STRIPE_SECRET_KEY;

  return new Stripe(key, {
    apiVersion: `2020-08-27`, // update this!
  });
}

function createStripeCheckout(params: CreateCheckoutParams) {
  const clientReferenceId = params.organizationId;
  const customer = params.customerId || undefined;
  const mode: Stripe.Checkout.SessionCreateParams.Mode = 'subscription';

  const stripe = await getStripeInstance();

  const lineItem: Stripe.Checkout.SessionCreateParams.LineItem = {
    quantity: 1,
    price: params.priceId,
  };

  // NB: trimmed for simplicity but use
  // smarter methods for appending the query parameters
  const successUrl = `${returnUrl}?success=true`;
  const cancelUrl = `${returnUrl}?cancel=true`;

  return stripe.checkout.sessions.create({
    mode,
    customer,
    line_items: [lineItem],
    success_url: successUrl,
    cancel_url: cancelUrl,
    client_reference_id: clientReferenceId,
  });
}
```

Now, we will create the API endpoint which will use the `createCheckoutSession` function defined above. 

To define an API endpoint, we need to create the following folder structure in our project: `/pages/api/stripe/session/checkout.ts`.

 ```tsx {% title="/api/stripe/session/checkout.ts" %}
export default async function checkoutsSessionHandler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { headers, firebaseUser } = req;
  const { organizationId, priceId, customerId, returnUrl } =req.body;

  // NB: here you may want to check that:
  // - the user can update billing
  // - the data sent is correct
  // - the user belongs to the organization in the body
  // we omit it for simplicity, but food for thought!

  try {
    const { url } = await createStripeCheckout({
      returnUrl,
      organizationId,
      priceId,
      customerId,
    });

    // redirect user back based on the response
    res.redirect(301, url);
  } catch (e) {
    console.error(e, `Stripe Checkout error`);

    // either end request or ideally redirect users to the same URL
    // but using a query parameter such as error=true
    return res.status(500).end();
  }
}
```

If everything went well, the user went through the checkout process and returned to the page. At this point, **Stripe will be sending us some webhooks to communicate the state of the transaction**.

Remember, the checkout process was complete, but it doesn't always mean the payment was successful. In many cases, payments are asynchronous, which means **we need to wait for updates from Stripe to know if the payment was successful or unsuccessful**.

## 5) Writing Webhooks for handling asynchronous events from Stripe

### Creating an API Handler for receiving Webhooks

To receive webhooks from Stripe, we need to create an API handler that Stripe can communicate with. To do so, let's create the following file `/pages/api/stripe/webhook.ts`.

Before proceeding, let's make sure you did the following steps:

- run the command `npm run stripe:listen`
- copy the `STRIPE_WEBHOOKS_SECRET` key that the CLI displayed, and add it to the `.env.local` file
- Finally, don't forget to restart the Next.js development server

### Verifying Stripe Webhooks

After creating the `webhook.ts` file, the first thing to do is being able to verify the message transmitted by Stripe.

To do so, we have to retrieve the raw body from the message so we can compare it with the `signature` sent by Stripe. This is essential because **we need to ensure we're communicating with Stripe**. Using the shared webhooks secret key, we will verify that we received the webhook from Stripe.

To get the raw body from the request, we use the package `raw-body`, so we install it with the following command:

```tsx
npm i raw-body --save
```

First, we need to instruct the Next.js handler not to parse the `body` of the `POST` request. We can do so by exporting a variable named `config`:

```tsx
export const config = {
  api: {
    bodyParser: false,
  },
};
```

To verify a [Stripe webhook](https://makerkit.dev/docs/next-fire/how-to/payments/stripe-webhooks-locally), we can use the Stripe function `stripe.webhooks.constructEvent`, which will throw an error if the signature could not be verified:

 ```tsx {% title="api/stripe/webhook.ts" %}
import type { Stripe } from 'stripe';
import getRawBody from 'raw-body';

const STRIPE_SIGNATURE_HEADER = 'stripe-signature';

// NB: we disable body parser to receive the raw body string. The raw body
// is fundamental to verify that the request is genuine
export const config = {
  api: {
    bodyParser: false,
  },
};

export default async function checkoutsWebhooksHandler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const signature = req.headers[STRIPE_SIGNATURE_HEADER];
  const rawBody = await getRawBody(req);
  const stripe = await getStripeInstance();

  const event = stripe.webhooks.constructEvent(
    rawBody,
    signature,
    webhookSecretKey
  );
  
  // NB: if stripe.webhooks.constructEvent fails, it would throw an error

  // here we handle each event based on {event.type}

  try {
    switch (event.type) {
      case StripeWebhooks.Completed: {
        const session = event.data.object as Stripe.Checkout.Session;
        const subscriptionId = session.subscription as string;

        const subscription = await stripe.subscriptions.retrieve(
          subscriptionId
        );

        await onCheckoutCompleted(session, subscription);

        break;
      }

      case StripeWebhooks.AsyncPaymentSuccess: {
        const session = event.data.object as Stripe.Checkout.Session;
        const organizationId = session.client_reference_id as string;

        await activatePendingSubscription(organizationId);

        break;
      }

      case StripeWebhooks.SubscriptionDeleted: {
        const subscription = event.data.object as Stripe.Subscription;

        await deleteOrganizationSubscription(subscription.id);

        break;
      }

      case StripeWebhooks.SubscriptionUpdated: {
        const subscription = event.data.object as Stripe.Subscription;

        await onSubscriptionUpdated(subscription);

        break;
      }

      case StripeWebhooks.PaymentFailed: {
        const session = event.data.object as Stripe.Checkout.Session;

        // TODO: handle this properly
        onPaymentFailed(session);

        break;
      }
    }

    return respondOk(res);
  } catch (e) {
    return internalServerErrorException(res);
  }
}
```

### Which Stripe events do we need to handle?

If everything went well, the `event` object will contain the event sent from Stripe. At this point, we need to use a different strategy based on the event type being sent.

Generally speaking, we will need to handle at least the following five events:

- payment completed
- asynchronous payment success
- subscription deleted
- subscription updated
- payment failed

Therefore, we create an enum containing the webhook types sent by Stripe:

```tsx
export enum StripeWebhooks {
  AsyncPaymentSuccess = 'checkout.session.async_payment_succeeded',
  Completed = 'checkout.session.completed',
  PaymentFailed = 'checkout.session.async_payment_failed',
  SubscriptionDeleted = 'customer.subscription.deleted',
  SubscriptionUpdated = 'customer.subscription.updated',
}
```

### Creating and storing Subscriptions for your SaaS in a Firestore database

Considering the case when the user subscribes to a plan, we have two scenarios:

1. the payment succeeds and Stripe returns a webhook with a confirmation
2. the payment is being processed and needs to wait for a confirmation

In any of the cases above, we create the subscription object and store it:

```tsx
case StripeWebhooks.Completed: {
  const session = event.data.object as Stripe.Checkout.Session;
  const subscriptionId = session.subscription as string;

  const subscription = await stripe.subscriptions.retrieve(
    subscriptionId
  );

  await onCheckoutCompleted(session, subscription);

  break;
}
```

As you can see, we retrieve the `subscription` object and pass it to the `onCheckoutCompleted` handler, defined below:

```tsx
async function onCheckoutCompleted(
  session: Stripe.Checkout.Session,
  subscription: Stripe.Subscription
) {
  const organizationId = session.client_reference_id as string;
  const customerId = session.customer as string;
  
  // status can either be PAID or AWAITING_PAYMENT (if asynchronous)
  const status
     = getOrderStatus(session.payment_status);

  const subscriptionData 
    = buildOrganizationSubscription(subscription, status);
  
  // use your DB methods to 
  // set organization.subscription=subscriptionData

  return setOrganizationSubscription({
    organizationId,
    customerId,
    subscription: subscriptionData,
  });
}
```

The `buildOrganizationSubscription` function is a simple factory that adapts the Stripe object to the object in our database. You can adapt it however you like, as long as it respects your database schema or interface.

If you're interested in knowing how we map it, below is the factory function:

```tsx
function buildOrganizationSubscription(
  subscription: Stripe.Subscription,
  status: OrganizationPlanStatus = OrganizationPlanStatus.Paid
): OrganizationSubscription {
  const lineItem = subscription.items.data[0];
  const price = lineItem.price;

  return {
    id: subscription.id,
    priceId: price?.id,
    status,
    currency: lineItem.price.currency ?? null,
    interval: price?.recurring?.interval ?? null,
    intervalCount: price?.recurring?.interval_count ?? null,
    createdAt: subscription.created,
    periodStartsAt: subscription.current_period_start,
    periodEndsAt: subscription.current_period_end,
    trialStartsAt: subscription.trial_start ?? null,
    trialEndsAt: subscription.trial_end ?? null,
  };
}
```

### Handling payments being processed in Stripe

Considering the case where a payment is being processed, we wait for confirmation from Stripe. Then, Stripe will send us either `AsyncPaymentSuccess` or `PaymentFailed`.

#### 1) When the payment is successful

In this case, we simply need to flip the `status` property of the `subscription` object from `OrganizationPlanStatus.AwaitingPayment` to `OrganizationPlanStatus.Paid`:

```tsx
case StripeWebhooks.AsyncPaymentSuccess: {
  const session = event.data.object as Stripe.Checkout.Session;
  const organizationId = session.client_reference_id as string;

  await activatePendingSubscription(organizationId);

  break;
}
```

#### 2) When the payment fails

In this case, you may want to email your customers:

```tsx
case StripeWebhooks.PaymentFailed: {
  const session = event.data.object as Stripe.Checkout.Session;

  onPaymentFailed(session);

  break;
}
```

#### What to do when the payment is still in progress?

Should you let your customers use your application while the payment is being processed? This is your decision based on what is best for your application, but if you decide not to, use the `status` property to understand if they're free to use your service or not. 

The `status` property needs to be equal to `OrganizationPlanStatus.Paid` (or whatever value you choose to use).

## 6) Managing the Stripe Portal after the user subscribed to a plan

Once users subscribe to a plan, they have two choices: update their subscription or unsubscribe.

### Creating a Button to access the Stripe Billing Portal

When a customer is assigned with a `customerId` in Stripe, they can access their Billing Portal hosted by Stripe, and therefore we need to let them visit it from our application's UI.

If you remember what we wrote above, we created a component named `BillingPortalRedirectButton`: this button is there to let your users access the Stripe Billing Portal.

Similar to the `CheckoutRedirectButton`, this button will send a `POST` request to the API, which will create a Stripe Billing Portal session and redirect the user to Stripe, where the user will be able to edit or cancel their subscription, or download invoices.

 ```tsx {% title="BillingPortalRedirectButton.tsx" %}
const BILLING_PORTAL_REDIRECT_ENDPOINT 
  = configuration.paths.api.billingPortal;

const BillingPortalRedirectButton: React.FCC<{
  customerId: string;
  className?: string;
}> = ({ children, customerId, className }) => {
  return (
    <form method="POST" action={BILLING_PORTAL_REDIRECT_ENDPOINT}>
      <input type={'hidden'} name={'customerId'} value={customerId} />

      <Button color={'secondary'} className={className}>
        {children}
      </Button>
    </form>
  );
};
```

Now, we have to write the API handler that takes care of creating the Stripe session and redirecting the user to Stripe. 

To do so, let's create an API function at `/pages/api/stripe/portal.ts`, and add the following content:

 ```tsx {% title="api/stripe/portal.ts" %}
export default function billingPortalRedirectHandler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { firebaseUser, headers } = req;
  const referrerPath = getApiRefererPath(headers);
  const { customerId } = req.body;

  // NB: you may want to check that
  // the user is authorized to access the portal 
  if (!canAccessBillingPortal()) {
    return redirectToErrorPage();
  }

  try {
    const returnUrl =
      req.headers.referer 
      || req.headers.origin 
      || configuration.paths.appHome;

    const stripe = await getStripeInstance();

    const { url } = 
      await stripe.billingPortal.sessions.create({
        customer: customerId,
        return_url: returnUrl,
      });

    res.redirect(301, url);
  } catch (e) {
    console.error(e, `Stripe Billing Portal redirect error`);

    // Here, consider redirecting the user to an error page
    return res.end(500);
  }
}
```

Check out the demo below as per the Makerkit SaaS starter:

{% Video src="/assets/images/docs/unsubscribe-plan-2.mp4" /%}

## 7) Writing a Permissions System based on the user's Subscription

Now that organizations can subscribe to a plan and pay for your services, you should also make sure that organizations that aren't subscribed cannot access certain pages or perform certain actions.

To do so, you should be able to pull your organization and make sure to check its subscription object. 

For example, you can write logic that checks that to invite more members to an organization, the user must have a valid/paid subscription:

```tsx
function useOrganizationSubscription() {
  const organization = useCurrentOrganization();
  
  return organization.subscription;
}

function useIsSubscriptionValid() {
  const subscription = useOrganizationSubscription();

  const isPaid = 
    subscription?.status === OrganizationPlanStatus.Paid;

  const isPeriodValid = 
    subscription?.periodEndsAt > new Date().getTime();

    return isPaid && isPeriodValid;
}

function useOrganizationCanInviteMembers() {
  return useIsSubscriptionValid();
}
```

## Conclusion

And that's it! If you followed this guide, you should have a good idea of what it takes to integrate Stripe with your Next.js application and start collecting payments for your SaaS.

The code above is a minimal stripped-down version of the Makerkit's [SaaS Boilerplate for Next.js and Firebase](/), which uses Stripe to handle payments. 

If you're interested in having all the code above already written for you, take a look! Otherwise, I hope this guide will help you implement Stripe and get your SaaS up and running.

Before you ship: use our **[Stripe fee calculator](/pricing-calculator/stripe)** to estimate your real monthly processing costs at your expected transaction volume and international mix.

Ready to learn more? Check out the [free Makerkit course](/courses/nextjs-turbo/introduction) to master building SaaS apps. Ciao!
