More Tutorials

Monetize API by implementing usage-based billing with Chargebee

Features
Metered Billing

Introduction

Monetizing an API for a SaaS product might seem challenging, but it’s simple and straightforward with Chargebee`s metered billing feature. For example, if you are building an AI-powered application and want to charge your users based on their usage, then this is the tutorial you need to start with. Here we will see how to set up usage-based billing in a Next.js application. It involves mainly the following steps,

  1. Creating a metered plan in Chargebee.
  2. Creating a NextJS serverless API.
  3. Subscribing user to a recurring subscription.
  4. Creating custom API keys to authenticate APIs.
  5. Finally, reporting the API usage to Chargebee.

Inspirational Quotes - Demo Application

We will build a demo application where the API would return random inspirational quotes. This can be easily extended to your real-world applications.

Prerequisites

To try out the tutorial yourself, you'll need the following:

Creating a Metered Plan in Chargebee

Let's start configuring the product catalog by creating a metered plan.

Creating a metered plan
Creating a metered plan

Creating a NextJS serverless API

We need to create a GET API file app/api/generate/route.ts. Assume that this is the core API that we want to charge our customers. Here we are generating a random quote and responding back.

// app/api/generate/route.ts

// Ignoring the imports to avoid losing the focus.

export async function GET(req: NextRequest) {
  const headersList = headers()

  /**
   * Validating the API Key and retrieve the associated user details.
   */
  const apiKey = headersList.get('api-key') as string

  const prisma = new PrismaClient()

  const user = await prisma.user.findUnique({
    where: {
      apiKey,
    },
  })

  if (!user) {
    return NextResponse.json({ message: 'Invalid API Key' }, { status: 400 })
  }

  return NextResponse.json({
    quote: getRandomInspirationalQuote(),
  })
}

Subscribing User to a Recurring Subscription

Chargebee Checkout redirects users to a hosted webpage where they can securely enter their payment information. Here let us create a POST API file app/api/checkout/route.ts. This takes care of creating a user record in the database and initializing a checkout session.

// app/api/checkout/route.ts

export async function POST(req: NextRequest) {
  const { email } = (await req.json()) as { email: string }

  /**
   * We are using PRISMA ORM for creating a user record
   */
  const prisma = new PrismaClient()
  const user = await prisma.user.create({
    data: {
      email,
    },
  })

  await prisma.$disconnect()

  /**
   * Creating a fresh hosted page checkout session in Chargebee
   */
  const payload: HostedPage.CheckoutNewForItemsInputParam = {
    subscription: {
      id: user.id,
    },
    subscription_items: [
      {
        item_price_id: PLAN_NAME,
      },
    ],
    customer: {
      id: user.id,
      email,
    },
  }

  const result = await getChargebee().hosted_page.checkout_new_for_items(payload).request()
  // url => "https://public-demos-test.chargebee.com/pages/v3/4UnlDpwbc8tXsNXUcuLdoh48iC7lBzhQk/",

  return NextResponse.json(result.hosted_page)
}

When we call this endpoint it returns a URL where the user can subscribe to the metered plan.

Chargebee's no code page.
Chargebee's no code page.

Webhooks

When the user subscribes to a plan, Chargebee will create a customer and subscription on its end, then send the data back to our server via a webhook. For that, we need to create a POST API app/webhooks/route.ts that will update the user table and generates the API key for the user to start using the application.

// app/webhooks/route.ts

export async function POST(req: NextRequest) {
  try {
    const headersList = headers()

    /**
     * Handling Chargebee's webhook
     * https://www.chargebee.com/docs/2.0/events_and_webhooks.html#handling-webhooks
     */

    const requestIp = headersList.get('x-real-ip') || headersList.get('x-forwarded-for')

    if (process.env.NODE_ENV === 'development' || isValidRequest(requestIp!)) {
      const payload = (await req.json()) as any
      const eventType = payload.event_type
      const content = payload.content

      switch (eventType) {
        case 'subscription_created':
          const { subscription } = content
          const userId = subscription.customer_id
          const prisma = new PrismaClient()
          const apiKey = generateKey()

          await prisma.user.update({
            where: {
              id: parseInt(userId),
            },
            data: {
              apiKey,
            },
          })

          await prisma.$disconnect()

          console.log(`💰 New user ${userId} subscribed to plan ${PLAN_NAME} \n API Key: ${apiKey}`)

          break
      }
      return NextResponse.json({ message: 'Successfully processed' }, { status: 200 })
    } else {
      return NextResponse.json(
        { error: 'IP Address Not Allowed' },
        { status: 405, headers: { Allow: 'POST' } }
      )
    }
  } catch (err) {
    return NextResponse.json(
      { error: `Webhook Error`, message: (err as any).message },
      { status: 400 }
    )
  }
}

Here you can see we are listening for the subscription_created event which gets triggered as soon as the user subscribes to the plan. In the next step, we can see how to generate API key.

Creating custom API keys to authenticate requests to the API

Depending upon your code platform, you can consume the library. For Node.js, the alternative is to use crypto library.

// src/lib/utils.ts

import { customAlphabet } from 'nanoid'

const API_KEY_PREFIX = 'key_'

export const generateKey = () =>
  API_KEY_PREFIX +
  customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 32)()

Reporting API usage to Chargebee

User can get the usage reports by invoking this API app/api/usage/\[userId\]/route.ts.

// app/api/usage/[userId]/route.ts

export async function GET(req: NextRequest, { params }: { params: { userId: string } }) {
  const userId = params.userId

  const result = await getChargebee()
    .usage.list({
      subscription_id: {
        is: userId,
      },
      limit: 100,
    })
    .request()

  return NextResponse.json(result)
}

/**
 * =>
 * "list": [
		{
			"usage": {
				"id": "usage_16Bhj9TlH5tiw3AUt",
				"usage_date": 1690517677,
				"subscription_id": "1",
				"item_price_id": "metered-plan-inr-monthly",
				"quantity": "1",
				"source": "api",
				"updated_at": 1690517678,
				"created_at": 1690517678,
				"resource_version": 1690517678598,
			}
		},
    ... etc
 */

Tip: After creating a pending invoice, you can display an invoice PDF to your customers and their usage records using view usages pdf functionality.

Utilizing Chargebee's metered billing functionality enables seamless monetization of your endpoints. To explore additional supported configurations, please take a look at the documentation on metered billing here. This empowers you with the flexibility to experiment and customize these options to align precisely with your unique requirements.

Was this tutorial helpful ?
Need more help?

We're always happy to help you with any questions you might have!

support@chargebee.com