Balance 服务

Hook 中直接扣费

推荐在业务 hook 中直接调用 balance service,而不是把价格规则写死在服务配置里。

这套服务最推荐的用法,不是声明式 pricing。

推荐模式是:

  1. 在 City 里注册 balanceService()
  2. 在业务 hook 里直接调用它
  3. 调用前 require()
  4. 调用成功后 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)

继续阅读