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:
- Open the
/settings.js
file. - Locate the
PLAN_DETAILS
array. - Navigate to the
payment
object of the plan you want to configure. - Update the relevant properties.
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 totrue
:- 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 tofalse
:- 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.
- If
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 totrue
:- 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 tofalse
:- 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.
- If
Consuming Credits
To consume credits (e.g., for AI apps or pay-per-use services):
There are two ways to consume credits:
- 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 namecustomers
. If you are using a custom table name, the function name will bedecrement_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:
- Make sure the
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}.`);
}
- 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
- Granting Access: Refer to the Protected Pages Guide.
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.