AI 扣完积分后生成失败,应该怎么退款?
AI 生成请求常常必须先消耗积分,再开始外部工作。这带来一个尴尬窗口:用户已经付费,但提供商还没有返回可用结果。
NimBuild 用显式补偿对象处理这个窗口。
补偿模式
文档中的流程是:
const compensation = createCreditCompensation({
userId,
amount: creditsNeeded,
reason: "credit_adjustment_refund",
referenceId,
});
try {
await doExternalWork();
compensation.settle();
} catch (error) {
await compensation.compensate();
throw error;
}
顺序是有意的:
- 检查是否可负担。
- 在数据库事务中扣除积分。
- 准备补偿。
- 调用提供商。
- 外部工作成功后结算。
- 失败后通过账本退款。
补偿路径不会原地改余额,而是生成可审计退款事件。
文档示例使用通用的 credit_adjustment_refund 演示这个 helper。AI 工具工作流还定义了更具体的 ai_generation_refund,用于把 AI 提供商补偿和其他积分调整区分开。
什么算失败?
对生产 AI 工作流来说,失败不只是 HTTP 500:
- 提供商超时
- 认证或额度错误
- 无效提供商响应
- 缺少必需输出字段
- 内容不安全或不可用
- 存储失败导致必需资产无法读取
上线前要明确哪些失败可退款。NimBuild 的补偿模式适用于“积分已扣除、外部工作随后失败”的操作。
账本原因为什么重要
NimBuild 用 ai_generation 表示消耗,用 ai_generation_refund 表示提供商失败补偿。这些名称让客服能区分:
- 正常使用扣费
- 失败生成退款
- 人工客服调整
- 通用产品退款
如果缺少这些区分,每次余额恢复看起来都一样,后续做风控、单位经济模型和提供商可靠性分析都会更困难。
生成历史是承诺的一部分
AI 工作区会记录生成历史。用户应能查看自己请求了什么、结果是什么;失败时能看到错误,成功时能找回工作流结果。
历史也提供运营上下文。偶尔一次超时,和影响大量客户的区域性提供商故障,应该有不同响应。
避免两个诱人的捷径
捷径一:提供商成功后再扣积分
这避免了退款,但会让用户在没有足够余额时也能无限启动外部工作,也会在两个请求并发读取同一余额时产生竞态。
捷径二:捕获所有错误后悄悄恢复余额
这看起来用户体验友好,却丢失了账务上下文。之后没人知道退款来自提供商失败、客服政策,还是系统 Bug。
NimBuild 的方式更严格:原子扣除,然后显式补偿。
测试你自己的工作流
- 余额不足的请求是否会启动外部工作?
- 两个并发请求是否会被阻止重复消费同一积分?
- 重试逻辑执行两次时,补偿是否幂等?
- 超时是否准确退回扣除金额?
- 失败操作是否出现在历史里?
- 管理员账本能否看到原始扣费和退款?
- 重试与退款是否使用同一个稳定引用?
失败后的用户体验
告诉用户发生了什么,以及恢复了什么:
- 生成失败。
- 已扣除积分已退回。
- 可以重试。
- 如果再次失败,可以联系支持排查。
不要隐藏失败,同时让积分消失。付费 AI 产品的信任来自可见的对账。
补偿流把提供商失败从余额 Bug 变成可审计的客户恢复流程。
