BillingSDK Integration
Reference: docs.dodopayments.com/developer-resources/billingsdk | billingsdk.com
BillingSDK provides open-source, customizable React components for billing interfaces - pricing tables, subscription management, usage meters, and more.
Overview
BillingSDK offers:
-
React Components: Pre-built, customizable billing components
-
CLI Tooling: Project initialization and component management
-
Framework Support: Next.js, Express.js, Hono, Fastify, React
-
Payment Provider: Full integration with Dodo Payments
Quick Start Options
Option 1: New Project (Recommended)
Complete project setup with framework configuration and API routes:
npx @billingsdk/cli init
The CLI will:
-
Configure your framework (Next.js App Router)
-
Set up Dodo Payments integration
-
Generate API routes for checkout, customers, webhooks
-
Install dependencies
-
Create configuration files
Option 2: Add to Existing Project
Add individual components using the CLI:
npx @billingsdk/cli add pricing-table-one npx @billingsdk/cli add subscription-management npx @billingsdk/cli add usage-meter-circle
Option 3: Manual via shadcn/ui
Install directly using shadcn registry:
npx shadcn@latest add @billingsdk/pricing-table-one
CLI Reference
Initialize Project
npx @billingsdk/cli init
Interactive setup prompts:
-
Select framework (Next.js, Express.js, Hono, Fastify, React)
-
Select payment provider (Dodo Payments)
-
Configure project settings
Add Components
npx @billingsdk/cli add <component-name>
Available components:
-
pricing-table-one
-
Simple pricing table
-
pricing-table-two
-
Feature-rich pricing table
-
subscription-management
-
Manage active subscriptions
-
usage-meter-circle
-
Circular usage visualization
-
More components available...
What happens when adding:
-
Downloads component from registry
-
Installs files to components/billingsdk/
-
Updates project configuration
-
Installs additional dependencies
Components
Pricing Table One
Simple, clean pricing table for displaying plans.
Installation:
npx @billingsdk/cli add pricing-table-one
or
npx shadcn@latest add @billingsdk/pricing-table-one
Usage:
import { PricingTableOne } from "@/components/billingsdk/pricing-table-one";
const plans = [ { id: 'prod_free', name: 'Free', price: 0, interval: 'month', features: ['5 projects', 'Basic support'], }, { id: 'prod_pro', name: 'Pro', price: 29, interval: 'month', features: ['Unlimited projects', 'Priority support', 'API access'], popular: true, }, { id: 'prod_enterprise', name: 'Enterprise', price: 99, interval: 'month', features: ['Everything in Pro', 'Custom integrations', 'Dedicated support'], }, ];
export function PricingPage() { const handleSelectPlan = async (planId: string) => { // Create checkout session const response = await fetch('/api/checkout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ productId: planId }), });
const { checkoutUrl } = await response.json();
window.location.href = checkoutUrl;
};
return ( <PricingTableOne plans={plans} onSelectPlan={handleSelectPlan} /> ); }
Pricing Table Two
Feature-comparison pricing table with toggle for monthly/yearly.
Installation:
npx @billingsdk/cli add pricing-table-two
Usage:
import { PricingTableTwo } from "@/components/billingsdk/pricing-table-two";
const plans = [ { id: 'prod_starter_monthly', yearlyId: 'prod_starter_yearly', name: 'Starter', monthlyPrice: 19, yearlyPrice: 190, features: [ { name: 'Projects', value: '10' }, { name: 'Storage', value: '5 GB' }, { name: 'Support', value: 'Email' }, ], }, { id: 'prod_pro_monthly', yearlyId: 'prod_pro_yearly', name: 'Pro', monthlyPrice: 49, yearlyPrice: 490, popular: true, features: [ { name: 'Projects', value: 'Unlimited' }, { name: 'Storage', value: '50 GB' }, { name: 'Support', value: 'Priority' }, ], }, ];
export function PricingPage() {
return (
<PricingTableTwo
plans={plans}
onSelectPlan={(planId, billingInterval) => {
console.log(Selected: ${planId}, Interval: ${billingInterval});
}}
/>
);
}
Subscription Management
Allow users to view and manage their subscription.
Installation:
npx @billingsdk/cli add subscription-management
Usage:
import { SubscriptionManagement } from "@/components/billingsdk/subscription-management";
export function AccountPage() { const subscription = { plan: 'Pro', status: 'active', currentPeriodEnd: '2025-02-21', amount: 49, interval: 'month', };
return ( <SubscriptionManagement subscription={subscription} onManageBilling={async () => { // Open customer portal const response = await fetch('/api/portal', { method: 'POST' }); const { url } = await response.json(); window.location.href = url; }} onCancelSubscription={async () => { if (confirm('Are you sure you want to cancel?')) { await fetch('/api/subscription/cancel', { method: 'POST' }); } }} /> ); }
Usage Meter
Display usage-based billing metrics.
Installation:
npx @billingsdk/cli add usage-meter-circle
Usage:
import { UsageMeterCircle } from "@/components/billingsdk/usage-meter-circle";
export function UsageDashboard() { return ( <div className="grid grid-cols-3 gap-4"> <UsageMeterCircle label="API Calls" current={8500} limit={10000} unit="calls" /> <UsageMeterCircle label="Storage" current={3.2} limit={5} unit="GB" /> <UsageMeterCircle label="Bandwidth" current={45} limit={100} unit="GB" /> </div> ); }
Next.js Integration
Project Structure (after init )
your-project/ ├── app/ │ ├── api/ │ │ ├── checkout/ │ │ │ └── route.ts │ │ ├── portal/ │ │ │ └── route.ts │ │ └── webhooks/ │ │ └── dodo/ │ │ └── route.ts │ └── pricing/ │ └── page.tsx ├── components/ │ └── billingsdk/ │ ├── pricing-table-one.tsx │ └── subscription-management.tsx ├── lib/ │ ├── dodo.ts │ └── billingsdk-config.ts └── .env.local
Generated API Routes
Checkout Route (app/api/checkout/route.ts ):
import { NextRequest, NextResponse } from 'next/server'; import { dodo } from '@/lib/dodo';
export async function POST(req: NextRequest) { const { productId, email } = await req.json();
const session = await dodo.checkoutSessions.create({
product_cart: [{ product_id: productId, quantity: 1 }],
customer: { email },
return_url: ${process.env.NEXT_PUBLIC_APP_URL}/success,
});
return NextResponse.json({ checkoutUrl: session.checkout_url }); }
Portal Route (app/api/portal/route.ts ):
import { NextRequest, NextResponse } from 'next/server'; import { dodo } from '@/lib/dodo'; import { getSession } from '@/lib/auth';
export async function POST(req: NextRequest) { const session = await getSession();
const portal = await dodo.customers.createPortalSession({
customer_id: session.user.customerId,
return_url: ${process.env.NEXT_PUBLIC_APP_URL}/account,
});
return NextResponse.json({ url: portal.url }); }
Configuration File
lib/billingsdk-config.ts :
export const plans = [ { id: process.env.NEXT_PUBLIC_PLAN_FREE_ID!, name: 'Free', description: 'Perfect for trying out', price: 0, interval: 'month' as const, features: [ '5 projects', '1 GB storage', 'Community support', ], }, { id: process.env.NEXT_PUBLIC_PLAN_PRO_ID!, name: 'Pro', description: 'For professionals', price: 29, interval: 'month' as const, popular: true, features: [ 'Unlimited projects', '50 GB storage', 'Priority support', 'API access', ], }, ];
export const config = { returnUrl: process.env.NEXT_PUBLIC_APP_URL + '/success', portalReturnUrl: process.env.NEXT_PUBLIC_APP_URL + '/account', };
Customization
Styling with Tailwind
Components use Tailwind CSS and shadcn/ui patterns. Customize via:
-
Theme variables in globals.css
-
Direct class overrides on components
-
Component source modification (files are local)
Example - Custom colors:
/* globals.css */ @layer base { :root { --primary: 220 90% 56%; --primary-foreground: 0 0% 100%; } }
Component Props
Most components accept standard styling props:
<PricingTableOne plans={plans} onSelectPlan={handleSelect} className="max-w-4xl mx-auto" containerClassName="gap-8" cardClassName="border-2" />
Environment Variables
.env.local
Dodo Payments
DODO_PAYMENTS_API_KEY=sk_live_xxxxx DODO_PAYMENTS_WEBHOOK_SECRET=whsec_xxxxx
Product IDs (from dashboard)
NEXT_PUBLIC_PLAN_FREE_ID=prod_xxxxx NEXT_PUBLIC_PLAN_PRO_ID=prod_xxxxx NEXT_PUBLIC_PLAN_ENTERPRISE_ID=prod_xxxxx
App
NEXT_PUBLIC_APP_URL=https://yoursite.com
Best Practices
- Use Product IDs from Environment
Keep product IDs in environment variables for easy staging/production switching.
- Handle Loading States
Components should show loading states during checkout:
const [loading, setLoading] = useState(false);
const handleSelect = async (planId: string) => { setLoading(true); try { const response = await fetch('/api/checkout', {...}); const { checkoutUrl } = await response.json(); window.location.href = checkoutUrl; } finally { setLoading(false); } };
- Server-Side Data Fetching
Fetch subscription data server-side when possible:
// app/account/page.tsx import { getSubscription } from '@/lib/subscription';
export default async function AccountPage() { const subscription = await getSubscription();
return <SubscriptionManagement subscription={subscription} />; }
- Implement Webhooks
Always use webhooks as source of truth for subscription status, not client-side data.
Resources
-
BillingSDK Documentation
-
Dodo Payments Integration
-
Component Gallery
-
GitHub Repository