退款双执行核对报告(供运营核对)

核对时间:2026-07-06 · 数据源:生产只读库 · 关联 PR:#569

一句话结论

「送钱」只在历史 wallet.Refund 事故里发生过(多送 $25.96,已处置);本次 PR #569 修的 AcceptRefund 漏洞方向相反(会多扣用户),且经 4 角度核查生产从未实际发生(212 笔退款流水 1:1 无一双扣)。

① 两条退款路径别混淆 —— 钱的方向相反

wallet.Refund(订单退款)AcceptRefund(充值退款,本次 PR 修)
触发场景接码订单取消/失败,退回下单预扣用户申请退充值,管理员批准
对钱包的操作加钱(balance + amount)扣钱(balance − net_amount,撤销当初充值入账)
双执行后果用户白得双倍余额 = 送钱(我方资损)用户余额被扣两次 = 多扣用户(客诉/赔付风险),不是送钱
是否实际发生✅ 发生过:32 单 / 31 用户 / $25.96(2026-05-04 ~ 06-30)❌ 未发生(本次核查证实)
处置状态已清理(38 行 void 标记)+ 决策 B1 写销,PR #543/#544/#553 三道防线PR #569 上线前拦截(CAS + 唯一索引 000120)

② 「支付那边应该有幂等」的澄清

topup 退款根本不走支付网关 API——代码里 AcceptRefund 只做三件事:扣钱包余额、记 topup_refund 流水、状态 refunding→refunded。真正把法币/加密货币退给用户是运营在支付侧人工操作(Stripe 后台 / 链上转账),系统外闭环。代码中唯一的 gateway 引用是把 GatewayOrderID 抄进流水做对账,无任何网关调用。

所以:网关的幂等保护不在这条链路上。钱包余额本身就是「钱」(用户拿余额买号),钱包账本的幂等只能靠我们自己的 CAS + DB 唯一索引——这正是 PR #569 补的。另外由于支付侧打款是按「订单」人工执行(一个订单只打一次款),钱包双扣不会引起支付侧双打款。

③ AcceptRefund 路径核查(4 角度,全部通过)

核查角度SQL 口径结果
精确判重同一 topup_id 多条 topup_refund 流水✅ 0 组
账实 1:1212 个 refunded 终态订单 ↔ topup_refund 流水条数✅ 全部恰好 1 条(无 0 条、无 >1 条)
模糊判重同用户+同金额+1 小时窗口内多条流水✅ 命中 2 对,核实均为同用户两笔不同充值的正常退款(topup_id 互异)
订正漏网负数 debit 残留(migration 000119 前旧口径)✅ 0 条
结论:无任何用户被双扣,无需赔付,无需运营侧账务调整。

④ 历史 wallet.Refund 事故 38 行多退记录(运营核对清单)

这些是「送钱」方向的实际记录:每行是一条多余的重复退款入账(每单已保留最早一条合法行)。清理时已做 void 标记(order_id→NULL + metadata 留痕),账本未删行。业务决策 B1:金额小(合计 $25.96)+ 用户无过错,写销不追回(追回会让 5 个用户余额为负)。最后一笔发生于 2026-06-30 02:44,PR #543 上线后零新增。

流水 ID用户邮箱多退金额(cents)原订单发生时间(UTC)
665177b6…3806698969@qq.com980a40ed29…2026-05-04 02:43:29
5ab49106…3806698969@qq.com980a40ed29…2026-05-04 02:43:31
d19481fd…3806698969@qq.com980a40ed29…2026-05-04 02:43:31
33c84a5e…tbcmomoovo@gmail.com27287e087d…2026-05-06 02:44:52
d3c7289e…3202497951@qq.com20c72649fb…2026-05-07 01:51:12
d636582e…lijia_qian@126.com49867c96223…2026-05-07 02:44:47
0a1b9c49…tridvokoja@gmail.com7049c489c0…2026-05-09 02:42:53
f0ba98e5…tridvokoja@gmail.com7049c489c0…2026-05-09 02:42:55
0889f9a7…tridvokoja@gmail.com708407e336…2026-05-09 02:44:38
4e1e60a5…lanyy45@gmail.com87c745f160…2026-05-10 02:47:32
9eee9321…lanyy45@gmail.com87c745f160…2026-05-10 02:47:32
f13bb6e4…lanyy45@gmail.com87c745f160…2026-05-10 02:47:34
54be7332…lanyy45@gmail.com87c745f160…2026-05-10 02:47:34
ab3fdbb5…bladimirkanayo@gmail.com495360b0db…2026-05-12 02:47:09
40bf5ab8…pengqiecom@gmail.com20e2a959ee…2026-05-14 02:44:45
d34970a5…enomu661@gmail.com33329a39d6…2026-05-17 07:18:00
6ca56ac1…xuliang_hit@foxmail.com25a6942f1f…2026-05-20 02:43:03
006d4eba…1668599813@qq.com22921418ad…2026-05-22 02:47:24
6db56883…17612350159@163.com2599a26fbb…2026-05-25 02:43:34
109e3379…tjhldy6666@gmail.com13934d4202f…2026-05-26 02:43:40
f3124ca9…carolvancelzokry@gmail.com25816b7d7b…2026-05-27 02:46:58
c3954916…liangyulu781@gmail.com36d7b07d96…2026-05-27 10:03:17
6874a414…ww1792129648@gmail.com615ced334c…2026-05-28 12:30:55
8577f93f…xionglang0715@gmail.com2033091a49…2026-05-29 02:44:46
724ee899…312650221@qq.com52b5fa9ec2…2026-05-31 02:45:36
aac5577b…ejetry@gmail.com61f3f8545c…2026-06-03 09:16:07
929a2c94…starking0503@163.com61aa76407d…2026-06-04 02:43:18
90952b8b…linghan2001@gmail.com2062c91977…2026-06-04 02:43:35
f55709ea…2916952846@qq.com107a18f94a3…2026-06-06 10:45:22
7e48e702…youweismile.tan@gmail.com41e43cf604…2026-06-07 02:45:28
5db21bc7…water.wsg@gmail.com578b082158…2026-06-07 12:07:35
d7cf1717…guoguiping12138@gmail.com36b93f1273…2026-06-08 02:44:28
05b8c1c7…1974775213@qq.com9809f2dccd…2026-06-10 02:43:56
895191f1…ali.h.alsaqoor@gmail.com21fb0d170a…2026-06-12 05:06:31
f6cae52b…niekunhao@gmail.com592d5883bf…2026-06-13 02:46:08
2b3ff656…3099384178@qq.com20eb7fbb5a…2026-06-13 02:46:10
5067d52a…zqxsasuke@163.com50e39ab7f4…2026-06-14 02:50:01
0d753b54…wran0199@gmail.com6137d5c65a…2026-06-30 02:44:12

合计:38 行 / 32 单 / 31 用户 / 2596 cents($25.96)。完整含 UUID 的 CSV 可向工程索取;复核 SQL:SELECT * FROM transactions WHERE type='refund' AND metadata->>'voided_reason'='concurrent-double-refund-cleanup'

⑤ 给运营的行动项

  1. 无需任何赔付/追讨动作——历史 $25.96 已按 B1 写销,AcceptRefund 未发生双扣。
  2. PR #569 合并部署前,管理后台「批准退款」请避免快速双击/重复点击(当前线上代码仍有该漏洞窗口)。
  3. 如需复核任一笔退款:refunded 订单在 admin 后台可见对应唯一一条 topup_refund 流水,多于一条即异常,立即上报。