billing-sdk
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBillingSDK Integration
BillingSDK 集成
BillingSDK provides open-source, customizable React components for billing interfaces - pricing tables, subscription management, usage meters, and more.
BillingSDK 提供开源、可定制的React组件,用于构建账单界面——包括定价表格、订阅管理、使用量计量器等。
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
BillingSDK 具备以下特性:
- React组件:预构建、可定制的账单组件
- CLI工具:项目初始化与组件管理
- 框架支持:Next.js、Express.js、Hono、Fastify、React
- 支付服务商集成:与Dodo Payments深度集成
Quick Start Options
快速开始选项
Option 1: New Project (Recommended)
选项1:新建项目(推荐)
Complete project setup with framework configuration and API routes:
bash
npx @billingsdk/cli initThe 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
完成包含框架配置和API路由的项目搭建:
bash
npx @billingsdk/cli initCLI将完成以下操作:
- 配置你的框架(Next.js App Router)
- 搭建Dodo Payments集成
- 生成用于结账、客户管理、Webhook的API路由
- 安装依赖
- 创建配置文件
Option 2: Add to Existing Project
选项2:添加到现有项目
Add individual components using the CLI:
bash
npx @billingsdk/cli add pricing-table-one
npx @billingsdk/cli add subscription-management
npx @billingsdk/cli add usage-meter-circle使用CLI添加单个组件:
bash
npx @billingsdk/cli add pricing-table-one
npx @billingsdk/cli add subscription-management
npx @billingsdk/cli add usage-meter-circleOption 3: Manual via shadcn/ui
选项3:通过shadcn/ui手动安装
Install directly using shadcn registry:
bash
npx shadcn@latest add @billingsdk/pricing-table-one直接通过shadcn注册表安装:
bash
npx shadcn@latest add @billingsdk/pricing-table-oneCLI Reference
CLI 参考
Initialize Project
初始化项目
bash
npx @billingsdk/cli initInteractive setup prompts:
- Select framework (Next.js, Express.js, Hono, Fastify, React)
- Select payment provider (Dodo Payments)
- Configure project settings
bash
npx @billingsdk/cli init交互式设置提示:
- 选择框架(Next.js、Express.js、Hono、Fastify、React)
- 选择支付服务商(Dodo Payments)
- 配置项目设置
Add Components
添加组件
bash
npx @billingsdk/cli add <component-name>Available components:
- - Simple pricing table
pricing-table-one - - Feature-rich pricing table
pricing-table-two - - Manage active subscriptions
subscription-management - - Circular usage visualization
usage-meter-circle - More components available...
bash
npx @billingsdk/cli add <component-name>可用组件:
- - 简洁定价表格
pricing-table-one - - 功能丰富的定价表格
pricing-table-two - - 管理活跃订阅
subscription-management - - 圆形使用量可视化组件
usage-meter-circle - 更多组件即将推出...
What happens when adding:
添加组件时的操作:
- Downloads component from registry
- Installs files to
components/billingsdk/ - Updates project configuration
- Installs additional dependencies
- 从注册表下载组件
- 将文件安装到目录
components/billingsdk/ - 更新项目配置
- 安装额外依赖
Components
组件
Pricing Table One
定价表格一
Simple, clean pricing table for displaying plans.
Installation:
bash
npx @billingsdk/cli add pricing-table-one用于展示套餐的简洁定价表格。
安装:
bash
npx @billingsdk/cli add pricing-table-oneor
或
npx shadcn@latest add @billingsdk/pricing-table-one
**Usage:**
```tsx
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}
/>
);
}npx shadcn@latest add @billingsdk/pricing-table-one
**使用示例:**
```tsx
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) => {
// 创建结账会话
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:
bash
npx @billingsdk/cli add pricing-table-twoUsage:
tsx
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}`);
}}
/>
);
}支持月付/年付切换的功能对比型定价表格。
安装:
bash
npx @billingsdk/cli add pricing-table-two使用示例:
tsx
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:
bash
npx @billingsdk/cli add subscription-managementUsage:
tsx
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' });
}
}}
/>
);
}允许用户查看和管理他们的订阅。
安装:
bash
npx @billingsdk/cli add subscription-management使用示例:
tsx
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 () => {
// 打开客户门户
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:
bash
npx @billingsdk/cli add usage-meter-circleUsage:
tsx
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>
);
}展示基于使用量的账单指标。
安装:
bash
npx @billingsdk/cli add usage-meter-circle使用示例:
tsx
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
Next.js 集成
Project Structure (after init
)
init项目结构(执行init
后)
inityour-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.localyour-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.localGenerated API Routes
生成的API路由
Checkout Route ():
app/api/checkout/route.tstypescript
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.tstypescript
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 });
}结账路由():
app/api/checkout/route.tstypescript
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 });
}门户路由():
app/api/portal/route.tstypescript
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.tstypescript
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',
};lib/billingsdk-config.tstypescript
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
使用Tailwind CSS进行样式定制
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:
css
/* globals.css */
@layer base {
:root {
--primary: 220 90% 56%;
--primary-foreground: 0 0% 100%;
}
}组件使用Tailwind CSS和shadcn/ui模式。可通过以下方式定制:
- 全局CSS中的主题变量
- 组件上的直接类名覆盖
- 修改组件源代码(文件已本地化)
示例 - 自定义颜色:
css
/* globals.css */
@layer base {
:root {
--primary: 220 90% 56%;
--primary-foreground: 0 0% 100%;
}
}Component Props
组件属性
Most components accept standard styling props:
tsx
<PricingTableOne
plans={plans}
onSelectPlan={handleSelect}
className="max-w-4xl mx-auto"
containerClassName="gap-8"
cardClassName="border-2"
/>大多数组件支持标准样式属性:
tsx
<PricingTableOne
plans={plans}
onSelectPlan={handleSelect}
className="max-w-4xl mx-auto"
containerClassName="gap-8"
cardClassName="border-2"
/>Environment Variables
环境变量
bash
undefinedbash
undefined.env.local
.env.local
Dodo Payments
Dodo Payments
DODO_PAYMENTS_API_KEY=sk_live_xxxxx
DODO_PAYMENTS_WEBHOOK_SECRET=whsec_xxxxx
DODO_PAYMENTS_API_KEY=sk_live_xxxxx
DODO_PAYMENTS_WEBHOOK_SECRET=whsec_xxxxx
Product IDs (from dashboard)
产品ID(来自控制台)
NEXT_PUBLIC_PLAN_FREE_ID=prod_xxxxx
NEXT_PUBLIC_PLAN_PRO_ID=prod_xxxxx
NEXT_PUBLIC_PLAN_ENTERPRISE_ID=prod_xxxxx
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
---NEXT_PUBLIC_APP_URL=https://yoursite.com
---Best Practices
最佳实践
1. Use Product IDs from Environment
1. 从环境变量中读取产品ID
Keep product IDs in environment variables for easy staging/production switching.
将产品ID存储在环境变量中,便于在 staging 和生产环境之间切换。
2. Handle Loading States
2. 处理加载状态
Components should show loading states during checkout:
tsx
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);
}
};组件在结账过程中应显示加载状态:
tsx
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);
}
};3. Server-Side Data Fetching
3. 服务端数据获取
Fetch subscription data server-side when possible:
tsx
// app/account/page.tsx
import { getSubscription } from '@/lib/subscription';
export default async function AccountPage() {
const subscription = await getSubscription();
return <SubscriptionManagement subscription={subscription} />;
}尽可能通过服务端获取订阅数据:
tsx
// app/account/page.tsx
import { getSubscription } from '@/lib/subscription';
export default async function AccountPage() {
const subscription = await getSubscription();
return <SubscriptionManagement subscription={subscription} />;
}4. Implement Webhooks
4. 实现Webhook
Always use webhooks as source of truth for subscription status, not client-side data.
始终将Webhook作为订阅状态的可信数据源,而非客户端数据。