fix(payment): 支付轮询链路收口——查单节流 + 前端轮询重写 #7

Merged
Blizzard merged 2 commits from feat/site into main 2026-07-20 04:55:30 +00:00

2 Commits

Author SHA1 Message Date
Blizzard 2c9ecc833e feat(admin): 积分包支持就地生成付款码,验证支付链路
管理端「支付 · 配置 → 积分包」每行加「生成付款码」:下单 → 二维码 → 盯到终态,
用来确认「渠道配置 → 下单 → 扫码 → 回调/查单 → 幂等入账 → 积分到账」在当前
环境真的通。

刻意走用户面的 /api/v1/billing/*,不做 admin 专用旁路 —— 造条测试专线的话,
验过了也不能说明线上用户那条路通。租户由服务端按登录用户解析,无需带租户头。

代价是这会真实扣款,所以:
- 弹窗顶部显著提示是真单、会真扣钱,并指向「订单与对账」的退款入口;
- 已下架的包按钮禁用(服务端本就会拒,别让人白扫)。

弹窗展示订单号/状态/轮询次数/到账积分,出问题时能直接判断卡在哪一环,
比只说一句"成功/失败"有用。轮询策略与 sundynix-web 支付弹窗保持一致:
递归 setTimeout、切后台暂停、失败指数退避、倒计时以服务端 expires_at 为准。

依赖:admin 新增 qrcode(懒加载进 PaymentConfigPage 分块,不进主包)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 12:52:24 +08:00
Blizzard 70b0419387 fix(payment): 支付轮询链路收口——查单节流 + 前端轮询重写
后端:
- 主动查单加最小间隔(5s)。此前 BillingOrderStatus 见 pending 就直连微信查单,
  而前端 2.5s 轮一次 —— 单笔订单在 30min TTL 内可打出约 720 次渠道调用,
  微信侧有频控,并发用户一多先被限流的是我们自己。回调才是入账主路径,
  查单只是兜底,节流最坏只让在场用户多等 5s。
- 标记随订单落终态清除,并在补偿定时器每轮 prune 掉超 TTL 的残留
  (用户扫码前就关弹窗的订单不会再被轮询,其标记无人回收)。
- 下单响应补 expires_at:二维码有效期由服务端 orderTTL 说了算,
  前端硬编码一份迟早漂移。

前端(sundynix-web):
- orderStatus 不再丢掉 warn。服务端在「已付但金额与订单不符」时不入账、
  挂起人工核对,订单一直停在 pending —— 丢掉 warn 用户就会一直等一个
  永远不会来的结果。现在弹窗显式告警并给出订单号。
- setInterval → 递归 setTimeout:请求慢于间隔时 setInterval 会把请求摞起来,
  对「每次可能触发渠道查单」的端点尤其糟。
- 标签页切走时暂停轮询(用户扫完码要切去微信 App),切回前台立刻补查一次。
- 连续失败指数退避至 15s 封顶,断网时不再定频猛打。
- 二维码倒计时 + 失效遮罩,过期不再让用户扫一个必然失败的码。
- 明说「关掉也不会丢钱,到账会自动补上」——掉单补偿定时器本就兜底,
  但此前 UI 没讲,用户只能守着弹窗。
- 关闭弹窗一律刷新余额:用户可能在关闭前一刻付款、状态刚落地。

测试:新增 handler 包首个测试,钉住节流与标记回收行为(-race 通过)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 12:46:40 +08:00