Docs
Payments
Credits and Rollover

AI-Ready Access Control

Overview

In this guide, we will learn how to give access to your product when someone buys a plan from you using Stripe or LemonSqueezy.

StartupBolt uses a credit-based system to manage access to your product. When a user buys a plan, they receive a certain number of credits. By default, the number of credits is 100, but you can customize this value to suit your needs.

This credit-based system is versatile:

  • For AI apps or services, usage can be tied to credits, offering flexibility and control.
  • For regular SaaS apps, you can use the system as a boolean-like mechanism:
    • 100 credits = access granted.
    • 0 credits = access denied.

This makes the system suitable for both normal SaaS apps and AI/credit-based apps.

Now, let's dive into the configuration.


PLAN_DETAILS Configuration

The settings.js file, located at /settings.js, is the central configuration file for your StartupBolt application. This file contains various settings to customize and manage your app's core parameters.

Steps to Configure PLAN_DETAILS:

  1. Open the /settings.js file.
  2. Locate the PLAN_DETAILS array.
  3. Navigate to the payment object of the plan you want to configure.
  4. Update the relevant properties.
Show payment object in settings.js

Credits Property

  • The credits property determines the number of credits a user receives when they buy the plan.
  • By default, it is set to 100, but you can change this value to fit your business requirements.
  • Access is granted if credits are greater than 0.

For more details on how to manage user access, refer to the Protected Pages Guide.


Credit Behavior for One-Off Payment Plans

  • When a user purchases a one-off plan:
    • The credits are added to their account, granting them access to the product.
    • For regular SaaS apps, treat 100 credits as "access granted" and 0 credits as "access denied."
    • Lifetime Access: Credits for one-off payments are available indefinitely unless consumed.
    • Rollover Behavior:
      • If rollover is set to true:
        • Credits are cumulative. If a user pays again, the new credits are added to their existing balance.
        • Example: A user has 50 unused credits and purchases a plan with 100 credits. The new total is 150 credits.
      • If rollover is set to false:
        • Credits reset to the plan's specified value. Unused credits do not carry over.
        • Example: A user with 50 unused credits purchases a plan with 100 credits. Their balance resets to 100 credits.

Credit Behavior for Subscription Plans

  • When a user subscribes to a plan:
    • Credits are added to their account, granting them access to the product.
    • For regular SaaS apps, treat 100 credits as "access granted" and 0 credits as "access denied."
    • Subscription Duration: Access is active during the subscription period. Once the subscription ends, access depends on the rollover property:
      • If rollover is set to true:
        • Unused credits are retained even after the subscription ends, meaning the user can still access the product until their credits are consumed.
        • Example: A user has 20 unused credits at the end of their subscription. These credits remain available for use, providing continued access. On renewal, they receive an additional 100 credits, resulting in 120 credits.
      • If rollover is set to false:
        • Credits reset to the plan's fixed value at the start of each billing cycle.
        • Access to the product is lost unless the user renews their subscription.
        • Example: A user has 20 unused credits. On renewal, their balance resets to 100 credits, regardless of the previous balance.

Consuming Credits

To consume credits (e.g., for AI apps or pay-per-use services):

There are two ways to consume credits:

  1. Using RPC Functions:
    • Make sure the decrement_credits_customers RPC function is created in your Supabase database. You can refer to the Supabase Configuration Guide for more details.
    • The RPC function name is decrement_credits_customers if you are using the default table name customers. If you are using a custom table name, the function name will be decrement_credits_<your_table_name>. Please refer to the Supabase Configuration Guide for more details.
    • Use the decrement_credits_customers RPC function to decrement the credits (Typically in a route handler or a server action). Example:
import { createClient } from '@/utils/supabase/server';
 
async function decrementCredits(USAGE_CREDITS) {
    // Create Supabase server client
    const supabase = createClient();
 
    // Get the authenticated user
    const { data, error } = await supabase.auth.getUser();
    if (error || !data?.user) {
        throw new Error("Unable to retrieve user. Please ensure the user is authenticated.");
    }
    const user = data.user;
 
    // Validate USAGE_CREDITS
    if (typeof USAGE_CREDITS !== "number" || USAGE_CREDITS <= 0) {
        throw new Error("Invalid value for USAGE_CREDITS.");
    }
 
    // Decrement credits using the RPC function
    const { error: rpcError } = await supabase.rpc('decrement_credits_customers', {
        user_id: user.id,
        decrement_value: USAGE_CREDITS,
    });
 
    if (rpcError) {
        throw new Error(`Failed to decrement credits: ${rpcError.message}`);
    }
 
    console.log(`Successfully decremented ${USAGE_CREDITS} credits for user ${user.id}.`);
}
  1. Using Supabase Database:
    • You can directly update the user's credits in the Supabase database.
    • This approach is simpler but less efficient for frequent updates.
    • Keep in mind that without the atomic behavior of the RPC function, you may encounter race conditions unless you carefully structure your operations. So we recommend using the RPC function for this.
    • Anyways, here's how you can do it:
import { createClient } from '@/utils/supabase/server';
 
async function decrementCredits(USAGE_CREDITS) {
    // Create Supabase server client
    const supabase = createClient();
 
    // Get the authenticated user
    const { data, error } = await supabase.auth.getUser();
    if (error || !data?.user) {
        throw new Error("Unable to retrieve user. Please ensure the user is authenticated.");
    }
    const user = data.user;
 
    // Validate USAGE_CREDITS
    if (typeof USAGE_CREDITS !== "number" || USAGE_CREDITS <= 0) {
        throw new Error("Invalid value for USAGE_CREDITS.");
    }
 
    // Fetch the user's current credits
    const { data: userData, error: fetchError } = await supabase
        .from("customers")
        .select("credits")
        .eq("id", user.id)
        .single();
 
    if (fetchError || !userData) {
        throw new Error(`Failed to retrieve user data: ${fetchError?.message || "Unknown error"}`);
    }
 
    const currentCredits = userData.credits;
 
    // Check if the user has sufficient credits
    if (currentCredits < USAGE_CREDITS) {
        throw new Error("Insufficient credits.");
    }
 
    // Decrement the credits
    const { error: updateError } = await supabase
        .from("customers")
        .update({ credits: currentCredits - USAGE_CREDITS })
        .eq("id", user.id);
 
    if (updateError) {
        throw new Error(`Failed to decrement credits: ${updateError.message}`);
    }
 
    console.log(`Successfully decremented ${USAGE_CREDITS} credits for user ${user.id}.`);
}

That's it! You have successfully configured and implemented credit-based access control in your StartupBolt application.

Next Steps

This guide equips you with the knowledge to configure and manage credit-based access control in StartupBolt, whether you're building a regular SaaS app or a credit-based/AI-driven product.