积分消费
积分系统用于 积分模式 和 配额订阅模式。如果你使用的是无限订阅或买断制,可以跳过这一页。
三种积分类型
系统内部维护三种积分,消费时按优先级依次扣减:
| 类型 | 来源 | 是否过期 | 消费优先级 |
|---|---|---|---|
subscription_credits | 配额订阅的月度配额 | 每月重置 | 最高 |
bonus_credits | 赠送/活动积分 | 有过期时间 | 中 |
purchased_credits | 用户购买的积分包 | 永不过期 | 最低 |
消费顺序: 优先扣月度配额 → 再扣赠送积分 → 最后扣购买积分。
这个优先级对用户最友好——先用快要过期的,购买的积分留到最后。
useCredits Hook
import { useCredits } from '@/hooks/useCredits'
function CreditsDisplay() {
const { balance, loading, refreshBalance, useCredit } = useCredits()
if (loading) return <div>加载中...</div>
return (
<div>
<p>可用积分: {balance.availableCredits}</p>
<p>已购积分: {balance.purchasedCredits}</p>
<p>已使用: {balance.usedCredits}</p>
</div>
)
}balance 对象
{
totalCredits: number, // 累计获得的积分
usedCredits: number, // 已使用的积分
availableCredits: number, // 当前可用积分
purchasedCredits: number, // 已购积分余额
}useCredit —— 消费积分
const { useCredit } = useCredits()
async function handleUse() {
const result = await useCredit(
'image_generation', // 服务类型
'生成了一张图片', // 描述(可选)
'img-456' // 关联 ID(可选)
)
if (result.success) {
console.log('剩余:', result.remainingCredits)
} else {
// 积分不足
console.log(result.message)
}
}useCredit 内部调用的是 /api/service/use,和 useAccess 的 useService 走的是同一个后端接口。两者都可以用,区别是:
useService(来自useAccess)—— 通用消费,会自动更新访问状态useCredit(来自useCredits)—— 侧重积分场景,会自动更新积分余额显示
扣减金额由服务端
config/payment.ts中的SERVICE_COSTS决定,前端无法篡改。详见访问控制页面。
刷新余额
购买成功后或需要手动刷新积分余额时:
const { refreshBalance, addCredits } = useCredits()
// 方式 1:从服务器重新拉取(精确)
await refreshBalance()
// 方式 2:本地立即增加(快速更新 UI)
addCredits(50) // 本地余额 +50服务端积分操作
在 API Route 或服务端逻辑中,可以直接使用 lib/payment/credits.ts 提供的函数:
import {
addCreditsToUser, // 添加购买积分
addBonusCredits, // 添加赠送积分(带过期时间)
getTotalAvailableCredits, // 获取总可用积分
initSubscriptionCredits, // 初始化订阅配额积分
clearSubscriptionCredits, // 清除订阅配额积分(取消订阅时)
} from '@/lib/payment/credits'
// 给用户添加 50 个购买积分
await addCreditsToUser(userId, 50)
// 给用户赠送 10 个积分,30 天后过期
import { getBonusExpiryDate } from '@/config/payment'
await addBonusCredits(userId, 10, getBonusExpiryDate(30))
// 查询总可用积分
const total = await getTotalAvailableCredits(userId)赠送积分
赠送积分(bonus_credits)按批次管理,每批有独立的过期时间。适合用于注册奖励、活动赠送、订阅附赠等场景。
Hero 区的"赠送积分"按钮仅用于演示 / 交付预览。线上 Demo 不暴露这个入口。它使用活动领取式接口(
/api/credits/claim-bonus):前端只传campaignKey,赠送数量、过期时间和防重复领取规则都由后端配置决定。生产环境中,赠送积分应由明确的业务规则触发,例如一次性领取活动、注册奖励、支付 webhook、管理后台或定时任务。
配置可领取活动:
在 config/payment.ts 中配置用户可领取的赠送活动:
export const BONUS_CAMPAIGNS = {
hero_welcome_bonus: {
amount: 10,
expiresInDays: 7,
oncePerUser: true,
enabled: true,
},
}领取接口不信任前端传来的金额。前端只传活动 key:
await grantBonus('hero_welcome_bonus')后端会把领取记录写入 credit_expiration.source,格式是 claim:<campaignKey>。数据库会通过 (user_id, app_id, source) 唯一索引防止同一用户重复领取同一个活动,同时不影响支付附赠、后台赠送等其他赠送来源。
配置过期天数:
在 config/payment.ts 中设置默认过期天数:
export const BONUS_CREDITS_EXPIRY_DAYS = 30 // 默认 30 天系统自动赠送的场景:
- 订阅激活时,如果商品 metadata 中配置了
bonus_credits,会自动赠送 - 积分包购买时,如果 metadata 中配置了
bonus_credits,会随购买附赠
手动赠送(前端页面):
const { grantBonus } = useAccess()
// 领取一个后端配置好的赠送活动
const res = await grantBonus('hero_welcome_bonus')
// res.success / res.message手动赠送(后端 API route / server action):
import { addBonusCredits } from '@/lib/payment/credits'
import { getBonusExpiryDate } from '@/config/payment'
// 赠送 20 个积分,30 天后过期
await addBonusCredits(userId, 20, getBonusExpiryDate(30))
// 赠送 50 个积分,7 天后过期,标记来源为活动
await addBonusCredits(userId, 50, getBonusExpiryDate(7), 'campaign')过期机制: 数据库 cron 每 10 分钟自动扫描并标记过期批次,触发器自动重算 user_credits 余额。应用层在用户访问时也会兜底检查。
原子扣减
所有积分扣减操作都通过数据库 RPC consume_quota 原子执行,不存在并发扣超的问题。RPC 内部按优先级(subscription_credits → bonus_credits → purchased_credits)依次扣减,并自动记录消费日志。
文档首页
回到完整实施目录。
价格方案
查看订阅、积分和终身买断方案。
博客
阅读更多 SaaS 支付和增长经验。