7.3 KiB
7.3 KiB
name, overview, todos
| name | overview | todos | |||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 修复XPay发货推送伪造漏洞 | 修复 S1:XPay 发货推送在发放权益前增加微信侧订单真实性反查(QueryOrder),杜绝伪造推送资损;S2:彻底删除服务端自动道具同步逻辑(避免每次启动自动同步),道具同步交由用户管理后台负责,并更新 README 标注。 |
|
用户需求
-
修复 S1(资损漏洞):XPay 发货推送无验签且缺少订单真实性反查,攻击者可伪造
xpay_goods_deliver_notify推送使订单 Pending→Paid 零成本获取权益。修复方式:发货前调用带 HMAC-SHA256 签名的微信服务端 APIquery_order反查订单真实支付状态,确认「已支付 + 金额一致 + 道具一致」后才流转发放,推送仅作触发信号。 -
S2 删除服务端自动同步逻辑:删除服务启动时自动执行的虚拟支付道具同步代码,避免每次运行服务端自动同步;道具同步改由用户自己的管理后台负责。同时在 README 标注运营约束:运营改价后必须通过管理后台同步道具(或重启服务),否则微信侧道具价格与本地不一致会导致发货金额校验拒绝(用户付款拿不到权益)或差价资损。
范围边界
- 仅修改虚拟支付链路:发货推送处理(
handleGoodsDeliver)增加真实性反查;普通微信支付 V3 回调链路(官方 SDK 验签)不涉及。 - 删除服务端道具同步链路(main 触发、service 同步逻辑、wechat 道具管理函数、同步专用 DAO),保留商品表
product_id/product_status字段与GoodsProduct*状态常量(下单校验依赖)。 - 退款推送伪造风险与 M1(RefundStatus 空串误判)不在本次范围。
技术方案
S1:发货推送真实性反查
策略:在 handleGoodsDeliver 中,幂等锁内、全部本地校验(pay_channel / product_id / amount / env)通过之后、条件流转(Pending→Paid)之前,插入微信侧订单反查。反查失败或结果不符时返回非 nil 错误码,使 XPayNotify 回 ErrCode:1,微信按推送重试策略重试(fail-closed)。
关键设计:
- 支付状态白名单判断(
pkg/wechat层集中管理):
- 新增「已支付」状态常量与
IsOrderPaidStatus(status string) bool白名单判断函数;未知状态/空串按未支付处理并告警。 virtualpay_test.go补充单测:已支付 / 未支付 / 未知状态 / 空串。
- 反查校验函数(service 层):
- 新增
verifyXPayOrderPaid(ctx, order, deliver) *errcode.Error,校验顺序:deliver.OpenID为空 → 拒绝wechat.QueryOrder(ctx, deliver.OpenID, deliver.OutTradeNo, "")接口错误 → 拒绝!wechat.IsOrderPaidStatus(info.Status)→ 拒绝info.PayFee != order.PayAmount→ 拒绝info.ProductID != order.XPayProductID→ 拒绝- 全部通过 → nil
- 插入位置:
handleGoodsDeliver中 env 校验之后、OrderUpdateStatusByOrderNo(Pending→Paid)之前。
- 错误码:
pkg/errcode/order_code.go新增OrderRealityCheckFailed = NewError(70009, "订单真实性校验失败")。
- 安全性与一致性:
- 反查走
callXPay的 HMAC 签名(激活环境 AppKey),攻击者无法伪造;伪造推送在微信侧无支付记录,反查必然失败 → 拒绝发货。 - 真实支付后反查必能查到已支付,微信推送重试保证最终发货。
- 反查使用激活环境,与订单
XPayEnv快照经本地校验一致。 - 成功路径沿用现有
finalizeVirtualPaidOrder(callback_raw 持久化原始推送),审计闭环不变。 - 审计日志携带
out_trade_no/ query 返回状态 / 金额,失败不落库(最小改动)。
性能:反查仅在幂等锁内执行一次(每笔发货推送一次 HTTP 调用,10s 超时),无额外热点。沙箱环境 query_order 行为需联调确认(代码注释保持「以官方文档为准」风格)。
S2:删除服务端自动道具同步逻辑
删除内容:
main.go:service.StartVirtualGoodsSync()调用及注释internal/service/virtualpay.go:StartVirtualGoodsSync/SyncVirtualGoods/syncOneGoods/pollGoodsTask及 4 个同步常量pkg/wechat/virtualpay.go:StartUploadGoods/QueryUploadGoods/StartPublishGoods/QueryPublishGoods、XPayGoodsItem/XPayTaskResult、4 个道具 path 常量internal/mysqldao/goods.go:GoodsListVirtualSync/GoodsUpdateByID(删除后无引用)
保留内容:
- 商品表
product_id/product_status字段(管理后台回填,下单校验依赖) GoodsProductUnpublished/Publishing/Published/PublishFail常量(下单校验与状态语义一致)setupVirtualPayClient/GetVirtualPayEnv/QueryOrder(下单、反查、回调仍需客户端)GoodsGetByCode/GoodsGetByID/GoodsList(商品模块与下单使用)
README 标注:
- 道具同步由管理后台负责,服务端不再自动同步
- 运营改价后必须通过管理后台同步道具(或重启服务),否则微信侧价格与本地不一致会导致发货金额校验拒绝或差价资损
- 删除后台任务表中「虚拟支付道具同步」行,同步更新交易与权益流程相关描述
文件修改清单
zt-closet-backend/
├── pkg/wechat/virtualpay.go # [MODIFY] 新增已支付状态常量与 IsOrderPaidStatus;删除道具管理函数/类型/path 常量
├── pkg/wechat/virtualpay_test.go # [MODIFY] 补 IsOrderPaidStatus 单测
├── pkg/errcode/order_code.go # [MODIFY] 新增 70009 错误码
├── internal/service/virtualpay.go # [MODIFY] 插入 verifyXPayOrderPaid 反查;删除同步逻辑与常量
├── internal/mysqldao/goods.go # [MODIFY] 删除 GoodsListVirtualSync / GoodsUpdateByID
├── main.go # [MODIFY] 删除 StartVirtualGoodsSync 调用
├── README.md # [MODIFY] 标注道具同步运营约束,删除后台任务表同步行
└── docs/代码审查报告-二次check.md # [MODIFY] S1 标「已修复」,S2 标「已删除自动同步,由管理后台负责」
实现注意事项
- 删除同步逻辑后需确认
internal/service/virtualpay.go的time包仍被锁 TTL 使用、wechat包仍被 BuildPaymentSign 使用,避免编译错误。 - 删除
XPayGoodsItem后需确认无其他文件引用(搜索已确认仅同步逻辑引用)。 - 反查状态白名单的枚举值以微信官方
query_order返回状态为准,实现时集中定义常量并注释「联调校正」。