Loading...
Loading...
Compare original and translation side by side
| Type | Description | Best For |
|---|---|---|
| Custom Unit | Your own metric (tokens, API calls, compute hours) with configurable precision (0–3 decimals) | API calls, AI tokens, compute hours, messages |
| Fiat Credits | Real currency value (USD, EUR, etc.) that depletes as customers use your service | Prepaid balances, promotional credits, compensation |
| 类型 | 描述 | 适用场景 |
|---|---|---|
| 自定义单元 | 您自定义的指标(令牌、API调用次数、计算小时数),可配置精度(0–3位小数) | API调用、AI令牌、计算小时数、消息数量 |
| 法币信用额度 | 对应真实货币价值(美元、欧元等),客户使用服务时余额会相应减少 | 预付费余额、促销信用额度、补偿额度 |
| Source | Description |
|---|---|
| Subscription | Credits issued each billing cycle |
| One-Time | Credits issued with a one-time payment |
| API | Credits granted manually via API or dashboard |
| Rollover | Credits carried over from a previous billing cycle |
| 来源 | 描述 |
|---|---|
| 订阅 | 每个计费周期发放的信用额度 |
| 一次性购买 | 随一次性支付发放的信用额度 |
| API | 通过API或控制台手动授予的信用额度 |
| 结转 | 从上一个计费周期结转的信用额度 |
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
});
const credit = await client.creditEntitlements.create({
name: 'API Credits',
credit_type: 'custom_unit',
unit_name: 'API Calls',
precision: 0,
expiry_duration: 30, // days
rollover_enabled: false,
allow_overage: false,
});import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
});
const credit = await client.creditEntitlements.create({
name: 'API Credits',
credit_type: 'custom_unit',
unit_name: 'API Calls',
precision: 0,
expiry_duration: 30, // days
rollover_enabled: false,
allow_overage: false,
});const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_ai_pro_plan', // Product with credits attached
quantity: 1,
}
],
customer: { email: 'customer@example.com' },
return_url: 'https://yourapp.com/success',
});
// Redirect to session.checkout_urlconst session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_ai_pro_plan', // 已关联信用额度的产品
quantity: 1,
}
],
customer: { email: 'customer@example.com' },
return_url: 'https://yourapp.com/success',
});
// 重定向到session.checkout_url// Meter linked to credit entitlement deducts automatically
await client.usageEvents.ingest({
events: [{
event_id: `gen_${Date.now()}_${crypto.randomUUID()}`,
customer_id: 'cus_abc123',
event_name: 'ai.generation',
timestamp: new Date().toISOString(),
metadata: { model: 'gpt-4', tokens: '1500' }
}]
});// 关联到信用权益的计量器会自动扣减额度
await client.usageEvents.ingest({
events: [{
event_id: `gen_${Date.now()}_${crypto.randomUUID()}`,
customer_id: 'cus_abc123',
event_name: 'ai.generation',
timestamp: new Date().toISOString(),
metadata: { model: 'gpt-4', tokens: '1500' }
}]
});const balance = await client.creditEntitlements.balances.get(
'cent_credit_id',
'cus_abc123'
);
console.log(`Available: ${balance.available_balance}`);
console.log(`Overage: ${balance.overage_balance}`);const balance = await client.creditEntitlements.balances.get(
'cent_credit_id',
'cus_abc123'
);
console.log(`可用额度: ${balance.available_balance}`);
console.log(`超额使用: ${balance.overage_balance}`);| Operation | Method | Endpoint |
|---|---|---|
| Create | | |
| List | | |
| Get | | |
| Update | | |
| Delete | | |
| Undelete | | |
| 操作 | 请求方法 | 接口地址 |
|---|---|---|
| 创建 | | |
| 列表 | | |
| 获取详情 | | |
| 更新 | | |
| 删除 | | |
| 恢复删除 | | |
| Operation | Method | Endpoint |
|---|---|---|
| List All Balances | | |
| Get Customer Balance | | |
| Create Ledger Entry | | |
| List Customer Ledger | | |
| List Customer Grants | | |
| 操作 | 请求方法 | 接口地址 |
|---|---|---|
| 列出所有余额 | | |
| 获取客户余额 | | |
| 创建账本记录 | | |
| 列出客户账本 | | |
| 列出客户信用额度发放记录 | | |
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
});
// Custom unit credit (AI tokens)
const tokenCredit = await client.creditEntitlements.create({
name: 'AI Tokens',
credit_type: 'custom_unit',
unit_name: 'tokens',
precision: 0,
expiry_duration: 30,
rollover_enabled: true,
max_rollover_percentage: 25,
rollover_timeframe: 'month',
max_rollover_count: 3,
allow_overage: true,
overage_limit: 50000,
price_per_unit: 0.001,
overage_behavior: 'bill_overage_at_billing',
});
// Fiat credit (USD balance)
const usdCredit = await client.creditEntitlements.create({
name: 'Platform Credits',
credit_type: 'fiat',
unit_currency: 'USD',
expiry_duration: 90,
rollover_enabled: false,
allow_overage: false,
});import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
});
// 自定义单元信用额度(AI令牌)
const tokenCredit = await client.creditEntitlements.create({
name: 'AI Tokens',
credit_type: 'custom_unit',
unit_name: 'tokens',
precision: 0,
expiry_duration: 30,
rollover_enabled: true,
max_rollover_percentage: 25,
rollover_timeframe: 'month',
max_rollover_count: 3,
allow_overage: true,
overage_limit: 50000,
price_per_unit: 0.001,
overage_behavior: 'bill_overage_at_billing',
});
// 法币信用额度(美元余额)
const usdCredit = await client.creditEntitlements.create({
name: 'Platform Credits',
credit_type: 'fiat',
unit_currency: 'USD',
expiry_duration: 90,
rollover_enabled: false,
allow_overage: false,
});// Grant credits manually (e.g., promotional bonus)
await client.creditEntitlements.balances.createLedgerEntry(
'cent_credit_id',
'cus_abc123',
{
type: 'credit',
amount: '500',
description: 'Welcome bonus - 500 free API credits',
idempotency_key: `welcome_bonus_${customerId}`,
}
);
// Debit credits manually (e.g., service compensation deduction)
await client.creditEntitlements.balances.createLedgerEntry(
'cent_credit_id',
'cus_abc123',
{
type: 'debit',
amount: '100',
description: 'Manual deduction for premium support',
idempotency_key: `support_deduction_${Date.now()}`,
}
);// 手动授予信用额度(例如促销奖励)
await client.creditEntitlements.balances.createLedgerEntry(
'cent_credit_id',
'cus_abc123',
{
type: 'credit',
amount: '500',
description: 'Welcome bonus - 500 free API credits',
idempotency_key: `welcome_bonus_${customerId}`,
}
);
// 手动扣减信用额度(例如服务补偿扣减)
await client.creditEntitlements.balances.createLedgerEntry(
'cent_credit_id',
'cus_abc123',
{
type: 'debit',
amount: '100',
description: 'Manual deduction for premium support',
idempotency_key: `support_deduction_${Date.now()}`,
}
);// Get current balance
const balance = await client.creditEntitlements.balances.get(
'cent_credit_id',
'cus_abc123'
);
console.log(`Balance: ${balance.available_balance}`);
// List all balances for a credit entitlement
const allBalances = await client.creditEntitlements.balances.list(
'cent_credit_id'
);
// Get full transaction history
const ledger = await client.creditEntitlements.balances.listLedger(
'cent_credit_id',
'cus_abc123'
);
for (const entry of ledger.items) {
console.log(`${entry.type}: ${entry.amount} | Balance: ${entry.balance_after}`);
}
// List credit grants
const grants = await client.creditEntitlements.balances.listGrants(
'cent_credit_id',
'cus_abc123'
);// 获取当前余额
const balance = await client.creditEntitlements.balances.get(
'cent_credit_id',
'cus_abc123'
);
console.log(`余额: ${balance.available_balance}`);
// 列出某个信用权益的所有客户余额
const allBalances = await client.creditEntitlements.balances.list(
'cent_credit_id'
);
// 获取完整交易历史
const ledger = await client.creditEntitlements.balances.listLedger(
'cent_credit_id',
'cus_abc123'
);
for (const entry of ledger.items) {
console.log(`${entry.type}: ${entry.amount} | 余额: ${entry.balance_after}`);
}
// 列出信用额度发放记录
const grants = await client.creditEntitlements.balances.listGrants(
'cent_credit_id',
'cus_abc123'
);await client.creditEntitlements.update('cent_credit_id', {
rollover_enabled: true,
max_rollover_percentage: 50,
allow_overage: true,
overage_limit: 10000,
price_per_unit: 0.002,
overage_behavior: 'bill_overage_at_billing',
});await client.creditEntitlements.update('cent_credit_id', {
rollover_enabled: true,
max_rollover_percentage: 50,
allow_overage: true,
overage_limit: 10000,
price_per_unit: 0.002,
overage_behavior: 'bill_overage_at_billing',
});from dodopayments import DodoPayments
import os
import uuid
from datetime import datetime
client = DodoPayments(bearer_token=os.environ["DODO_PAYMENTS_API_KEY"])from dodopayments import DodoPayments
import os
import uuid
from datetime import datetime
client = DodoPayments(bearer_token=os.environ["DODO_PAYMENTS_API_KEY"])undefinedundefinedpackage main
import (
"context"
"fmt"
"os"
"time"
"github.com/dodopayments/dodopayments-go"
"github.com/google/uuid"
)
func main() {
client := dodopayments.NewClient(
option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
)
ctx := context.Background()
// Create credit entitlement
credit, err := client.CreditEntitlements.Create(ctx, &dodopayments.CreditEntitlementCreateParams{
Name: "AI Tokens",
CreditType: "custom_unit",
UnitName: "tokens",
Precision: 0,
})
if err != nil {
panic(err)
}
// Get customer balance
balance, err := client.CreditEntitlements.Balances.Get(ctx, credit.ID, "cus_abc123")
if err != nil {
panic(err)
}
fmt.Printf("Balance: %s\n", balance.AvailableBalance)
// Send usage events
_, err = client.UsageEvents.Ingest(ctx, &dodopayments.UsageEventIngestParams{
Events: []dodopayments.UsageEvent{{
EventID: fmt.Sprintf("api_%d_%s", time.Now().Unix(), uuid.New().String()),
CustomerID: "cus_abc123",
EventName: "ai.tokens",
Timestamp: time.Now().Format(time.RFC3339),
Metadata: map[string]string{
"tokens": "1500",
"model": "gpt-4",
},
}},
})
if err != nil {
panic(err)
}
}package main
import (
"context"
"fmt"
"os"
"time"
"github.com/dodopayments/dodopayments-go"
"github.com/google/uuid"
)
func main() {
client := dodopayments.NewClient(
option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
)
ctx := context.Background()
// 创建信用权益
credit, err := client.CreditEntitlements.Create(ctx, &dodopayments.CreditEntitlementCreateParams{
Name: "AI Tokens",
CreditType: "custom_unit",
UnitName: "tokens",
Precision: 0,
})
if err != nil {
panic(err)
}
// 获取客户余额
balance, err := client.CreditEntitlements.Balances.Get(ctx, credit.ID, "cus_abc123")
if err != nil {
panic(err)
}
fmt.Printf("余额: %s\n", balance.AvailableBalance)
// 发送使用事件
_, err = client.UsageEvents.Ingest(ctx, &dodopayments.UsageEventIngestParams{
Events: []dodopayments.UsageEvent{{
EventID: fmt.Sprintf("api_%d_%s", time.Now().Unix(), uuid.New().String()),
CustomerID: "cus_abc123",
EventName: "ai.tokens",
Timestamp: time.Now().Format(time.RFC3339),
Metadata: map[string]string{
"tokens": "1500",
"model": "gpt-4",
},
}},
})
if err != nil {
panic(err)
}
}| Setting | Description |
|---|---|
| Rollover Enabled | Toggle to allow unused credits to carry forward |
| Max Rollover Percentage | Limit how much carries over (0–100%) |
| Rollover Timeframe | How long rolled-over credits remain valid (day, week, month, year) |
| Max Rollover Count | Maximum consecutive rollovers before credits are forfeited |
| 设置项 | 描述 |
|---|---|
| 启用结转 | 开启/关闭未使用信用额度的结转功能 |
| 最大结转比例 | 可结转的未使用额度比例(0–100%) |
| 结转有效期 | 结转后的信用额度有效期(天、周、月、年) |
| 最大结转次数 | 信用额度可连续结转的最大次数,超出后将被作废 |
| Setting | Description |
|---|---|
| Allow Overage | Let customers continue past zero balance |
| Overage Limit | Max credits consumable beyond balance |
| Price Per Unit | Cost per additional credit (with currency) |
| Overage Behavior | How overage is handled at cycle end |
| Behavior | Description |
|---|---|
| Forgive overage at reset | Overage tracked but not billed (default) |
| Bill overage at billing | Overage charged on next invoice |
| Carry over deficit | Negative balance carries into next cycle |
| Carry over deficit (auto-repay) | Deficit auto-repaid from new credits next cycle |
| 设置项 | 描述 |
|---|---|
| 允许超额使用 | 允许客户在余额为零后继续使用服务 |
| 超额使用上限 | 超出余额后的最大可使用信用额度 |
| 单位价格 | 超额使用部分的单价(含货币类型) |
| 超额使用处理方式 | 周期结束时超额部分的处理方式 |
| 方式 | 描述 |
|---|---|
| 重置时豁免 | 记录超额使用但不计费(默认) |
| 计费周期结算 | 超额部分计入下一账单 |
| 结转赤字 | 负余额结转至下一周期 |
| 结转赤字(自动偿还) | 赤字将从下一周期的新信用额度中自动扣除 |
| Setting | Description |
|---|---|
| Credit Expiry | Duration after issuance: 7, 30, 60, 90, custom days, or never |
| Trial Credits Expire After Trial | Whether trial-specific credits expire when trial ends |
| 设置项 | 描述 |
|---|---|
| 信用额度有效期 | 发放后的有效期:7、30、60、90天,自定义天数,或永不过期 |
| 试用信用额度随试用结束过期 | 试用专属信用额度是否在试用结束时过期 |
| Event | Description |
|---|---|
| Credits granted to a customer |
| Credits consumed through usage or manual debit |
| Unused credits expired |
| Credits carried forward to a new grant |
| Credits forfeited at max rollover count |
| Overage charges applied |
| Manual credit/debit adjustment made |
| Balance dropped below configured threshold |
| 事件 | 描述 |
|---|---|
| 向客户授予了信用额度 |
| 通过使用或手动扣减消耗了信用额度 |
| 未使用的信用额度已过期 |
| 信用额度已结转至新的发放记录 |
| 信用额度因达到最大结转次数而作废 |
| 已对超额使用部分计费 |
| 已手动调整信用额度(增减) |
| 余额已降至配置的阈值以下 |
// app/api/webhooks/dodo/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
const event = await req.json();
switch (event.type) {
case 'credit.added':
await handleCreditAdded(event.data);
break;
case 'credit.deducted':
await handleCreditDeducted(event.data);
break;
case 'credit.balance_low':
await handleBalanceLow(event.data);
break;
case 'credit.expired':
await handleCreditExpired(event.data);
break;
case 'credit.overage_charged':
await handleOverageCharged(event.data);
break;
}
return NextResponse.json({ received: true });
}
async function handleCreditAdded(data: any) {
const { customer_id, credit_entitlement_id, amount, balance_after } = data;
// Update internal records
await prisma.creditBalance.upsert({
where: { customerId_creditId: { customerId: customer_id, creditId: credit_entitlement_id } },
create: { customerId: customer_id, creditId: credit_entitlement_id, balance: balance_after },
update: { balance: balance_after },
});
}
async function handleCreditDeducted(data: any) {
const { customer_id, credit_entitlement_id, amount, balance_after } = data;
await prisma.creditBalance.update({
where: { customerId_creditId: { customerId: customer_id, creditId: credit_entitlement_id } },
data: { balance: balance_after },
});
}
async function handleBalanceLow(data: any) {
const {
customer_id,
credit_entitlement_name,
available_balance,
threshold_percent,
} = data;
// Notify the customer
await sendEmail(customer_id, {
subject: `Your ${credit_entitlement_name} balance is running low`,
body: `You have ${available_balance} credits remaining (${threshold_percent}% threshold reached). Consider upgrading your plan or purchasing additional credits.`,
});
}
async function handleCreditExpired(data: any) {
const { customer_id, credit_entitlement_id, amount, balance_after } = data;
await prisma.creditBalance.update({
where: { customerId_creditId: { customerId: customer_id, creditId: credit_entitlement_id } },
data: { balance: balance_after },
});
// Optionally notify customer
await sendCreditExpiryNotification(customer_id, amount);
}
async function handleOverageCharged(data: any) {
const { customer_id, credit_entitlement_id, amount, overage_after } = data;
// Track overage for billing
await prisma.overageRecord.create({
data: {
customerId: customer_id,
creditId: credit_entitlement_id,
amount,
overageBalance: overage_after,
},
});
}// app/api/webhooks/dodo/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
const event = await req.json();
switch (event.type) {
case 'credit.added':
await handleCreditAdded(event.data);
break;
case 'credit.deducted':
await handleCreditDeducted(event.data);
break;
case 'credit.balance_low':
await handleBalanceLow(event.data);
break;
case 'credit.expired':
await handleCreditExpired(event.data);
break;
case 'credit.overage_charged':
await handleOverageCharged(event.data);
break;
}
return NextResponse.json({ received: true });
}
async function handleCreditAdded(data: any) {
const { customer_id, credit_entitlement_id, amount, balance_after } = data;
// 更新内部记录
await prisma.creditBalance.upsert({
where: { customerId_creditId: { customerId: customer_id, creditId: credit_entitlement_id } },
create: { customerId: customer_id, creditId: credit_entitlement_id, balance: balance_after },
update: { balance: balance_after },
});
}
async function handleCreditDeducted(data: any) {
const { customer_id, credit_entitlement_id, amount, balance_after } = data;
await prisma.creditBalance.update({
where: { customerId_creditId: { customerId: customer_id, creditId: credit_entitlement_id } },
data: { balance: balance_after },
});
}
async function handleBalanceLow(data: any) {
const {
customer_id,
credit_entitlement_name,
available_balance,
threshold_percent,
} = data;
// 通知客户
await sendEmail(customer_id, {
subject: `您的${credit_entitlement_name}余额不足`,
body: `您剩余${available_balance}个信用额度(已达到${threshold_percent}%的阈值)。请考虑升级套餐或购买额外信用额度。`,
});
}
async function handleCreditExpired(data: any) {
const { customer_id, credit_entitlement_id, amount, balance_after } = data;
await prisma.creditBalance.update({
where: { customerId_creditId: { customerId: customer_id, creditId: credit_entitlement_id } },
data: { balance: balance_after },
});
// 可选:通知客户
await sendCreditExpiryNotification(customer_id, amount);
}
async function handleOverageCharged(data: any) {
const { customer_id, credit_entitlement_id, amount, overage_after } = data;
// 记录超额使用用于计费
await prisma.overageRecord.create({
data: {
customerId: customer_id,
creditId: credit_entitlement_id,
amount,
overageBalance: overage_after,
},
});
}credit.balance_low{
"business_id": "bus_xxxxx",
"type": "credit.balance_low",
"timestamp": "2025-08-04T06:15:00.000000Z",
"data": {
"payload_type": "CreditBalanceLow",
"customer_id": "cus_xxxxx",
"subscription_id": "sub_xxxxx",
"credit_entitlement_id": "cent_xxxxx",
"credit_entitlement_name": "API Credits",
"available_balance": "15",
"subscription_credits_amount": "100",
"threshold_percent": 20,
"threshold_amount": "20"
}
}credit.balance_low{
"business_id": "bus_xxxxx",
"type": "credit.balance_low",
"timestamp": "2025-08-04T06:15:00.000000Z",
"data": {
"payload_type": "CreditBalanceLow",
"customer_id": "cus_xxxxx",
"subscription_id": "sub_xxxxx",
"credit_entitlement_id": "cent_xxxxx",
"credit_entitlement_name": "API Credits",
"available_balance": "15",
"subscription_credits_amount": "100",
"threshold_percent": 20,
"threshold_amount": "20"
}
}meter_units_per_creditmeter_units_per_creditimport DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
});
// Track AI token usage — meter auto-deducts credits
async function trackAIUsage(
customerId: string,
promptTokens: number,
completionTokens: number,
model: string
) {
const totalTokens = promptTokens + completionTokens;
await client.usageEvents.ingest({
events: [{
event_id: `ai_${Date.now()}_${crypto.randomUUID()}`,
customer_id: customerId,
event_name: 'ai.tokens',
timestamp: new Date().toISOString(),
metadata: {
tokens: totalTokens.toString(),
prompt_tokens: promptTokens.toString(),
completion_tokens: completionTokens.toString(),
model,
}
}]
});
}
// After AI completion
await trackAIUsage('cus_abc123', 500, 1200, 'gpt-4');import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
});
// 追踪AI令牌使用 — 计量器自动扣减信用额度
async function trackAIUsage(
customerId: string,
promptTokens: number,
completionTokens: number,
model: string
) {
const totalTokens = promptTokens + completionTokens;
await client.usageEvents.ingest({
events: [{
event_id: `ai_${Date.now()}_${crypto.randomUUID()}`,
customer_id: customerId,
event_name: 'ai.tokens',
timestamp: new Date().toISOString(),
metadata: {
tokens: totalTokens.toString(),
prompt_tokens: promptTokens.toString(),
completion_tokens: completionTokens.toString(),
model,
}
}]
});
}
// AI生成完成后调用
await trackAIUsage('cus_abc123', 500, 1200, 'gpt-4');// Middleware to track and deduct API credits
async function trackAPICredit(customerId: string, req: Request) {
await client.usageEvents.ingest({
events: [{
event_id: `api_${Date.now()}_${crypto.randomUUID()}`,
customer_id: customerId,
event_name: 'api.call',
timestamp: new Date().toISOString(),
metadata: {
endpoint: new URL(req.url).pathname,
method: req.method,
}
}]
});
}// 用于追踪和扣减API信用额度的中间件
async function trackAPICredit(customerId: string, req: Request) {
await client.usageEvents.ingest({
events: [{
event_id: `api_${Date.now()}_${crypto.randomUUID()}`,
customer_id: customerId,
event_name: 'api.call',
timestamp: new Date().toISOString(),
metadata: {
endpoint: new URL(req.url).pathname,
method: req.method,
}
}]
});
}// app/api/credits/balance/route.ts
import { NextRequest, NextResponse } from 'next/server';
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
});
export async function GET(req: NextRequest) {
const customerId = req.nextUrl.searchParams.get('customer_id');
const creditId = req.nextUrl.searchParams.get('credit_id');
if (!customerId || !creditId) {
return NextResponse.json({ error: 'Missing parameters' }, { status: 400 });
}
try {
const balance = await client.creditEntitlements.balances.get(creditId, customerId);
return NextResponse.json({
available: balance.available_balance,
overage: balance.overage_balance,
});
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}// app/api/credits/balance/route.ts
import { NextRequest, NextResponse } from 'next/server';
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
});
export async function GET(req: NextRequest) {
const customerId = req.nextUrl.searchParams.get('customer_id');
const creditId = req.nextUrl.searchParams.get('credit_id');
if (!customerId || !creditId) {
return NextResponse.json({ error: '缺少参数' }, { status: 400 });
}
try {
const balance = await client.creditEntitlements.balances.get(creditId, customerId);
return NextResponse.json({
available: balance.available_balance,
overage: balance.overage_balance,
});
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}// app/api/credits/grant/route.ts
import { NextRequest, NextResponse } from 'next/server';
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
});
export async function POST(req: NextRequest) {
const { customerId, creditId, amount, description } = await req.json();
try {
const entry = await client.creditEntitlements.balances.createLedgerEntry(
creditId,
customerId,
{
type: 'credit',
amount: amount.toString(),
description,
idempotency_key: `grant_${customerId}_${Date.now()}`,
}
);
return NextResponse.json({ success: true, balance_after: entry.balance_after });
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}// app/api/credits/grant/route.ts
import { NextRequest, NextResponse } from 'next/server';
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
});
export async function POST(req: NextRequest) {
const { customerId, creditId, amount, description } = await req.json();
try {
const entry = await client.creditEntitlements.balances.createLedgerEntry(
creditId,
customerId,
{
type: 'credit',
amount: amount.toString(),
description,
idempotency_key: `grant_${customerId}_${Date.now()}`,
}
);
return NextResponse.json({ success: true, balance_after: entry.balance_after });
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}// hooks/useCreditBalance.ts
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then(r => r.json());
export function useCreditBalance(customerId: string, creditId: string) {
const { data, error, mutate } = useSWR(
`/api/credits/balance?customer_id=${customerId}&credit_id=${creditId}`,
fetcher,
{ refreshInterval: 30000 } // Refresh every 30s
);
return {
available: data?.available,
overage: data?.overage,
isLoading: !error && !data,
isError: error,
refresh: mutate,
};
}
// Usage in component
function CreditDisplay() {
const { available, isLoading } = useCreditBalance('cus_abc123', 'cent_xxxxx');
if (isLoading) return <div>Loading...</div>;
return (
<div>
<h3>Credits Remaining</h3>
<p className="text-3xl font-bold">{available}</p>
</div>
);
}// hooks/useCreditBalance.ts
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then(r => r.json());
export function useCreditBalance(customerId: string, creditId: string) {
const { data, error, mutate } = useSWR(
`/api/credits/balance?customer_id=${customerId}&credit_id=${creditId}`,
fetcher,
{ refreshInterval: 30000 } // 每30秒刷新一次
);
return {
available: data?.available,
overage: data?.overage,
isLoading: !error && !data,
isError: error,
refresh: mutate,
};
}
// 在组件中使用
function CreditDisplay() {
const { available, isLoading } = useCreditBalance('cus_abc123', 'cent_xxxxx');
if (isLoading) return <div>加载中...</div>;
return (
<div>
<h3>剩余信用额度</h3>
<p className="text-3xl font-bold">{available}</p>
</div>
);
}// Product: "500 API Credit Pack" with 500 credits attached
const session = await client.checkoutSessions.create({
product_cart: [
{ product_id: 'prod_credit_pack_500', quantity: 1 }
],
customer: { email: 'customer@example.com' },
return_url: 'https://yourapp.com/credits/success',
});// 产品:"500 API信用额度包",关联500个信用额度
const session = await client.checkoutSessions.create({
product_cart: [
{ product_id: 'prod_credit_pack_500', quantity: 1 }
],
customer: { email: 'customer@example.com' },
return_url: 'https://yourapp.com/credits/success',
});// Product: Pro Plan - $49/month, 10,000 tokens included, $0.002/token overage
// Credits attached in dashboard with:
// - Credits per cycle: 10000
// - Allow overage: true
// - Price per unit: $0.002
// - Overage behavior: Bill overage at billing
const session = await client.checkoutSessions.create({
product_cart: [
{ product_id: 'prod_pro_with_credits', quantity: 1 }
],
subscription_data: { trial_period_days: 14 },
customer: { email: 'customer@example.com' },
return_url: 'https://yourapp.com/success',
});// 产品:专业套餐 - 每月49美元,包含10,000个令牌,超额部分0.002美元/令牌
// 在控制台中关联信用额度:
// - 每周期信用额度:10000
// - 允许超额使用:是
// - 单位价格:0.002美元
// - 超额使用处理方式:计费周期结算
const session = await client.checkoutSessions.create({
product_cart: [
{ product_id: 'prod_pro_with_credits', quantity: 1 }
],
subscription_data: { trial_period_days: 14 },
customer: { email: 'customer@example.com' },
return_url: 'https://yourapp.com/success',
});// Middleware to check credit balance before allowing API access
async function requireCredits(customerId: string, creditId: string): Promise<boolean> {
try {
const balance = await client.creditEntitlements.balances.get(creditId, customerId);
const available = parseFloat(balance.available_balance);
return available > 0;
} catch {
return false;
}
}
// Express middleware
async function creditGate(req: Request, res: Response, next: Function) {
const customerId = req.headers['x-customer-id'] as string;
const hasCredits = await requireCredits(customerId, 'cent_api_credits');
if (!hasCredits) {
return res.status(402).json({
error: 'Insufficient credits',
message: 'Your credit balance is exhausted. Please upgrade your plan or purchase more credits.',
});
}
next();
}// 允许API访问前检查信用额度的中间件
async function requireCredits(customerId: string, creditId: string): Promise<boolean> {
try {
const balance = await client.creditEntitlements.balances.get(creditId, customerId);
const available = parseFloat(balance.available_balance);
return available > 0;
} catch {
return false;
}
}
// Express中间件
async function creditGate(req: Request, res: Response, next: Function) {
const customerId = req.headers['x-customer-id'] as string;
const hasCredits = await requireCredits(customerId, 'cent_api_credits');
if (!hasCredits) {
return res.status(402).json({
error: '信用额度不足',
message: '您的信用额度已耗尽,请升级套餐或购买更多信用额度。',
});
}
next();
}// Grant promotional credits with idempotency
async function grantPromoCredits(
customerId: string,
creditId: string,
amount: number,
promoCode: string
) {
await client.creditEntitlements.balances.createLedgerEntry(
creditId,
customerId,
{
type: 'credit',
amount: amount.toString(),
description: `Promotional credit: ${promoCode}`,
idempotency_key: `promo_${promoCode}_${customerId}`, // Prevents double-granting
}
);
}// 使用幂等键授予促销信用额度
async function grantPromoCredits(
customerId: string,
creditId: string,
amount: number,
promoCode: string
) {
await client.creditEntitlements.balances.createLedgerEntry(
creditId,
customerId,
{
type: 'credit',
amount: amount.toString(),
description: `促销信用额度: ${promoCode}`,
idempotency_key: `promo_${promoCode}_${customerId}`, // 防止重复授予
}
);
}Plan | Price | Credits/Month | Overage
----------|----------|----------------|----------
Starter | $29/mo | 10,000 tokens | $0.003/token
Pro | $99/mo | 100,000 tokens | $0.002/token
Enterprise| $499/mo | 1,000,000 tokens| $0.001/token
Credit Config:
Type: Custom Unit ("AI Tokens"), Precision: 0
Rollover: 25% max, 1-month timeframe
Overage: Bill overage at billing
Meter: ai.generation (Sum on tokens)套餐 | 价格 | 每月信用额度 | 超额使用
----------|----------|----------------|----------
入门版 | 29美元/月 | 10,000个令牌 | 0.003美元/令牌
专业版 | 99美元/月 | 100,000个令牌 | 0.002美元/令牌
企业版 | 499美元/月| 1,000,000个令牌| 0.001美元/令牌
信用额度配置:
类型: 自定义单元("AI令牌"),精度: 0
结转: 最大25%,有效期1个月
超额使用: 计费周期结算
计量器: ai.generation(令牌求和)Plan | Price | Credits/Month | Overage
----------|----------|----------------|----------
Free | $0/mo | 1,000 calls | Blocked
Developer | $19/mo | 50,000 calls | $0.001/call
Business | $99/mo | 500,000 calls | $0.0005/call
Credit Config:
Type: Custom Unit ("API Calls"), Precision: 0
Rollover: Disabled
Overage: Free=disabled, Dev+=forgive at reset
Meter: api.request (Count)套餐 | 价格 | 每月信用额度 | 超额使用
----------|----------|----------------|----------
免费版 | 0美元/月 | 1,000次调用 | 禁止
开发者版 | 19美元/月 | 50,000次调用 | 0.001美元/次
企业版 | 99美元/月 | 500,000次调用 | 0.0005美元/次
信用额度配置:
类型: 自定义单元("API调用"),精度: 0
结转: 禁用
超额使用: 免费版禁用,其他版本重置时豁免
计量器: api.request(计数)Plan | Price | Credits/Month | Overage
----------|----------|----------------|----------
Personal | $9/mo | 100 GB-hours | $0.05/GB-hour
Team | $49/mo | 1,000 GB-hours | $0.03/GB-hour
Credit Config:
Type: Custom Unit ("GB-hours"), Precision: 2
Rollover: 50% max, carries over once
Overage: Enabled with 200% limit
Meter: storage.usage (Sum)套餐 | 价格 | 每月信用额度 | 超额使用
----------|----------|----------------|----------
个人版 | 9美元/月 | 100 GB小时 | 0.05美元/GB小时
团队版 | 49美元/月 | 1,000 GB小时 | 0.03美元/GB小时
信用额度配置:
类型: 自定义单元("GB小时"),精度: 2
结转: 最大50%,可结转一次
超额使用: 启用,上限200%
计量器: storage.usage(求和)| Transaction Type | Description |
|---|---|
| Credit Added | Credits granted (subscription, one-time, or API) |
| Credit Deducted | Credits consumed through usage or manual debit |
| Credit Expired | Credits expired without rollover |
| Credit Rolled Over | Credits carried forward to the next period |
| Rollover Forfeited | Rolled credits forfeited after max count reached |
| Overage Charged | Usage beyond credit balance with overage enabled |
| Auto Top-Up | Automatic credit replenishment at low balance |
| Manual Adjustment | Credit or debit applied manually by merchant |
| Refund | Credits refunded |
| 交易类型 | 描述 |
|---|---|
| 信用额度增加 | 授予信用额度(订阅、一次性购买或API) |
| 信用额度扣减 | 通过使用或手动扣减消耗信用额度 |
| 信用额度过期 | 未使用的信用额度过期且未结转 |
| 信用额度结转 | 信用额度结转至下一周期 |
| 结转额度作废 | 结转次数达到上限后额度作废 |
| 超额使用计费 | 对超出信用额度的使用进行计费 |
| 自动充值 | 余额低时自动补充信用额度 |
| 手动调整 | 商家手动增减信用额度 |
| 退款 | 信用额度退款 |
credit.balance_lowcredit.balance_lowidempotency_key: `promo_${promoCode}_${customerId}`idempotency_key: `promo_${promoCode}_${customerId}`