# How to Implement Seat-based Billing with Stripe Checkout

Modern SaaS businesses need a pricing model that aligns with customer usage and scales effectively. Seat-based billing is a common pricing model for SaaS businesses, allowing customers to pay a fee based on the number of seats or users they need each month.

In this article, we’ll explore how to implement a seat-based subscription model using Stripe Hosted Checkout for initial subscription creation and Stripe Customer Portal for post-purchase seat management.

[View implementation on Github](https://github.com/chideraao/seat-based-billing-tutorial).

## Prerequisites

In this section, we'll go through the requirements for the tutorial. To follow along with the rest of this guide, you will need the following:

* A [Stripe](https://dashboard.stripe.com/register) account: You can use an account in test mode for now. However, ensure you enable live mode on your account when in production.
    
* Node.js: You can download and install a NodeJS version with *Long-Term Support (LTS)* from the [NodeJS download page](https://nodejs.org/en/download/package-manager).
    
* A code editor of choice. The tutorial assumes that you already have a product with a recurring billing setup. If you don't have one, see [Create products and prices](https://docs.stripe.com/products-prices/getting-started#create-products-prices) on the Stripe documentation for more information.
    

## Step 1: Install the Stripe library

To get started, we must first install the Stripe Node Library. This library is required to create a new checkout session on our application. To install the library, run the following command in the root of your project's directory:

```sh
npm install stripe express
```

The command also installs Express to the project. *Express* is a minimalistic back-end web application framework used for building applications with Node.js.

## Step 2: Building the UI

Next, we need to build out the UI for the application. This step involves building a lightweight UI to handle the transition to Stripe's hosted checkout, as well as the `success.html` and `cancel.html` pages required by Stripe checkout.

### Create the `checkout.html`

For the `checkout.html`, we'll create a simple HTML page with a form and button that triggers Stripe's checkout session when clicked, as shown:

```html
<body>
  <section>
    <form action="/create-checkout-session" method="POST">
      <button type="submit" id="checkout-button">Checkout</button>
    </form>
  </section>
</body>
```

### Create the success and cancel pages

Next, let's create the success and cancel pages—`success.html` and `cancel.html`. Stripe requires these pages as they determine how our application handles redirects after a successful checkout or upon cancellation.

```html
<!-- success.html -->
<body>
  <section>
    <p>
      We appreciate your business! If you have any questions, please email
      <a href="mailto:orders@example.com">orders@example.com</a>.
    </p>
  </section>
</body>
```

For the cancel.html:

```html
<!-- cancel.html -->
<body>
  <section>
    <p>
      Forgot to add something to your cart? Shop around then come back to pay!
    </p>
  </section>
</body>
```

## Step 3: Setup the server

Now that we've built the UI, we can begin setting up the backend server that handles the checkout session.

First, we must import and initialize the Stripe and Express libraries that we installed earlier as shown:

```js
// server.js
const stripe = require("stripe")("YOUR_STRIPE_SECRET_KEY");
const express = require("express");
const app = express();
app.use(express.static("public"));

const YOUR_DOMAIN = "http://localhost:4242";
```

### Create the checkout session

Now, we can begin setting up the checkout session. To begin, we must define an endpoint on the server that handles the form submission request from the front end. This endpoint must match the value of the `action` attribute on the `form` element in the `checkout.html` file. For this tutorial, we will be using `/create-checkout-session` as shown:

```js
// server.js
app.post("/create-checkout-session", async (req, res) => {
  const session = await stripe.checkout.sessions.create({
    line_items: [
      {
        // Provide the Price ID of the recurring product
        price: "PRICE_ID",
        quantity: 4,
      },
    ],
    mode: "subscription",
    success_url: `${YOUR_DOMAIN}/success.html`,
    cancel_url: `${YOUR_DOMAIN}/cancel.html`,
  });

  res.redirect(303, session.url);
});
```

The `stripe.checkout.sessions.create` method creates a [Checkout Session](https://docs.stripe.com/api/checkout/sessions). A Checkout Session controls what your customer sees on the payment page, such as line items, the order amount, and acceptable payment methods.

Within the `line_items` array, provide the price ID of the product for which you want to implement seat-based billing within the `price` field and the number of initial seats within the `quantity` field. Each object in the array represents a product in your product catalog. To create seat-based billing, the price IDs of all products in the array must have a recurring price.

Next, include the values of the success and cancel URLs. These values must match the HTML pages created earlier. Since seat-based billing is a type of recurring payment, we must set the billing mode to `subscription` using the `mode` parameter.

At this point, our `server.js` should look like this:

```js
const stripe = require("stripe")("YOUR_STRIPE_SECRET_KEY");
const express = require("express");
const app = express();
app.use(express.static("public"));

const YOUR_DOMAIN = "http://localhost:4242";

app.post("/create-checkout-session", async (req, res) => {
  const session = await stripe.checkout.sessions.create({
    line_items: [
      {
        // Provide the exact Price ID (for example, pr_1234) of the product you want to sell
        price: "PRICE_ID",
        quantity: 4,
      },
    ],
    mode: "subscription",
    success_url: `${YOUR_DOMAIN}/success.html`,
    cancel_url: `${YOUR_DOMAIN}/cancel.html`,
  });

  res.redirect(303, session.url);
});

app.listen(4242, () => console.log("Running on port 4242"));
```

Now, we can test the application to ensure it works as desired. To begin:

1. Start the application server and navigate to [`http://localhost:4242/checkout.html`](http://localhost:4242/checkout.html).
    
2. Click the **Checkout** button to trigger the checkout session. This will redirect you to the Stripe Checkout page.
    
3. Fill in the required information and click the **Subscribe** button to create the subscription. This creates a new subscription for the product. You can find the new subscription on the [Subscriptions](https://dashboard.stripe.com/test/subscriptions?status=active) page on your Stripe dashboard.
    

## Upgrade and downgrade seats

Stripe allows us to manage the subscription by making updates after creating a new seat-based billing subscription.

However, since Stripe Checkout only supports creating a new subscription, we must use an alternative method for updating our seat-based billing subscription. For this tutorial, we’ll use Stripe’s [customer portal](https://docs.stripe.com/customer-management). The customer portal allows customers to self-manage their payment details, invoices, and subscriptions.

To get started:

1. Go to the [customer portal configuration](https://dashboard.stripe.com/test/settings/billing/portal) page on the Stripe dashboard.
    
2. Click the **Activate test link** button.
    
3. Go to the **Subscriptions** menu on the portal configuration section.
    
4. Enable the **Customers can switch plans** and **Customers can update the quantity of their plans** options.
    
5. Next, select the product for which you created a subscription.
    
6. Finally, click the **Save changes** button to save the latest changes.
    

![Subscriptions configuration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v866v0kkltzn1w6v1tt8.png align="left")

After completing these steps, Stripe will generate a customer portal link that your customers can use to manage their subscriptions.

To update a subscription:

1. Go to your customer portal link on a browser.
    
2. Enter your customer email to log in. The customer email you provide must be associated with an existing customer. Stripe automatically creates a new customer when a subscription is created, so you can use the customer email provided at checkout.
    
3. Click on the login link sent to the customer email provided.
    
4. Click the **Update subscription** button and then set the new number of seats to be purchased.
    
5. Click **Continue &gt; Confirm** to confirm the update.
    

![Update subscription](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/otebvgxmlgjcytw8on0i.png align="left")

Note that in test mode, Stripe will only send authentication emails if you have a test mode customer associated with the email address provided.

## Next steps

Using Stripe's Hosted Checkout and Customer Portal, you have learned how to create a new seat-based billing subscription and upgrade and downgrade seat count after purchase.

While this provides a solid foundation for per-seat billing, some notable limitations can create challenges for businesses managing seat-based subscriptions. For example, Stripe's proration invoices can be difficult to interpret, especially when customers upgrade or downgrade their seat count mid-cycle. The resulting invoice lacks clear line items that reflect standard industry practices.

Secondly, the simplicity of Stripe's approach forces businesses to build additional logic to enforce seat limits within their application. These gaps can lead to increased engineering effort and commitment for companies looking for a more robust and scalable solution.
