富友支付支持

This commit is contained in:
2026-08-18 17:13:20 +08:00
parent 46c8e819df
commit 656a921ff0
17 changed files with 622 additions and 16 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-08-18

View File

@@ -0,0 +1,47 @@
## Context
代理在线充值现有 `OnlinePaymentPort` 抽象只有微信直连 H5/MWEB 与支付宝 WAP 两个 Adapter`payment-methods` 只调用 `wechat.Available``alipay.Available`。当前生效配置为富友时,微信 Adapter 因 `provider_type=fuiou` 判定不可用,导致只返回 `alipay`。富友本质是微信支付上游通道,`pkg/fuiou` 已具备 XML/GBK/双重 URL 编码/RSA 签名验签与回调解析能力,但缺少主扫下单与订单查询。参见 proposal.md - Why。
## Goals / Non-Goals
**Goals:**
- 对外支付方式枚举保持 `wechat` / `alipay`,不暴露 `fuiou`
- 富友配置下 `wechat` 走富友主扫统一下单,复用现有 `qr_content` 契约。
- 富友通道下主动查单通过 `/commonQuery` 收敛状态,不重建支付链接。
- 回调与确认校验接受 `channel=fuiou` 对应业务方式 `wechat`
**Non-Goals:**
- 不新增对外 `fuiou` 支付方式,不改前端枚举。
- 不接入富友退款、撤销、条码支付(商户扫用户)等其它交易类型。
- 不改支付宝通道(仍走直接支付宝 WAP
## Decisions
### 新增富友扫码 Adapter 而非扩展微信 Adapter
新增 `FuiouScanAdapter` 实现 `OnlinePaymentPort``Available` 判定 `provider_type==fuiou` 且富友字段完整;`CreatePaymentURL` 调主扫下单返回 `qr_code``Query` 调订单查询映射 `trans_stat`。备选方案是在 `WechatWebAdapter` 内部分支,但会混淆微信直连与富友的日志提供方、错误语义与恢复策略,故放弃。
### Adapter 选择改为配置感知
`OnlineCreationService``RecoverOnlinePaymentService``adapter()` 增加 `config` 参数:`wechat` 方法 + `provider_type==fuiou` 返回富友 Adapter否则返回微信 Adapter。恢复阶段富友与微信一致只查单不重建链接。
### 业务方式与渠道分离存储
`Payment.PaymentMethod` 与充值记录 `PaymentMethod` 保持 `wechat`(业务语义),充值记录 `PaymentChannel``fuiou`(实际渠道);`paymentMerchantIdentity` 在富友下返回 `FyMchntCd`。回调 `PaymentMethod=fuiou` 在确认入口归一化为业务方式 `wechat`,领域校验允许 `channel=fuiou` 映射到 `method=wechat`
### 复用现有富友回调
异步通知复用 `FuiouPayCallback``VerifyNotify``NotifyRequest` 字段与主扫通知报文一致,不做改动。
## Risks / Trade-offs
- [富友主扫 `mchnt_order_no` 必须全局唯一,重复会被拒绝] → 复用本地 `payment_no` 作为商户订单号,且恢复阶段只查单不重建链接。
- [富友查询 `trans_stat``9999`/空/`1010` 时状态未知] → 映射为 unknown保持待恢复继续查不确认收款也不关闭。
- [富友 `reserved_*` 字段不参与签名且渠道会新增] → 复用 `pkg/fuiou` 现有 `structToMap` 排除 reserved 前缀的签名规则。
- [回调无支付时间或金额不一致] → 现有确认用例已校验金额、配置身份与支付时间,富友金额用 `order_amt`(分)、时间用 `reserved_txn_fin_ts`
## Migration Plan
无数据库迁移、无新外部依赖。代码上线后,将生效支付配置切为富友即可使代理在线充值展示微信扫码;回滚为恢复生效配置为微信直连或回退代码,不改变既有数据语义。

View File

@@ -0,0 +1,31 @@
## Why
当前生效支付配置为富友(`provider_type=fuiou`)时,代理在线充值可用支付方式接口只返回 `alipay`,不返回 `wechat`。原因是现有微信 Adapter 只支持微信直连(`wechat`/`wechat_v2`),而富友虽然本质是微信支付上游通道,却未接入代理在线充值链路。本变更让富友主扫下单成为内部 `wechat` 通道,使代理在线充值在富友配置下也能展示并完成微信扫码支付。
## What Changes
- 代理在线充值 `payment-methods` 在富友配置完整时把 `wechat` 列为可用支付方式(对外枚举仍只有 `wechat``alipay`,不新增 `fuiou`)。
- `POST /api/admin/agent-recharges` 使用 `wechat` 创建时,若生效配置为富友,则调用富友主扫统一下单,返回 `qr_code` 作为支付链接。
- 新增富友主扫下单(`/preCreate`)与主动查单(`/commonQuery`)客户端能力;支付结果继续复用现有富友异步通知回调。
- 代理在线充值支付恢复(主动查单)在富友通道下通过 `commonQuery` 收敛状态,不重建链接。
- 支付确认校验允许富友渠道(`channel=fuiou`)对应业务支付方式 `wechat`
## Capabilities
### New Capabilities
(无)
### Modified Capabilities
- `agent-funds-commission`: 代理在线充值可用支付方式与创建行为在富友配置下按微信通道处理。
- `external-integration`: 富友主扫统一下单与订单查询的调用、状态映射与失败边界。
## Impact
- `internal/application/agentrecharge``OnlineCreationService``RecoverOnlinePaymentService`、确认用例渠道校验)
- `internal/domain/agentrecharge`(支付确认渠道一致性校验)
- `internal/infrastructure/payment`(新增富友扫码 Adapter
- `pkg/fuiou`(新增主扫下单与订单查询请求/响应)
- `internal/handler/callback`(富友回调支付方式归一化为业务方式 `wechat`
- 无数据库迁移、无新外部依赖;对外支付方式枚举不变。

View File

@@ -0,0 +1,29 @@
## ADDED Requirements
### Requirement: 代理在线充值可用支付方式按支付配置判定
系统 SHALL 按当前生效支付配置判定代理在线充值可用支付方式:微信直连(`wechat``wechat_v2`)配置完整,或富友(`fuiou`)配置完整时,返回 `wechat`;支付宝字段完整时返回 `alipay`。对外支付方式枚举 MUST 固定为 `wechat``alipay`MUST NOT 返回 `fuiou`。代理账号以 `wechat` 创建在线充值单时,若生效配置为富友,系统 MUST 使用富友主扫统一下单并将返回的二维码链接作为支付链接。
#### Scenario: 富友配置完整时微信可用
- **GIVEN** 当前生效支付配置 `provider_type=fuiou` 且富友机构号、商户号、终端号、私钥、公钥、API 地址、通知地址均非空
- **WHEN** 代理账号查询可用支付方式
- **THEN** 系统返回包含 `wechat` 的方式列表且不包含 `fuiou`
#### Scenario: 微信直连配置完整时微信可用
- **GIVEN** 当前生效支付配置为微信直连且对应字段完整
- **WHEN** 代理账号查询可用支付方式
- **THEN** 系统返回包含 `wechat` 的方式列表
#### Scenario: 支付宝字段完整时支付宝可用
- **GIVEN** 当前生效支付配置的支付宝应用 ID、应用私钥、支付宝公钥、通知地址均非空
- **WHEN** 代理账号查询可用支付方式
- **THEN** 系统返回包含 `alipay` 的方式列表
#### Scenario: 富友配置下微信创建走主扫下单
- **GIVEN** 当前生效支付配置为富友且字段完整
- **WHEN** 代理账号以 `wechat` 创建在线充值单
- **THEN** 系统调用富友主扫统一下单并返回二维码链接作为支付链接,本地充值单支付方式为 `wechat`、支付渠道为 `fuiou`

View File

@@ -0,0 +1,36 @@
## ADDED Requirements
### Requirement: 富友主扫统一下单与订单查询
系统 SHALL 通过富友主扫统一下单创建微信二维码支付并返回 `qr_code` 二维码链接;系统 SHALL 通过富友订单查询按 `trans_stat` 将状态映射为已支付、已关闭、待支付或未知,未知状态 MUST 保持待恢复。下单与查询失败 MUST 映射为项目稳定错误,已接入外部交互日志的调用保留脱敏结果。
#### Scenario: 主扫下单成功返回二维码链接
- **GIVEN** 富友支付配置完整
- **WHEN** 系统发起主扫统一下单且渠道返回成功
- **THEN** 系统返回 `qr_code` 作为支付链接,业务以该链接生成二维码
#### Scenario: 查询映射支付成功
- **WHEN** 富友订单查询返回 `trans_stat=SUCCESS`
- **THEN** 系统将状态映射为已支付并取得渠道交易号、金额与支付时间
#### Scenario: 查询映射已关闭
- **WHEN** 富友订单查询返回 `trans_stat``PAYERROR``CLOSED``REVOKED`
- **THEN** 系统将状态映射为已关闭
#### Scenario: 查询映射待支付
- **WHEN** 富友订单查询返回 `trans_stat``USERPAYING``NOTPAY`
- **THEN** 系统将状态映射为待支付
#### Scenario: 查询状态未知保持待恢复
- **WHEN** 富友订单查询返回系统错误、找不到交易或无法识别的 `trans_stat`
- **THEN** 系统将状态映射为未知并保持本地支付单待恢复,不得据此确认收款或关闭订单
#### Scenario: 下单失败返回稳定错误
- **WHEN** 富友主扫统一下单返回失败或请求结果未知
- **THEN** 系统返回项目稳定错误且已接入外部交互日志的调用记录脱敏结果

View File

@@ -0,0 +1,33 @@
## 1. 富友主扫下单与查询客户端
- [x] 1.1 在 `pkg/fuiou` 新增主扫统一下单请求/响应结构(`/preCreate`,含 `order_type``notify_url``reserved_expire_minute`,响应含 `qr_code`
- [x] 1.2 在 `pkg/fuiou` 新增主扫下单方法,复用 `Client.Sign``DoRequest`
- [x] 1.3 在 `pkg/fuiou` 新增订单查询请求/响应结构(`/commonQuery`,响应含 `trans_stat``order_amt``transaction_id``reserved_txn_fin_ts`
- [x] 1.4 在 `pkg/fuiou` 新增订单查询方法,复用 `Client.Sign``DoRequest`
- [x] 1.5 补充 `trans_stat` 到统一支付状态的映射SUCCESS→已支付PAYERROR/CLOSED/REVOKED→已关闭USERPAYING/NOTPAY→待支付其余→未知
## 2. 富友扫码 Adapter
- [x] 2.1 新增 `internal/infrastructure/payment/fuiou_scan.go``FuiouScanAdapter`,实现 `OnlinePaymentPort`
- [x] 2.2 `Available` 判定 `provider_type==fuiou` 且富友机构号/商户号/终端号/私钥/公钥/API 地址/通知地址完整
- [x] 2.3 `CreatePaymentURL` 调主扫下单并以 `qr_code` 作为 `QRContent`,接入外部交互日志
- [x] 2.4 `Query` 调订单查询并按映射返回统一查询结果
## 3. 代理在线充值 Adapter 选择与渠道事实
- [x] 3.1 `OnlineCreationService` 注入富友 Adapter`adapter()` 增加配置参数并按 `provider_type==fuiou` 分流
- [x] 3.2 `RecoverOnlinePaymentService` 同样注入并按配置分流,恢复阶段富友只查单不重建链接
- [x] 3.3 `paymentMerchantIdentity` 在富友下返回 `FyMchntCd`
- [x] 3.4 创建本地事实时 `PaymentChannel``fuiou``PaymentMethod` 仍存 `wechat`
## 4. 回调与确认校验
- [x] 4.1 `confirmAgentRechargePayment` 将回调 `PaymentMethod==fuiou` 归一化为业务方式 `wechat` 后再进入确认用例
- [x] 4.2 `domain.ValidatePaymentConfirmation` 允许 `channel=fuiou` 对应 `method=wechat`,保留其余一致性校验
## 5. 装配与验证
- [x] 5.1 `bootstrap/services.go` 注入富友扫码 Adapter 到在线创建与恢复服务
- [x] 5.2 `gofmt -w` 变更文件,`go build ./cmd/api ./cmd/worker` 通过
- [x] 5.3 `go run cmd/gendocs/main.go` 重新生成文档(如路由/DTO 有变化)
- [x] 5.4 `openspec validate --all` 通过