Balance 服务
Hook 中直接扣费
推荐在业务 hook 中直接调用 balance service,而不是把价格规则写死在服务配置里。
这套服务最推荐的用法,不是声明式 pricing。
推荐模式是:
- 在 City 里注册
balanceService() - 在业务
hook里直接调用它 - 调用前
require() - 调用成功后
sub()
为什么这样更自由
因为真正的价格规则通常属于业务层:
- 哪些 action 要收费
- 某个 model 要不要更贵
- 某个 town 要不要免费
- 某类用户要不要打折
这些都更适合写在你自己的 hook 里。
推荐写法
const balance = balanceService({
init: 100,
unit: "credits",
});
base.use(balance);
const ai = new AIService();
ai.hook.before(async (ctx) => {
if (ctx.identity?.kind !== "user") return;
if (ctx.action?.id !== "chat/completions") return;
await balance.require(ctx.user!.user_id, 10);
ctx.locals.balance_amount = 10;
});
ai.hook.after(async (ctx) => {
if (ctx.identity?.kind !== "user") return;
if (ctx.action?.id !== "chat/completions") return;
if (ctx.output instanceof Response && ctx.output.status >= 400) return;
const amount = Number(ctx.locals.balance_amount ?? 0);
if (amount <= 0) return;
await balance.sub(ctx.user!.user_id, amount, {
note: "agent chat",
meta: {
town_id: ctx.town?.town_id,
model_id: ctx.variant?.id,
},
});
});这段代码表达了什么
- 调用前先确认余额足够
- 调用失败不扣
- 调用成功才真正扣费
- town 只写进流水 metadata,不参与账户主键
最小 API
这一页最相关的方法是:
balance.read(user_id)balance.require(user_id, amount)balance.add(user_id, amount, extra)balance.sub(user_id, amount, extra)