first commit

This commit is contained in:
2026-09-11 01:41:52 +08:00
commit 9f22e6ccae
263 changed files with 28935 additions and 0 deletions
@@ -0,0 +1,124 @@
---
name: 虚拟支付沙箱环境自动选择
overview: 为虚拟支付增加环境自动选择:保持 EnvAppKey/SandboxAppKey 双槽位配置,按「只填沙箱 key → env=1 沙箱;只填现网 key 或两把都填 → env=0 现网」的规则在客户端初始化时决定激活环境,并把 env 贯穿到道具同步、下单签名、订单记录与回调校验全链路。
todos:
- id: wechat-env-select
content: 改造 pkg/wechat/virtualpay.go:新增 resolveEnvKey 与 client.env,调整初始化校验,道具 API 带 env,QueryOrder/RefundOrder/BuildPaymentSign 去 env 入参并新增 GetVirtualPayEnv
status: completed
- id: service-env-wire
content: 接入 internal/service/virtualpay.go:VirtualOrderCreate 改用 GetVirtualPayEnv 记录订单环境并适配签名调用
status: completed
dependencies:
- wechat-env-select
- id: test-adapt
content: 适配 virtualpay_test.go 现有测试并新增 TestResolveEnvKey 四分支单测
status: completed
dependencies:
- wechat-env-select
- id: sync-config-docs
content: 更新 config.yaml、config.example.yaml 与 README 中 WechatVirtualPay 的环境选择规则说明
status: completed
dependencies:
- wechat-env-select
- id: verify-build
content: 执行 go build、go vet、go test 与 read_lints 全量验证
status: completed
dependencies:
- service-env-wire
- test-adapt
- sync-config-docs
---
## 产品概述
为微信小程序虚拟支付(XPay)增加环境自动选择能力:配置保留双槽位(EnvAppKey 现网 / SandboxAppKey 沙箱),服务启动初始化时按规则自动判定激活环境,并把 env 贯穿到道具同步、下单签名、查单退款全链路。测试与上线切换只需修改配置文件,无需改代码。
## 核心功能
- 环境自动选择:只填沙箱 AppKey 时激活沙箱环境(env=1,测试不真实扣款);只填现网 AppKey 或两把都填时激活现网环境(env=0);两把都空时初始化报错、虚拟支付禁用
- 全链路 env 贯穿:道具上传/发布、订单查询/退款、前端调起签名(signData 的 env 字段与 paySig)均使用激活环境的 key 与 env 值
- 订单记录环境:虚拟支付订单的 xpay_env 字段记录下单时的激活环境,发货推送按此校验(沙箱推送不会误发现网订单)
- 环境切换零代码:切换测试/现网只需改配置后重启,启动时的全量道具同步会自动把商品发布到新激活的环境
## 技术栈
- 语言与框架:Go 1.27(复用现有项目栈,不改架构)
- 涉及模块:pkg/wechat/virtualpay.go(XPay 客户端)、internal/service/virtualpay.go(虚拟支付业务)、pkg/wechat/virtualpay_test.go(单元测试)
- 配置:configs/config.yaml、configs/config.example.yaml(仅更新注释)
## 实现方案
### 环境选择规则(初始化时一次决定)
抽成纯函数便于单测:
```
// resolveEnvKey 按配置决定激活环境:
// EnvAppKey 非空 -> 现网(env=0,两把都填时现网优先);
// EnvAppKey 空且 SandboxAppKey 非空 -> 沙箱(env=1);
// 两把都空 -> 返回错误。
func resolveEnvKey(envAppKey, sandboxAppKey string) (appKey string, env int, err error)
```
`InitVirtualPayClient` 调用该函数得到激活 key 与 env:校验 OfferID 非空 + resolveEnvKey 成功;激活 key 存入 client 的 envAppKey 字段(保持 callXPay 签名逻辑不动),新增 `env int` 字段记录激活环境。日志输出当前激活环境,便于运维确认。
### env 贯穿链路
```mermaid
flowchart LR
A[config.yaml<br/>EnvAppKey / SandboxAppKey] --> B[InitVirtualPayClient<br/>resolveEnvKey]
B --> C[client.envAppKey = 激活key<br/>client.env = 0/1]
C --> D[道具同步 API<br/>body 带 env + 沙箱key签名]
C --> E[BuildPaymentSign<br/>signData.env = client.env<br/>paySig 用激活key]
C --> F[QueryOrder / RefundOrder<br/>内部用 client.env]
E --> G[VirtualOrderCreate<br/>订单 xpay_env = GetVirtualPayEnv]
G --> H[发货推送校验<br/>推送 Env == 订单 xpay_env]
```
### 关键改动点
**pkg/wechat/virtualpay.go**
- 新增 `resolveEnvKey` 纯函数(规则见上)
- `virtualPayClient` 增加 `env int` 字段;`InitVirtualPayClient` 改用 resolveEnvKey(原校验改为 OfferID 非空 + 至少一把 key)
- 道具 API(StartUploadGoods/QueryUploadGoods/StartPublishGoods/QueryPublishGoods)body 增加 `"env": c.env`(微信按 env+key 双校验,注释标注字段联调以官方文档为准)
- `QueryOrder`/`RefundOrder` 删除 env 入参(当前无 service 调用点,改动安全),内部使用 `c.env`
- `BuildPaymentSign` 删除 env 入参,signData 的 env 字段与 paySig 使用 `c.env` 与 `c.envAppKey`(即激活环境)
- 新增导出函数 `GetVirtualPayEnv() (int, error)` 供 service 层记录订单环境
**internal/service/virtualpay.go**
- `VirtualOrderCreate`:`XPayEnv` 由写死的 `wechat.XPayEnvProd` 改为 `wechat.GetVirtualPayEnv()` 返回值;`BuildPaymentSign` 调用去掉 env 实参
- `SyncVirtualGoods` 道具同步调用不变(env 已在 wechat 层内部带入)
- `handleGoodsDeliver` 的 Env 校验逻辑保持不动(订单 xpay_env 与推送 Env 匹配)
**pkg/wechat/virtualpay_test.go**
- 适配 `TestBuildPaymentSign`:client 构造改为含 env 字段、调用去掉 env 实参
- 新增 `TestResolveEnvKey`:覆盖四个分支(只填现网、只填沙箱、两把都填、两把都空)
**配置与文档**
- `configs/config.yaml` 与 `configs/config.example.yaml`:WechatVirtualPay 段注释更新为环境选择规则(只填沙箱 key 走沙箱测试;只填现网 key 或两把都填走现网)
- `README.md` 配置表 WechatVirtualPay 行同步该规则
## 目录结构
```
pkg/wechat/
├── virtualpay.go # [MODIFY] resolveEnvKey 纯函数、client.env 字段、初始化调整、道具 API 带 env、QueryOrder/RefundOrder 去 env 入参、BuildPaymentSign 去 env 入参、GetVirtualPayEnv
└── virtualpay_test.go # [MODIFY] 适配现有签名测试 + 新增 TestResolveEnvKey 四分支单测
internal/service/
└── virtualpay.go # [MODIFY] VirtualOrderCreate 改用 GetVirtualPayEnv 记录订单环境
configs/
├── config.yaml # [MODIFY] WechatVirtualPay 段注释更新(不改已填值)
└── config.example.yaml # [MODIFY] WechatVirtualPay 段注释更新环境选择规则
README.md # [MODIFY] 配置表 WechatVirtualPay 行说明同步
```
## 实现注意事项
- 只改虚拟支付环境选择与 env 贯穿,不动支付架构、权益发放/回退、订单状态机、普通 JSAPI 支付
- 环境切换后重启即可:P0-2 修复后的全量道具同步会自动把商品发布到新激活环境,无需额外迁移
- 沙箱与现网的道具/订单/推送隔离由微信侧保证(按签名 key 与 env 双校验)
- 验证:go build ./...、go vet ./...、go test ./pkg/wechat/ 通过;read_lints 无新增错误