--- name: 修复支付业务逻辑问题 overview: 修复二次审查发现的支付业务问题:2 个资损级缺陷(虚拟支付订单取消漏洞、道具价格不同步)+ 5 个逻辑缺陷(iOS 最低价校验、退款状态判断、PaidTime 兜底等)+ 2 处文档同步,全部在现有支付代码上做定点修复。 todos: - id: fix-p0-1 content: 修复取消漏洞:新增 70007 错误码、Cancel 拒绝虚拟支付订单、handleGoodsDeliver 补 Cancelled 补发货分支 status: completed - id: fix-p0-2 content: 修复道具价格不同步:GoodsListVirtualSync 改为全量查询上架商品并更新同步注释与运营约定 status: completed - id: fix-p1 content: 修复 P1 缺陷:新增 70008 与 iOS 最低金额校验、退款推送状态判断、PaidTime 兜底、策略注释 status: completed - id: sync-p2 content: 同步 P2:OrderResponse 加 pay_channel 字段与映射,接口文档补退款路径、错误码与道具刷新说明 status: completed - id: verify content: 执行 go build、go vet、go test 与 read_lints 全量验证 status: completed dependencies: - fix-p0-1 - fix-p0-2 - fix-p1 - sync-p2 --- ## 产品概述 对支付业务逻辑二次审查发现的 2 个资损级问题、5 个逻辑缺陷与 2 个文档问题做定点修复,不改变支付链路整体架构与权益发放回退幂等机制。 ## 核心修复内容 ### P0 资损级 - P0-1 虚拟支付订单取消漏洞:虚拟支付订单(pay_channel=2)不可再取消(新增 70007 错误码);发货推送补「已取消订单补发货」防御分支,杜绝「用户取消后继续支付、钱已扣但权益永不发放」。 - P0-2 道具价格不同步:道具同步改为全量刷新全部上架商品(含已发布),每次启动重新上传发布道具信息与价格,保证微信侧道具价格与本地改价一致,避免金额校验失败导致付款不发货。 ### P1 逻辑缺陷 - iOS 下单校验最低 1 元(100 分),低于此金额拒绝(70008)。 - 退款推送仅「成功」状态才回退权益,防中间状态误回退。 - 发货推送 PaidTime 缺失或异常时兜底为当前时间。 - iOS 退款问询保持默认建议退款,补充策略注释。 ### P2 文档与提示 - 订单响应增加 pay_channel 字段(1=微信支付 2=虚拟支付)。 - 接口文档补充虚拟支付退款操作路径(虚拟支付后台 Midas)、道具价格刷新运营约定、新错误码说明。 ## 技术方案 ### 修复策略 定点修复,复用现有架构与幂等机制:不动 `benefit_grant_tasks` 发放凭证、唯一索引幂等、`settleRefundSuccess`/`revokeOrderBenefits` 回退链路;不新增表结构与迁移 SQL。 ### 关键改动 **1. 错误码(pkg/errcode/order_code.go)** - 新增 `VirtualOrderCancelNotAllowed = NewError(70007, "虚拟支付订单不支持取消")` - 新增 `VirtualPayAmountTooLow = NewError(70008, "iOS 虚拟支付最低 1 元")` - 70007/70008 空闲无冲突(70005/70006 已占用)。 **2. 取消漏洞(internal/service/order.go + virtualpay.go)** - `Cancel`:`getOwnedOrder` 后加 `if order.PayChannel == PayChannelVirtual { return nil, errcode.VirtualOrderCancelNotAllowed }`(置于状态校验前,统一拒绝提示);同时避免对虚拟订单误调 JSAPI `CloseOrder`。 - `handleGoodsDeliver`:switch 增加 `case OrderStatusCancelled:`,条件流转 `Cancelled → Paid` 成功后 `finalizeVirtualPaidOrder` 补发货,与 `Expired` 分支并列(防御历史数据与竞态)。 **3. 道具全量同步(internal/mysqldao/goods.go + virtualpay.go)** - `GoodsListVirtualSync` 查询条件改为 `Where("status = ?", 1)`,返回全部上架商品(含已发布)。 - `SyncVirtualGoods`/`syncOneGoods` 注释更新为全量同步语义:重复 upload+publish 同 goods_id 视为更新道具信息与价格;商品 SKU 少(5 类),启动一次开销可忽略;失败仍置 `GoodsProductPublishFail`。 **4. P1 系列(internal/service/virtualpay.go)** - `VirtualOrderCreate`:goods 上架校验后、落单前加 `if request.Platform == XPayPlatformIOS && payAmount < 100` → 返回 70008。 - `handleRefundNotify`:仅 `RefundStatus == ""` 或 `== "SUCCESS"` 时调用 `settleRefundSuccess`,其他状态 `slog.WarnContext` 记录并返回(枚举联调确认)。 - `finalizeVirtualPaidOrder`:`paidTime <= 0` 时兜底 `nowUnix()`。 - `handleIOSRefundQuery`:补充策略注释(默认建议退款、已用权益不回退、后续可按权益使用情况细化拒绝)。 **5. 文档与 DTO(internal/dto/order.go + service/order.go + 接口文档.md)** - `OrderResponse` 加 `PayChannel int8 json:"pay_channel"`;`buildOrderResponse` 加映射。 - 接口文档:订单对象(4.5 节)补 pay_channel 字段;退款章节补「虚拟支付订单请在虚拟支付后台(Midas)发起退款」;虚拟支付下单章节补 70007/70008 说明与「改价后重启服务自动刷新道具价格」运营约定;错误码表补 70007/70008。 ## 验证 - `go build ./...`、`go vet ./...`、`go test ./pkg/wechat/` 通过。 - read_lints 检查改动文件无新增错误。 ## 约束 - 不改支付架构、权益幂等机制、表结构;不修改 configs/config.yaml(本地文件,含非法 YAML)。