为什么 AI SaaS Demo 在第一笔订单后就崩掉
AI 产品 Demo 通常运行在最好的条件下:一个已登录的开发者、一个已知提示词、一个额度充足的 API Key,并且没有任何真实支付事件。于是 Demo 看起来很完整,但商业产品需要的每一次状态转换都没有被验证。
难点不是生成结果,而是在登录、结账、订阅续费、积分发放、AI 生成、退款、管理员干预和客服排查之间,保持账户状态一致。
Demo 通过,是因为它跳过了状态转换
典型的“AI SaaS Demo”有一个表单和一个模型响应,可能还有精致的定价页。它缺少的是产品背后的状态机:
- 访客变成账户。
- 账户变成客户。
- 客户获得可消费积分。
- 积分在外部工作开始前被扣除。
- 提供商成功或失败后被重新对账。
- 订阅续费不会重复发放积分。
- 客服能看到发生了什么,并安全修复。
每个转换都可能独立失败。如果它们只存在于 UI 状态里,第一笔真实支付就会暴露缺口。
认证是授权边界,不是一个按钮
NimBuild Starter 刻意采用 Firebase Google 登录和 HttpOnly 服务端会话 Cookie。Firebase 验证身份;PostgreSQL 保存角色、套餐、积分和封禁状态等产品字段。受保护路由依赖服务端会话,而不是客户端登录标记。
这个区分很重要,因为“浏览器说自己已登录”并不等于授权。生产应用必须回答:
- 会话是否有效?
- 本地用户是否存在?
- 用户是否被封禁?
- 路由是否仅管理员可访问?
- 刚刚修改的角色或访问状态是否需要让旧授权缓存失效?
没有这些问题,一个漂亮的 Google 按钮只是表演。
结账改变的不只是套餐标签
客户完成 Stripe Checkout 后,产品应该创建或更新多条关联记录:支付、订阅、用户套餐、积分和审计历史。如果只更新用户的套餐标签,UI 看起来正确,账务却在漂移。
Webhook 可能迟到、重复送达,或按不同于浏览器跳转的顺序到达。因此商业流程必须在发放任何权益前完成签名验证和幂等处理。
NimBuild 将 Stripe 作为支付提供商,同时保留显式本地账务。完成的 Checkout 会创建支付记录、更新订阅状态、通过积分账本路径发放积分,并发送确认邮件。这些记录必须一起成功,或一起失败。
积分需要审计轨迹
一个单独的 credits 整数很方便,但无法回答真实使用后的问题:
- 这些积分是购买、发放、调整、退款,还是过期?
- 哪个订阅周期发放了它们?
- 余额为什么减少?
- 提供商故障后,客服应该退多少?
NimBuild 使用三个关联概念:
user.credits:快速访问余额credit_balance_bucket:包含来源、数量、优先级和可选过期时间的可消费积分桶credit_ledger:不可变审计记录
每次变更都写入同一个数据库事务,兼顾速度和可解释性。
AI 调用引入第二个一致性问题
很多 AI 工作流必须在调用提供商前先扣积分。如果提供商随后失败,客户等于为无结果付费;如果先退款而提供商后来成功,业务又会损失收入。
实用模式是补偿:
- 事务内检查并扣除积分。
- 创建补偿对象。
- 调用 AI 提供商。
- 成功后标记为已结算。
- 失败后通过账本退款,并记录原因。
这就是 NimBuild 使用 ai_generation 和 ai_generation_refund 作为显式账本原因,而不是悄悄改回数字的原因。
管理后台是运营控制面
一旦客户付费,管理界面就不是装饰,而是团队安全回答“这个账户发生了什么”的途径。
NimBuild 包含用户管理、订阅可见性、积分调整和账本历史。最后那个页面尤其重要:只有能把一次调整和前后事件放在一起审查,这次调整才可信。
更好的验收方式
宣称 AI 产品生产可用之前,请运行这些场景:
- 登录和退出后刷新所有页面。
- 完成一次测试结账,检查支付、订阅、套餐、积分和账本记录。
- 重复发送同一个 webhook 事件。
- 在积分扣除后强制 AI 提供商失败。
- 让一批促销积分过期。
- 修改管理员角色,验证旧授权缓存已失效。
- 从管理后台审查每一次变更。
这些场景仍能产生一致记录时,你拥有的才不只是 AI Demo,而是商业 AI SaaS 的起点。
