VibeAny Docs
支付

Webhook、订阅与积分

了解持久履约、每月积分发放、幂等处理和手动退款流程。

VibeAny 将带签名的 Webhook 和数据库状态视为账单系统的唯一事实来源。结账返回页只负责向用户提供反馈。

持久 Webhook 流水线

所有支付服务商都把事件发送到同一路由:

POST /api/payments/webhook

当前服务商的验证器按以下顺序处理事件:

  1. 读取未经修改的请求体;
  2. 验证当前服务商的签名;
  3. 把服务商事件转换成应用层统一账单事件;
  4. 以服务商和事件 ID 组成幂等键;没有事件 ID 时改用载荷哈希,并通过该键从 webhook_inbox 取得处理权;
  5. 执行订阅或积分履约;
  6. 只有履约成功后,才把收件箱记录标记为已处理。

重复投递会返回成功,并带有 deduped: true。如果履约失败,系统会释放处理权,供服务商重试。签名无效时返回 HTTP 400;服务商配置不完整时返回 HTTP 503。

Webhook 验证同样遵循单服务商模式,因此未启用服务商发来的 Webhook 不会被路由到对应验证器。这正是修改 PAYMENTS_PROVIDER 不能替代迁移方案的原因。

订阅状态

订阅 Webhook 会保存:

  • 服务商以及外部订阅/客户 ID;
  • 方案和计费周期;
  • 生命周期状态和到期取消状态;
  • 当前周期边界;
  • 服务商事件时间和下一次计划积分发放时间。

同一用户的写入会串行执行,较旧的服务商事件不能覆盖较新的状态。数据库会强制每个用户只有一条订阅记录。

invoice 成功事件会记录支付生命周期,但不会直接发放积分。invoice payment failed 事件当前不会触发后续业务动作。催收、访问限制和客户通知都属于产品政策;实现这些策略前,不要对用户作出相关承诺。

独立调度每月订阅积分

订阅积分由应用自己的月度调度发放,而不是在每次 invoice Webhook 到达时发放。这样,年付订阅仍能按月获得积分配额。

Vercel 每天调用一次受保护的调度器:

GET /api/cron/subscription-credits
Authorization: Bearer <CRON_SECRET>

内置调度表达式是 5 0 * * *,即每天 UTC 00:05。只要启用了订阅商品,就必须配置强随机、仅供服务端使用的 CRON_SECRET

调度器会对每个 active、trialing 或已安排取消的订阅执行:

  1. 以订阅周期开始时间作为月度锚点;
  2. 创建幂等的 subscription_grant 积分批次;
  3. 在下一次月度发放或订阅周期结束时使该批次过期,以较早者为准;
  4. 通过 compare-and-set 原子更新下一次发放时间。

Webhook 流程也会运行到期积分发放用例,因此新激活订阅不必等待下一次每日 cron,就能收到已到期的首次发放。

积分账本行为

系统优先消耗最早且未过期的积分批次。积分账本同时维护缓存余额和 FIFO 投影,来源标识可防止同一笔支付或同一次计划发放重复入账。

内置积分包会增加:

SKU积分有效期
credits_small100履约后 365 天
credits_medium500履约后 365 天
credits_large1,500履约后 365 天

AI 图片生成每张消耗 10 积分。系统会在调用模型服务商前扣除积分;如果服务商生成失败,则返还积分。

Free 方案的商品文案不等于自动周期发放任务。如果产品承诺定期赠送免费积分,请新增并测试明确的调度任务或新用户发放逻辑。

手动退款政策

Starter 将退款资格评估与退款执行分开处理。

设置订阅积分使用比例的严格上限(不含边界):

PAYMENTS_SUBSCRIPTION_REFUND_MAX_USAGE_PERCENT=5

值为 5 时,使用比例低于 5% 才会得到全额退款建议;恰好 5% 不符合条件。默认政策将积分包标记为不可退款,但适用法律另有要求时除外。

运行只读评估:

npm run payments:refund:assess -- \
  --product subscription \
  --granted-credits 500 \
  --consumed-credits 24

评估积分包时,传入 --product credit-package

该命令不会读取数据库、发起退款、取消订阅或修改积分。执行退款前:

  1. 找到原始服务商、扣款、计费周期和准确的积分批次;
  2. 根据该批次计算消耗量,不要使用用户的总余额;
  3. 保留评估结果;
  4. 在原始支付服务商后台执行审核通过的全额退款;
  5. 如有需要,另行取消订阅;
  6. 手动核对剩余权益,并记录最终的服务商退款 ID。

服务商退款事件不会自动映射回积分批次。当前 Starter 中,自动冲销、支付争议和拒付都需要手动核对。

验证

npm run preflight
npm run test:unit
npm run test:credit-ledger:integration
npm run test:subscription:integration

PostgreSQL 集成测试套件需要可销毁的测试数据库。完整支付验收还要求完成服务商测试支付、公网 HTTPS Webhook 投递、重复事件重放,并从 Webhook 收件箱、subscriptionscredit_transactions 回读数据库状态。

本页目录