billing-sdk

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

BillingSDK 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 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
完成包含框架配置和API路由的项目搭建:
bash
npx @billingsdk/cli init
CLI将完成以下操作:
  • 配置你的框架(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-circle

Option 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-one

CLI Reference

CLI 参考

Initialize Project

初始化项目

bash
npx @billingsdk/cli init
Interactive setup prompts:
  1. Select framework (Next.js, Express.js, Hono, Fastify, React)
  2. Select payment provider (Dodo Payments)
  3. Configure project settings
bash
npx @billingsdk/cli init
交互式设置提示:
  1. 选择框架(Next.js、Express.js、Hono、Fastify、React)
  2. 选择支付服务商(Dodo Payments)
  3. 配置项目设置

Add Components

添加组件

bash
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...
bash
npx @billingsdk/cli add <component-name>
可用组件:
  • pricing-table-one
    - 简洁定价表格
  • pricing-table-two
    - 功能丰富的定价表格
  • subscription-management
    - 管理活跃订阅
  • usage-meter-circle
    - 圆形使用量可视化组件
  • 更多组件即将推出...

What happens when adding:

添加组件时的操作:

  1. Downloads component from registry
  2. Installs files to
    components/billingsdk/
  3. Updates project configuration
  4. Installs additional dependencies

  1. 从注册表下载组件
  2. 将文件安装到
    components/billingsdk/
    目录
  3. 更新项目配置
  4. 安装额外依赖

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-one

or

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-two
Usage:
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-management
Usage:
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-circle
Usage:
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
后)

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
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

生成的API路由

Checkout Route (
app/api/checkout/route.ts
):
typescript
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
):
typescript
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.ts
):
typescript
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.ts
):
typescript
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
:
typescript
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.ts
typescript
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:
  1. Theme variables in
    globals.css
  2. Direct class overrides on components
  3. 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模式。可通过以下方式定制:
  1. 全局CSS中的主题变量
  2. 组件上的直接类名覆盖
  3. 修改组件源代码(文件已本地化)
示例 - 自定义颜色:
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
undefined
bash
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作为订阅状态的可信数据源,而非客户端数据。

Resources

资源