metered-usage-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Metered Usage

计量使用量

Gate access and track consumption with
check()
and
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

参数

ParameterRequiredDescription
customerId
YesYour app's user ID
featureId
YesFeature to check (type-safe)
required
NoCheck if at least this many units remain
参数是否必填描述
customerId
你的应用用户ID
featureId
要检查的功能(类型安全)
required
检查是否至少剩余指定数量的可用单元

Return Value

返回值

typescript
interface CheckResult {
  allowed: boolean
  balance: {
    limit: number
    remaining: number
    resetAt: Date | null
    unlimited: boolean
  } | null
}
For boolean features:
allowed
is
true
/
false
,
balance
is
null
.
For metered features:
allowed
is
true
if
remaining > 0
(or
remaining >= required
),
balance
contains usage details.
typescript
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
包含使用量详情。

Pre-checking Availability

预检查可用性

Use
required
to check if enough units remain before a batch operation:
typescript
const { allowed } = await paykit.check({
  customerId: "user_123",
  featureId: "api_calls",
  required: 50,
})

if (!allowed) {
  throw new Error("Not enough API calls remaining")
}

使用
required
参数在批量操作前检查是否有足够的可用单元:
typescript
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

参数

ParameterRequiredDescription
customerId
YesYour app's user ID
featureId
YesFeature to decrement (type-safe)
amount
NoUnits consumed. Default:
1
参数是否必填描述
customerId
你的应用用户ID
featureId
要扣除使用量的功能(类型安全)
amount
消耗的单元数,默认值:
1

Return Value

返回值

typescript
interface ReportResult {
  success: boolean
  balance: {
    limit: number
    remaining: number
    resetAt: Date | null
    unlimited: boolean
  } | null
}
success
is
false
if the customer doesn't have enough remaining balance.

typescript
interface ReportResult {
  success: boolean
  balance: {
    limit: number
    remaining: number
    resetAt: Date | null
    unlimited: boolean
  } | null
}
如果客户剩余余额不足,
success
false

Usage 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
check()
and use
report()
directly. It fails if balance is insufficient:
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 call

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

typescript
const { balance } = await paykit.check({
  customerId: userId,
  featureId: "messages",
})

// balance.remaining  // 剩余单元数
// balance.limit      // 总允许量
// balance.resetAt    // 使用量重置时间
// balance.unlimited  // 无限制则为true

Entitlement Resets

权限重置

Metered entitlements reset lazily. The reset doesn't happen on a cron. It triggers on the next
check()
or
report()
call after the reset time has passed.
Reset IntervalBehavior
"day"
Resets every 24 hours from first usage
"week"
Resets every 7 days
"month"
Resets every calendar month
"year"
Resets every calendar year

计量型权限采用延迟重置机制。重置不会通过定时任务触发,而是在重置时间过后的下一次
check()
report()
调用时触发。
重置间隔行为
"day"
首次使用后每24小时重置一次
"week"
每7天重置一次
"month"
每个日历月重置一次
"year"
每个日历年重置一次

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