Documentation Index Fetch the complete documentation index at: https://mintlify.com/sammaji/budgetbee/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The @budgetbee/billing package provides integration with Polar for subscription management, payment processing, and billing operations.
Installation
pnpm add @budgetbee/billing --filter your-package
Or in your package.json:
{
"dependencies" : {
"@budgetbee/billing" : "workspace:*"
}
}
{
"name" : "@budgetbee/billing" ,
"version" : "1.0.0" ,
"description" : "Billing utilities + polar client." ,
"main" : "index.ts"
}
Exports
import { polar } from "@budgetbee/billing" ;
import {
isPro ,
isTeams ,
isProOrTeams ,
isProOrHigher ,
isTeamsOrHigher
} from "@budgetbee/billing" ;
Polar Client
The package exports a pre-configured Polar SDK client:
import { polar } from "@budgetbee/billing" ;
// Client is automatically configured with access token
const products = await polar . products . list ();
const subscriptions = await polar . subscriptions . list ();
Configuration
The Polar client is initialized with your access token from environment variables:
export const polar = new Polar ({
accessToken: process . env . POLAR_ACCESS_TOKEN ,
});
Set your Polar access token in .env file:POLAR_ACCESS_TOKEN = your_polar_access_token
Subscription Tiers
BudgetBee offers two subscription tiers:
Pro Individual users with advanced features
Teams Organizations with collaboration features
Environment Variables
Configure your product IDs in .env:
# Pro tier (monthly)
POLAR_PRODUCT_PRO = prod_xxxxxxxxxxxx
# Pro tier (yearly)
POLAR_PRODUCT_PRO_YEARLY = prod_yyyyyyyyyyyy
# Teams tier (monthly)
POLAR_PRODUCT_TEAMS = prod_zzzzzzzzzzzz
# Teams tier (yearly)
POLAR_PRODUCT_TEAMS_YEARLY = prod_wwwwwwwwwwww
Tier Checking Functions
The package provides helper functions to check subscription tiers:
isPro()
Check if a price ID is for the Pro tier:
import { isPro } from "@budgetbee/billing" ;
const userPriceId = user . subscription ?. priceId ;
if ( isPro ( userPriceId )) {
// User has Pro subscription
console . log ( 'Pro features enabled' );
}
isTeams()
Check if a price ID is for the Teams tier:
import { isTeams } from "@budgetbee/billing" ;
if ( isTeams ( userPriceId )) {
// User has Teams subscription
console . log ( 'Teams features enabled' );
}
isProOrTeams()
Check if a price ID is for either Pro or Teams:
import { isProOrTeams } from "@budgetbee/billing" ;
if ( isProOrTeams ( userPriceId )) {
// User has any paid subscription
console . log ( 'Premium features enabled' );
}
isProOrHigher()
Check if a price ID is Pro tier or higher:
import { isProOrHigher } from "@budgetbee/billing" ;
if ( isProOrHigher ( userPriceId )) {
// User has Pro or Teams subscription
console . log ( 'Advanced features available' );
}
isTeamsOrHigher()
Check if a price ID is Teams tier or higher:
import { isTeamsOrHigher } from "@budgetbee/billing" ;
if ( isTeamsOrHigher ( userPriceId )) {
// User has Teams subscription (highest tier)
console . log ( 'Organization features enabled' );
}
Usage Examples
Feature Gating
import { isProOrHigher } from "@budgetbee/billing" ;
import { auth } from "@budgetbee/core/auth" ;
export async function GET ( request : Request ) {
const session = await auth . api . getSession ({
headers: request . headers
});
if ( ! session ?. user ) {
return new Response ( 'Unauthorized' , { status: 401 });
}
// Check if user has access to premium features
const priceId = session . user . subscription ?. priceId ;
if ( ! isProOrHigher ( priceId )) {
return new Response (
JSON . stringify ({ error: 'Upgrade to Pro to access this feature' }),
{ status: 403 }
);
}
// User has access, proceed with request
return new Response (
JSON . stringify ({ data: 'Premium data' })
);
}
Conditional UI Rendering
import { isTeamsOrHigher } from "@budgetbee/billing" ;
import { Button } from "@budgetbee/ui/core/button" ;
import { Badge } from "@budgetbee/ui/core/badge" ;
function FeatureCard ({ user }) {
const hasTeams = isTeamsOrHigher ( user . subscription ?. priceId );
return (
< div className = "border rounded-lg p-4" >
< div className = "flex items-center gap-2" >
< h3 > Organization Management </ h3 >
{ ! hasTeams && < Badge > Teams Only </ Badge > }
</ div >
{ hasTeams ? (
< Button > Manage Organization </ Button >
) : (
< Button variant = "outline" > Upgrade to Teams </ Button >
) }
</ div >
);
}
Subscription Status Check
import { polar , isProOrHigher } from "@budgetbee/billing" ;
async function checkSubscriptionStatus ( userId : string ) {
// Get user's subscriptions from Polar
const subscriptions = await polar . subscriptions . list ({
customerId: userId
});
const activeSubscription = subscriptions . items . find (
sub => sub . status === 'active'
);
if ( ! activeSubscription ) {
return { hasAccess: false , tier: 'free' };
}
const priceId = activeSubscription . priceId ;
return {
hasAccess: isProOrHigher ( priceId ),
tier: isTeamsOrHigher ( priceId ) ? 'teams' : 'pro'
};
}
Integration with Better Auth
BudgetBee uses Polar’s Better Auth plugin for seamless integration:
import { polar as polarPlugin } from "@polar-sh/better-auth" ;
import { betterAuth } from "better-auth" ;
import { polar } from "@budgetbee/billing" ;
export const auth = betterAuth ({
plugins: [
polarPlugin ({
client: polar ,
// Polar plugin configuration
})
]
});
Available Better Auth Plugins
The core package uses these Polar Better Auth plugins:
Checkout
Portal
Usage
Webhooks
import { checkout } from "@polar-sh/better-auth" ;
// Handles checkout flow
// Creates checkout sessions
// Redirects users to payment page
API Operations
List Products
import { polar } from "@budgetbee/billing" ;
const products = await polar . products . list ();
products . items . forEach ( product => {
console . log ( product . name , product . prices );
});
Get Subscription
const subscription = await polar . subscriptions . get ( subscriptionId );
console . log ( subscription . status );
console . log ( subscription . currentPeriodEnd );
List Customer Subscriptions
const subscriptions = await polar . subscriptions . list ({
customerId: userId ,
status: 'active'
});
Create Checkout Session
const checkout = await polar . checkouts . create ({
priceId: process . env . POLAR_PRODUCT_PRO ,
customerId: userId ,
successUrl: 'https://app.budgetbee.com/success' ,
cancelUrl: 'https://app.budgetbee.com/pricing'
});
// Redirect user to checkout.url
Webhook Handling
Handle Polar webhooks to sync subscription status:
import { polar } from "@budgetbee/billing" ;
export async function POST ( request : Request ) {
const signature = request . headers . get ( 'polar-signature' );
const body = await request . text ();
// Verify webhook signature
const event = polar . webhooks . constructEvent (
body ,
signature ! ,
process . env . POLAR_WEBHOOK_SECRET !
);
switch ( event . type ) {
case 'subscription.created' :
// Handle new subscription
await handleSubscriptionCreated ( event . data );
break ;
case 'subscription.updated' :
// Handle subscription update
await handleSubscriptionUpdated ( event . data );
break ;
case 'subscription.canceled' :
// Handle cancellation
await handleSubscriptionCanceled ( event . data );
break ;
}
return new Response ( 'OK' );
}
Dependencies
The package depends on:
{
"dependencies" : {
"@polar-sh/sdk" : "0.42.2"
}
}
Environment Variables
Required environment variables:
# Polar API
POLAR_ACCESS_TOKEN = polar_xxxxxxxxxxxx
# Product IDs
POLAR_PRODUCT_PRO = prod_xxxxxxxxxxxx
POLAR_PRODUCT_PRO_YEARLY = prod_yyyyyyyyyyyy
POLAR_PRODUCT_TEAMS = prod_zzzzzzzzzzzz
POLAR_PRODUCT_TEAMS_YEARLY = prod_wwwwwwwwwwww
# Webhooks
POLAR_WEBHOOK_SECRET = whsec_xxxxxxxxxxxx
Best Practices
Always check nulls Subscription price IDs can be null or undefined - all helper functions handle this safely
Use tier helpers Use isProOrHigher() instead of checking individual tiers for better maintainability
Cache subscription data Cache subscription status to avoid repeated API calls
Handle webhooks Always implement webhook handlers to keep subscription status in sync
Common Patterns
Middleware for Route Protection
import { isProOrHigher } from "@budgetbee/billing" ;
import { auth } from "@budgetbee/core/auth" ;
import { NextResponse } from "next/server" ;
export async function middleware ( request : Request ) {
const session = await auth . api . getSession ({
headers: request . headers
});
if ( ! session ?. user ) {
return NextResponse . redirect ( '/login' );
}
const priceId = session . user . subscription ?. priceId ;
if ( ! isProOrHigher ( priceId )) {
return NextResponse . redirect ( '/upgrade' );
}
return NextResponse . next ();
}
export const config = {
matcher: '/dashboard/premium/:path*'
};
Server Actions
'use server' ;
import { isTeamsOrHigher } from "@budgetbee/billing" ;
import { auth } from "@budgetbee/core/auth" ;
export async function createOrganization ( data : FormData ) {
const session = await auth . api . getSession ();
if ( ! session ?. user ) {
throw new Error ( 'Unauthorized' );
}
const priceId = session . user . subscription ?. priceId ;
if ( ! isTeamsOrHigher ( priceId )) {
throw new Error ( 'Teams subscription required' );
}
// Create organization...
}
Troubleshooting
Subscription not detected
Check that:
POLAR_ACCESS_TOKEN is set correctly
Product IDs match your Polar dashboard
User’s subscription is active in Polar
Webhook not receiving events
Verify:
Webhook URL is publicly accessible
POLAR_WEBHOOK_SECRET matches Polar dashboard
Webhook is enabled in Polar settings
Price ID comparison failing
Ensure you’re using the helper functions which handle:
Both monthly and yearly variants
Null and undefined values
Case sensitivity
Next Steps
Core Package Learn about auth integration with Polar
Project Structure Understand how billing fits in the monorepo