metered-usage-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMetered Usage
计量使用量
Gate access and track consumption with and .
check()report()通过和控制访问权限并跟踪消耗情况。
check()report()check()
check()
Verify whether a customer can use a feature.
typescript
const result = await paykit.check({
customerId: "user_123",
featureId: "messages",
})
if (!result.allowed) {
throw new Error("Usage limit reached")
}验证客户是否可以使用某一功能。
typescript
const result = await paykit.check({
customerId: "user_123",
featureId: "messages",
})
if (!result.allowed) {
throw new Error("Usage limit reached")
}Parameters
参数
| Parameter | Required | Description |
|---|---|---|
| Yes | Your app's user ID |
| Yes | Feature to check (type-safe) |
| No | Check if at least this many units remain |
| 参数 | 是否必填 | 描述 |
|---|---|---|
| 是 | 你的应用用户ID |
| 是 | 要检查的功能(类型安全) |
| 否 | 检查是否至少剩余指定数量的可用单元 |
Return Value
返回值
typescript
interface CheckResult {
allowed: boolean
balance: {
limit: number
remaining: number
resetAt: Date | null
unlimited: boolean
} | null
}For boolean features: is /, is .
allowedtruefalsebalancenullFor metered features: is if (or ), contains usage details.
allowedtrueremaining > 0remaining >= requiredbalancetypescript
interface CheckResult {
allowed: boolean
balance: {
limit: number
remaining: number
resetAt: Date | null
unlimited: boolean
} | null
}对于布尔型功能: 为/,为。
allowedtruefalsebalancenull对于计量型功能: 如果(或),为,包含使用量详情。
remaining > 0remaining >= requiredallowedtruebalancePre-checking Availability
预检查可用性
Use to check if enough units remain before a batch operation:
requiredtypescript
const { allowed } = await paykit.check({
customerId: "user_123",
featureId: "api_calls",
required: 50,
})
if (!allowed) {
throw new Error("Not enough API calls remaining")
}使用参数在批量操作前检查是否有足够的可用单元:
requiredtypescript
const { allowed } = await paykit.check({
customerId: "user_123",
featureId: "api_calls",
required: 50,
})
if (!allowed) {
throw new Error("Not enough API calls remaining")
}report()
report()
Decrement usage after consumption.
typescript
const result = await paykit.report({
customerId: "user_123",
featureId: "messages",
amount: 1,
})
if (!result.success) {
// Usage limit exceeded
}消耗后扣除使用量。
typescript
const result = await paykit.report({
customerId: "user_123",
featureId: "messages",
amount: 1,
})
if (!result.success) {
// Usage limit exceeded
}Parameters
参数
| Parameter | Required | Description |
|---|---|---|
| Yes | Your app's user ID |
| Yes | Feature to decrement (type-safe) |
| No | Units consumed. Default: |
| 参数 | 是否必填 | 描述 |
|---|---|---|
| 是 | 你的应用用户ID |
| 是 | 要扣除使用量的功能(类型安全) |
| 否 | 消耗的单元数,默认值: |
Return Value
返回值
typescript
interface ReportResult {
success: boolean
balance: {
limit: number
remaining: number
resetAt: Date | null
unlimited: boolean
} | null
}successfalsetypescript
interface ReportResult {
success: boolean
balance: {
limit: number
remaining: number
resetAt: Date | null
unlimited: boolean
} | null
}如果客户剩余余额不足,为。
successfalseUsage Patterns
使用模式
Gate before action (check-then-act)
操作前校验(先检查后执行)
typescript
const { allowed } = await paykit.check({
customerId: userId,
featureId: "messages",
})
if (!allowed) {
return { error: "Message limit reached. Upgrade your plan." }
}
await sendMessage(content)
await paykit.report({
customerId: userId,
featureId: "messages",
})typescript
const { allowed } = await paykit.check({
customerId: userId,
featureId: "messages",
})
if (!allowed) {
return { error: "Message limit reached. Upgrade your plan." }
}
await sendMessage(content)
await paykit.report({
customerId: userId,
featureId: "messages",
})Atomic check-and-decrement
原子性检查并扣除
For simpler flows, skip and use directly. It fails if balance is insufficient:
check()report()typescript
const { success, balance } = await paykit.report({
customerId: userId,
featureId: "api_calls",
})
if (!success) {
return Response.json(
{ error: "API call limit exceeded", resetAt: balance?.resetAt },
{ status: 429 },
)
}
// Process the API call对于更简单的流程,可以跳过直接使用。如果余额不足,该方法会执行失败:
check()report()typescript
const { success, balance } = await paykit.report({
customerId: userId,
featureId: "api_calls",
})
if (!success) {
return Response.json(
{ error: "API call limit exceeded", resetAt: balance?.resetAt },
{ status: 429 },
)
}
// Process the API callBoolean feature gate
布尔型功能权限控制
typescript
const { allowed } = await paykit.check({
customerId: userId,
featureId: "custom_branding",
})
if (!allowed) {
return { error: "Custom branding requires a Pro plan" }
}typescript
const { allowed } = await paykit.check({
customerId: userId,
featureId: "custom_branding",
})
if (!allowed) {
return { error: "Custom branding requires a Pro plan" }
}Show usage to the user
向用户展示使用量
typescript
const { balance } = await paykit.check({
customerId: userId,
featureId: "messages",
})
// balance.remaining // units left
// balance.limit // total allowed
// balance.resetAt // when usage resets
// balance.unlimited // true if no limittypescript
const { balance } = await paykit.check({
customerId: userId,
featureId: "messages",
})
// balance.remaining // 剩余单元数
// balance.limit // 总允许量
// balance.resetAt // 使用量重置时间
// balance.unlimited // 无限制则为trueEntitlement Resets
权限重置
Metered entitlements reset lazily. The reset doesn't happen on a cron. It triggers on the next or call after the reset time has passed.
check()report()| Reset Interval | Behavior |
|---|---|
| Resets every 24 hours from first usage |
| Resets every 7 days |
| Resets every calendar month |
| Resets every calendar year |
计量型权限采用延迟重置机制。重置不会通过定时任务触发,而是在重置时间过后的下一次或调用时触发。
check()report()| 重置间隔 | 行为 |
|---|---|
| 首次使用后每24小时重置一次 |
| 每7天重置一次 |
| 每个日历月重置一次 |
| 每个日历年重置一次 |
Reading Entitlements Directly
直接读取权限信息
Entitlements are also available on the customer object:
typescript
const customer = await paykit.getCustomer({ id: "user_123" })
for (const [featureId, entitlement] of Object.entries(customer.entitlements)) {
console.log(featureId) // "messages"
console.log(entitlement.balance) // current balance
console.log(entitlement.limit) // max allowed
console.log(entitlement.usage) // consumed
console.log(entitlement.unlimited) // boolean
console.log(entitlement.nextResetAt)
}权限信息也可通过客户对象获取:
typescript
const customer = await paykit.getCustomer({ id: "user_123" })
for (const [featureId, entitlement] of Object.entries(customer.entitlements)) {
console.log(featureId) // "messages"
console.log(entitlement.balance) // 当前余额
console.log(entitlement.limit) // 最大允许量
console.log(entitlement.usage) // 已消耗量
console.log(entitlement.unlimited) // 布尔值
console.log(entitlement.nextResetAt)
}Complete Example: AI Chat with Usage Limits
完整示例:带使用量限制的AI聊天
typescript
// lib/paykit.ts
const messages = feature({ id: "messages", type: "metered" })
const proModels = feature({ id: "pro_models", type: "boolean" })
const free = plan({
id: "free",
group: "base",
default: true,
includes: [messages({ limit: 50, reset: "day" })],
})
const pro = plan({
id: "pro",
group: "base",
price: { amount: 20, interval: "month" },
includes: [messages({ limit: 2_000, reset: "day" }), proModels()],
})
// app/api/chat/route.ts
export async function POST(request: Request) {
const { userId, model, content } = await request.json()
// Check message quota
const { allowed, balance } = await paykit.check({
customerId: userId,
featureId: "messages",
})
if (!allowed) {
return Response.json({
error: "Daily message limit reached",
resetAt: balance?.resetAt,
}, { status: 429 })
}
// Check model access
if (model === "gpt-4") {
const { allowed } = await paykit.check({
customerId: userId,
featureId: "pro_models",
})
if (!allowed) {
return Response.json(
{ error: "Pro models require a Pro plan" },
{ status: 403 },
)
}
}
const response = await generateResponse(model, content)
// Decrement usage
await paykit.report({
customerId: userId,
featureId: "messages",
})
return Response.json({ response })
}typescript
// lib/paykit.ts
const messages = feature({ id: "messages", type: "metered" })
const proModels = feature({ id: "pro_models", type: "boolean" })
const free = plan({
id: "free",
group: "base",
default: true,
includes: [messages({ limit: 50, reset: "day" })],
})
const pro = plan({
id: "pro",
group: "base",
price: { amount: 20, interval: "month" },
includes: [messages({ limit: 2_000, reset: "day" }), proModels()],
})
// app/api/chat/route.ts
export async function POST(request: Request) {
const { userId, model, content } = await request.json()
// 检查消息配额
const { allowed, balance } = await paykit.check({
customerId: userId,
featureId: "messages",
})
if (!allowed) {
return Response.json({
error: "Daily message limit reached",
resetAt: balance?.resetAt,
}, { status: 429 })
}
// 检查模型访问权限
if (model === "gpt-4") {
const { allowed } = await paykit.check({
customerId: userId,
featureId: "pro_models",
})
if (!allowed) {
return Response.json(
{ error: "Pro models require a Pro plan" },
{ status: 403 },
)
}
}
const response = await generateResponse(model, content)
// 扣除使用量
await paykit.report({
customerId: userId,
featureId: "messages",
})
return Response.json({ response })
}