# Set Up Usage-Based Billing with Prepaid Credits

With Freemius, you can easily sell AI credits or set up usage-based billing for other consumptive features in your SaaS product.

You can sell different subscription plans that provide a specific number of credits per month.

However, you may also want to sell credit top-ups through one-time purchases. This is where Freemius usage-based billing with prepaid credits comes in.

If you're building a SaaS product with a similar setup using Next.js, you can watch our [video tutorial](https://www.youtube.com/playlist?list=PLFo6V0kfAw9PteLMNjLbV7vMPL0XZJg29) for a complete walkthrough. The code is also available in our [GitHub repository](https://github.com/Freemius/ai-chat-nextjs-example).

## Configuring Your Plans[​](#configuring-your-plans "Direct link to Configuring Your Plans")

We will configure the plans using two strategies:

1. A set of plans with subscription-only pricing. These plans provide continued access to your SaaS product's features and may optionally include recurring monthly credits (e.g., 100 credits per month).
2. A dedicated plan for credit top-ups. We will configure this plan with multi-unit pricing so that users can purchase different quantities of credits.

If you do not wish to have subscription plans, you can skip the first step and just set up the credit top-up plan.

### Subscription-Only Plans[​](#subscription-only-plans "Direct link to Subscription-Only Plans")

![Freemius plans with monthly and annual subscription pricing and no one-off pricing](/help/assets/ideal-img/freemius-plans-subscriptions-only.4e6d3e9.480.png)

For each subscription plan, set only the annual and/or monthly price under **Billing Cycles**.

![Billing Cycles settings with monthly and annual prices and an empty one-off price](/help/assets/ideal-img/freemius-pricing-subscription-only.12d971f.480.png)

To avoid confusion, ensure that these plans do not have a **One-off Price**.

### Credit Top-Up Plan[​](#credit-top-up-plan "Direct link to Credit Top-Up Plan")

Create a **new** plan named **Credit Topup**. Under the plan's pricing, click **Add Bulk** to set prices for bulk credits.

In the example below, we have set the price for 1,000 credits as $8, 5,000 credits as $30, and 10,000 credits as $50.

![Credit Topup plan with multi-unit pricing and one-off prices for 1,000, 5,000, and 10,000 credits](/help/assets/ideal-img/freemius-plan-credit.ee176b2.480.png)

After you set multi-unit pricing, the plan UI changes from **Billing Cycles** to unit-based pricing. The plan is now a **multi-unit** plan, allowing users to purchase different quantities of credits. For each pricing option, set only the **One-off Price** and leave the monthly and annual prices empty.

Configure Unit Label

By default, the unit label is set to **Unit**. You can change it to **Credit** or another label that makes sense for your SaaS product by following our [guide](https://freemius.com/help/help/documentation/saas/customize-license-unit-label/.md).

## Modify the Entitlement Logic[​](#modify-the-entitlement-logic "Direct link to Modify the Entitlement Logic")

In our basic [SaaS Integration](https://freemius.com/help/help/documentation/saas/saas-integration/.md) guide, we explained how to set up the [purchase data processing logic](https://freemius.com/help/help/documentation/saas/saas-integration/.md#saving-purchase-information) for active subscriptions.

We will use the same database table and logic to maintain the user's credit balance.

Assume that the user's current balance is stored in a `credits` column in the `users` table. After the user makes a purchase, you can update their credit balance from the same function:

* JS SDK
* PHP

src/user-entitlement.ts

```
import { type PurchaseInfo } from '@freemius/sdk';

import { db } from './db'; // our database instance



// A map of pricing id with the number of credits it represents

const pricingToResourceMap: Record<string, keyof typeof resourceRecord> = {

  // The pricing ids are generated by Freemius and you can either put them here or in your .env file.

  [process.env.FREEMIUS_PRICING_ID_TOPUP_1000!]: 1000,

  [process.env.FREEMIUS_PRICING_ID_TOPUP_5000!]: 5000,

  [process.env.FREEMIUS_PRICING_ID_TOPUP_10000!]: 10000,

};



// A new function add credits



export async function addCredits(

  userId: string,

  credits: number

): Promise<void> {

  await db.user.update({

    where: { id: userId },

    data: { credit: { increment: credits } },

  });

}



export async function processPurchaseInfo(purchase: PurchaseInfo) {

  const user = await findUserByEmail(purchase.email);



  // We exit if the user hasn't registered to our application yet.

  // Alternatively, you can register the user automatically here if desired.

  if (!user) {

    return;

  }



  // Convert the insertion into a transaction to find out if the entitlement already exists

  await db.transaction(async (tx) => {

    const isExisting = await tx.userFsEntitlement.findUnique({

      where: {

        fsLicenseId: purchase.licenseId,

      },

    });



    if (isExisting) {

      // Just update the existing entitlement record with the new purchase info

      await tx.userFsEntitlement.update({

        where: {

          fsLicenseId: purchase.licenseId,

        },

        data: purchase.toEntitlementRecord(),

      });

    } else {

      // New purchase, so add it and also update the user's credit balance based on the pricing id

      const creditsToAdd = pricingToResourceMap[purchase.pricingId];

      await addCredits(user.id, creditsToAdd);

    }

  });

}
```

lib/user-entitlement.php

```
function addCredits(string $user_id, int $credits): void {

  db()->user

    ->where('id', $user_id)

    ->increment('credits', $credits);

}



function processPurchaseInfo(string $license_id): void {

  $pricing_to_credit_map = [

    getenv('FREEMIUS_PRICING_ID_TOPUP_1000') => 1000,

    getenv('FREEMIUS_PRICING_ID_TOPUP_5000') => 5000,

    getenv('FREEMIUS_PRICING_ID_TOPUP_10000') => 10000,

  ];



  // @see https://freemius.com/help/api/licenses/retrieve/

  $license = getLicenseFromFreemiusApi($license_id);

  // @see https://freemius.com/help/api/users/retrieve/

  $user = getUserFromFreemiusApi($license['user_id']);

  // @see https://freemius.com/help/api/licenses/retrieve-latest-subscription/

  $subscription = getSubscriptionFromFreemiusApi($license_id);



  $local_user = findUserByEmail($user['email']);



  // We exit if the user hasn't registered to our application yet.

  // Alternatively, you can register the user automatically here if desired.

  if (!$local_user) {

    return;

  }



  $credits_to_add = $pricing_to_credit_map[$license['pricing_id']] ?? null;



  if (is_null($credits_to_add)) {

    throw new UnexpectedValueException('Unknown Freemius pricing ID.');

  }



  db()->transaction(function () use (

    $credits_to_add,

    $license,

    $local_user,

    $subscription,

    $user

  ): void {

    $existing_entitlement = db()->user_fs_entitlement

      ->where('fs_license_id', $license['id'])

      ->first();



    // Insert or update the purchase in our local database.

    db()->user_fs_entitlement->updateOrInsert(

      ['fs_license_id' => $license['id']],

      [

        'user_id'       => $local_user->id,

        'fs_plan_id'    => $license['plan_id'],

        'fs_pricing_id' => $license['pricing_id'],

        'fs_user_id'    => $user['id'],

        'type'          => is_null($subscription) ? 'lifetime' : 'subscription',

        'expiration'    => $license['expiration'],

        'is_canceled'   => $license['is_canceled'],

      ]

    );



    // Add credits only when processing the purchase for the first time.

    if (!$existing_entitlement) {

      addCredits($local_user->id, $credits_to_add);

    }

  });

}
```

The code above performs the following steps:

1. Checks the `user_fs_entitlement` table to see if the purchase already exists.
2. If it exists, it updates the existing record with the new purchase info.
3. If it does not exist, the purchase has not been processed before, so it adds the record and updates the user's credit balance based on the purchase's pricing ID.

The pricing ID is mapped to the number of credits it represents in the `pricingToResourceMap` object. You can either hardcode the pricing IDs here or store them in your `.env` file.

## Consuming Credits[​](#consuming-credits "Direct link to Consuming Credits")

Credit consumption must be handled in your application logic. For example, if you have a metered feature that consumes credits, you can check the user's credit balance before allowing access to it. For this purpose, we recommend creating utility functions.

* JS SDK
* PHP

```
export async function getCredits(userId: string): Promise<number> {

  const user = await db.user.findUniqueOrThrow({ where: { id: userId } });

  return user.credit;

}



export async function hasCredits(

  userId: string,

  credits: number = 1

): Promise<boolean> {

  const creditsAvailable = await getCredits(userId);



  return creditsAvailable >= credits;

}



export async function deductCredits(

  userId: string,

  credits: number

): Promise<User | null> {

  const updatedUser = await db.user.update({

    where: { id: userId },

    data: { credit: { decrement: credits } },

  });



  return updatedUser;

}
```

lib/user-entitlement.php

```
function getCredits(string $user_id): int {

  $user = db()->user

    ->where('id', $user_id)

    ->first();



  if (!$user) {

    throw new UnexpectedValueException('User not found.');

  }



  return (int) $user->credits;

}



function hasCredits(string $user_id, int $credits = 1): bool {

  $credits_available = getCredits($user_id);



  return $credits_available >= $credits;

}



function deductCredits(string $user_id, int $credits): ?User {

  return db()->user

    ->where('id', $user_id)

    ->decrement('credits', $credits);

}
```

You can call these functions in your API routes or backend logic to check whether the user has enough credits before allowing access to a metered feature, then deduct the credits after the feature is used.

## UI for Purchasing Credits[​](#ui-for-purchasing-credits "Direct link to UI for Purchasing Credits")

You can use our [React Starter Kit](https://freemius.com/help/help/documentation/saas-sdk/react-starter/.md) to display the credit top-up plan in your SaaS application. The component will automatically determine which plan is a multi-unit plan and display the pricing accordingly.

![React Starter Kit credit top-up component with purchase options for 1,000, 5,000, and 10,000 credits](/help/assets/ideal-img/freemius-starter-kit-topup-component.3340d8e.480.png)

Follow these steps:

1. Set up the Checkout endpoint using our [JavaScript SDK](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/starter-kit-api-endpoints/.md#setting-up-the-checkout-endpoint).
2. Use the `Topup` component from the [React Starter Kit](https://freemius.com/help/help/documentation/saas-sdk/react-starter/components/.md#one-off-purchase-table) to display the credit top-up plan in your SaaS application.

Alternatively, you can use our [Checkout Integration](https://freemius.com/help/help/documentation/checkout/integration/.md) guide to implement a custom checkout flow for purchasing credits.
