关于 metered-usage-best-practices
这份 Paykit 使用指南介绍如何在应用中检查功能权限并记录用量,适用于实现按用量限制的产品功能。它涵盖预检、扣减、余额读取及权益重置行为。
主要功能
- 01检查布尔型与计量型权益
- 02预检所需用量余额
- 03报告消耗并扣减用量
- 04读取余额、限额和重置时间
- 05按周期延迟重置权益
适用场景
- 01执行操作前验证用户额度
- 02为批量任务预留所需单位
- 03用报告操作完成原子扣减
- 04向用户展示剩余量和重置时间
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ getpaykit/ skills --skill metered-usage-best-practices安装后在 Claude Code 中输入/metered-usage-best-practices确认已加载。
详细介绍
计量用量
使用 check() 和 report() 控制访问权限并跟踪用量。
check()
检查客户是否可以使用某项功能。
const result = await paykit.check({
customerId: "user_123",
featureId: "messages",
})
if (!result.allowed) {
throw new Error("Usage limit reached")
}
参数
| 参数 | 必填 | 说明 |
|---|---|---|
customerId | 是 | 你应用的用户 ID |
featureId | 是 | 要检查的功能(类型安全) |
required | 否 | 检查是否至少还剩这么多单位 |
返回值
interface CheckResult {
allowed: boolean
balance: {
limit: number
remaining: number
resetAt: Date | null
unlimited: boolean
} | null
}
对于布尔型功能: allowed 为 true/false,balance 为 null。
对于计量型功能: 如果 remaining > 0(或 remaining >= required),则 allowed 为 true;balance 包含用量详情。
预先检查可用量
在批量操作前,使用 required 检查是否有足够的剩余单位:
const { allowed } = await paykit.check({
customerId: "user_123",
featureId: "api_calls",
required: 50,
})
if (!allowed) {
throw new Error("Not enough API calls remaining")
}
report()
使用后扣减用量。
const result = await paykit.report({
customerId: "user_123",
featureId: "messages",
amount: 1,
})
if (!result.success) {
// Usage limit exceeded
}
参数
| 参数 | 必填 | 说明 |
|---|---|---|
customerId | 是 | 你应用的用户 ID |
featureId | 是 | 要扣减的功能(类型安全) |
amount | 否 | 消耗的单位数。默认值:1 |
返回值
interface ReportResult {
success: boolean
balance: {
limit: number
remaining: number
resetAt: Date | null
unlimited: boolean
} | null
}
如果客户的剩余余额不足,success 为 false。
用法模式
执行操作前进行权限检查(先检查,再操作)
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",
})
原子式检查并扣减
对于更简单的流程,可以跳过 check(),直接使用 report()。如果余额不足,操作会失败:
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
布尔型功能权限检查
const { allowed } = await paykit.check({
customerId: userId,
featureId: "custom_branding",
})
if (!allowed) {
return { error: "Custom branding requires a Pro plan" }
}
向用户显示用量
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 limit
权益重置
计量型权益采用惰性重置,不会通过 cron 执行。重置时间过后,下一次调用 check() 或 report() 时才会触发重置。
| 重置间隔 | 行为 |
|---|---|
"day" | 从首次使用起每隔 24 小时重置 |
"week" | 每隔 7 天重置 |
"month" | 每个日历月重置 |
"year" | 每个日历年重置 |
直接读取权益
也可以从客户对象中读取权益:
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)
}
完整示例:带用量限制的 AI 聊天
// 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 })
}
评论
支持 Markdown