🚨 2026-06-12 转化率暴跌根因调查
注册→首充率 6/10 44.5% → 6/12 36.5%(-8pp 连降两天)· 充值发起→支付成功率 6/11 60.5% → 6/12 50.6%(-10pp)
🎯 高置信度根因:v3.3.3 (6/10) 部署的 API contract 改名 + 浏览器旧 JS 缓存
核心 KPI 对比
6/12 attempt→paid
62.2%
-10pp vs 6/11 (72.2%)
6/12 nihaopay paid_pct
50.6%
-10pp vs 6/11 (60.5%)
6/12 平均存活时间
15.2 min
+3 min vs 历史 12 min
6/12 注册量
326
-22% vs 6/11 (419)
📅 时间线(确认根因部署窗口)
2026-06-09 09:47 SH
v3.3.0 release — fix(referral/rental) 钱包改绑邮件
无相关性 ✓
2026-06-10 02:04 UTC (10:04 SH)
PR #468 合并 — refactor(openapi): 路由改挂根 /v1/* 免 CF Origin Rule + 启用子域硬绑
真正的炸弹:实际改动远超 PR 标题,同时把 topup 后端 API contract 整个改名:
• gatewaySlug → channelId(请求字段,binding:"required")
• gatewayPaymentUrl → paymentUrl(响应字段)
• gatewayCheckoutForm → checkoutForm(响应字段)
• 删除 gatewayName / gatewayOrderId / paymentMethod / userId / derivationIndex 等字段
2026-06-10 08:56 SH
v3.3.3 release — 部署 PR #468 + PR #481 到 production
含 backwards-incompatible API rename,但 changes/ 文档自己写"旧客户端需要切换为 channelId"——没做 dual-read 兼容
2026-06-10 — 全天数据
paid_pct 58.1% — 表面看不出问题
部署当天大多用户首次访问 dashboard 拿到的是新 JS,问题不显著
2026-06-11 全天
paid_pct 60.5% — 暂时回升,但 attempt_pct 开始跌(55.5%)
用户看到 dashboard 不点充值的比例增加,疑似前端组件 partial broken
2026-06-12 全天
paid_pct 50.6% / attempt→paid 62.2% — 全面暴跌
第三天浏览器缓存广泛失效,更多用户碰到 broken UI;CF/Vercel CDN 边缘缓存渐进失效
1. 数据证据链(按因果链排序)
1.1 转化率连续 3 天下降,6/12 为低点
| 日期 | 注册 | attempt_pct | first_paid_pct | attempt→paid |
| 6/08 | 518 | 60-62% | 40.7% | 73% |
| 6/09 | 512 | 60-62% | 46.1% | 75% |
| 6/10 部署 | 472 | 61.2% | 43.4% | 71.0% |
| 6/11 | 419 | 55.5% | 40.0% | 72.2% |
| 6/12 | 326 | 60.3% | 37.5% | 62.2% |
1.2 主力 gateway nihaopay 同步下滑 -10pp
| 日期 | nihaopay 单量 | paid | paid_pct |
| 6/10 | 391 | 227 | 58.1% |
| 6/11 | 306 | 185 | 60.5% |
| 6/12 | 255 | 129 | 50.6% |
1.3 stripe 同步下跌排除"nihaopay 渠道单点故障"
stripe 6/12 paid_pct 37.5% vs 6/11 46.2%(-9pp)。两条独立 gateway 同步下滑,不是单 gateway 故障,根因在前端。
1.4 用户停留时间 +3 min — 找不到支付入口的特征
| 日期 | nihaopay 平均 lifetime (min) | 已付款平均付款时间 (min) |
| 6/10 | 12.4 | 0.6 |
| 6/11 | 12.1 | 0.6 |
| 6/12 | 15.2 (+3 min) | 0.7 |
付款的人付款时间没变(0.6-0.7 min),但没付款的人停留更久(+3 min)。完全符合"用户看到了页面但找不到支付入口"模式。
1.5 后端没故障 — 排除 API/支付通道问题
- SigNoz 6/12 tuna-api 错误日志 top 5:jwt verification failed (4) / audit batcher flush (2) / nihaopay callback (1) — 极少
- 所有 6/12 topup 100% 拿到
gateway_checkout_form(nihaopay alipay 表单数据)
- topups 表 6/12 仍有 255 单 nihaopay 创建记录 — API 调用通
- 没有大额未付款用户污染(与 5/16 / 5/21 那种 vincentforwin / xiangqian 大额 expire 模式不同)
2. 🎯 根因:PR #468 backwards-incompatible API rename
PR #468 在 v3.3.3 (6/10 SH 08:56) 部署,做了系统级 API contract 改名,且没做 dual-read 兼容:
2.1 后端 createTopupRequest 强制要求新字段
- GatewaySlug string `json:"gatewaySlug" binding:"required"`
+ ChannelID string `json:"channelId" binding:"required"`
2.2 后端 response 字段全部改名
- "gatewayName": topup.GatewayName,
- "gatewayOrderId": topup.GatewayOrderID,
- "gatewayPaymentUrl": topup.GatewayPaymentURL,
- "gatewayCheckoutForm": topup.GatewayCheckoutForm,
- "paymentMethod": topup.PaymentMethod,
- "userId": topup.UserID,
+ "channelId": ChannelIDForGatewaySlug(topup.GatewayName),
+ "paymentUrl": topup.GatewayPaymentURL,
+ "checkoutForm": topup.GatewayCheckoutForm,
2.3 前端 openTopupHostedPayment 改读新字段
- if (topup.gatewayCheckoutForm) submitHostedPaymentForm(topup.gatewayCheckoutForm)
- if (topup.gatewayPaymentUrl) window.location.assign(topup.gatewayPaymentUrl)
+ if (topup.checkoutForm) submitHostedPaymentForm(topup.checkoutForm)
+ if (topup.paymentUrl) window.location.assign(topup.paymentUrl)
throw new ApiError('Missing hosted payment target')
2.4 用户侧失败模式(高置信度推断)
| 用户场景 | 后果 |
| 浏览器缓存了 6/10 之前的旧 JS 的 active tab 用户 | 旧 JS 发 gatewaySlug 请求被新后端 400 拒;或拿到 response 后读 gatewayPaymentUrl = undefined,跳转失败 → 看不到支付页 → expired |
| CF/Vercel CDN 边缘节点未刷新到新版 chunk | 新用户访问拿到旧 hash 的 JS,同上结局 |
| Service Worker / Next.js precache 持有旧版 | PWA-style 缓存 → 用户离线时拿到旧版,进入同样陷阱 |
PR #468 的 changes/2026-06-05-topup-payment-channel-redaction.md 文档自己写明"旧客户端需要切换为 channelId"——已知风险但没做兼容,没有 dual-read pattern 双周窗口。
3. 不能 100% 确定的 caveats
- 6/10 当天 paid_pct 还是 58.1%(vs 6/12 50.6%),如果纯粹是浏览器缓存问题,6/10 应该当天就跌。可能 cache invalidation 是渐进的,但也可能有其他叠加因素。
- 6/12 注册量 326 比 6/11 419 跌 22%。流量来源缩水可能也叠加影响 attempt_pct,但不能解释 attempt→paid 跌 -10pp。
- 无法直接看 CF/Vercel 边缘节点的 JS chunk hash 分布证实"旧版用户占比"——需运维介入。
4. 🚨 紧急处置建议
P0-A · 立即 hotfix:后端做 dual-read 兼容(30 分钟修复)
在 backend/internal/api/topup/handler.go 的 createTopupRequest 同时接 channelId 和 gatewaySlug:
type createTopupRequest struct {
...
ChannelID string `json:"channelId"`
GatewaySlug string `json:"gatewaySlug"` // 兼容旧客户端,至少保留 14 天
}
// 在 handler 解析:
channelID := strings.TrimSpace(req.ChannelID)
if channelID == "" { channelID = topupsvc.ChannelIDForGatewaySlug(req.GatewaySlug) }
if channelID == "" { return 400 }
response 端同步 双写新旧字段名(gatewayPaymentUrl + paymentUrl 同时存在),至少保留 14 天兼容窗口。
预期效果:4-6 小时内 nihaopay paid_pct 应回弹到 58%+ 历史水位。
P0-B · 联系运维:检查 CF/Vercel 边缘缓存 + 强制 purge
- 检查 Vercel
tuna-web-staging + 生产项目的 CDN 缓存策略 — 静态 chunk 缓存时长是否过长
- 触发一次手动 purge,强制刷掉旧版 JS chunk
- 看是否启用了 service-worker / PWA precache — 如有,需配 cache invalidation 策略
P1 · 复盘 + 流程改进
- 所有 backwards-incompatible API field rename 必须走 dual-read 双周窗口:先双写双读 14 天,再删旧字段
- PR description 里"风险"章节如果提到 "旧客户端需要切换",CI 自动 block merge 要求加 dual-read 兼容
- 这种"标题做 A 实际改 B"的 PR(PR #468 标题是 OpenAPI 路由改名,实际改了所有 topup API 字段)需要审视 PR scope 拆分规范
P1 · 监控加固
- 加 metric: nihaopay attempt→paid 漏斗每日 paid_pct(当前只能事后 SQL 查),定义阈值 < 50% 立即飞书告警
- 加 frontend RUM (Real User Monitoring) — 用户点"立即充值"后是否成功跳转的客户端事件
- SigNoz dashboard 加"创建 topup → 拿到 checkoutForm → 跳转 alipay 用时"漏斗,看哪一步丢人
5. 验证 hotfix 效果的 KPI
| 指标 | 当前(6/12 已沦陷) | 部署 P0-A 后 4h 预期 |
| nihaopay paid_pct | 50.6% | ≥ 56% |
| 整体 attempt→paid | 62.2% | ≥ 70% |
| 注册→首充率 | 37.5% | ≥ 43% |
| 平均 topup lifetime | 15.2 min | ≤ 13 min |