归档支付商户池变更并同步主规格
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 1m25s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 1m25s
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-08-31
|
||||
@@ -0,0 +1,67 @@
|
||||
## Context
|
||||
|
||||
现有 `tb_wechat_config` 同时承担支付渠道配置;订单、充值和 `tb_payment` 通过 `payment_config_id` 供回调加载创建时配置。`tb_payment` 已有非敏感 `merchant_identity` 快照,但不足以区分商户池、服务商和完整路由。新模型必须让新单和旧单分流,不能把历史订单指向迁移后新商户。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 独立实体与不可变路由快照
|
||||
新增商户、商户池、池成员、微信授权配置及金额/笔数统计事实;支付单扩展实际商户和商户池 ID、非敏感快照及创建时的 `routing_epoch`。商户凭证只留在商户表受控字段,支付单/审计不复制。被引用后锁定商户支付方式、服务商与身份,避免历史验签与退款语义漂移。
|
||||
|
||||
### 路由和统计在支付创建/成功边界闭合
|
||||
支付创建在事务内读取唯一启用池、按方式选择成员并冻结路由和 `routing_epoch`;无成员/池失败即返回。`routing_epoch` 是该次创建命中的池策略、统计周期与成员排序世代。金额/笔数统计只在现有“支付成功首次生效”路径以支付 ID 唯一事实按冻结商户和 epoch 递增,不在预下单预占。迟到的首次成功仍记入冻结 epoch,当前选择器只读取当前 epoch,因此周期切换、成员调整或排序变化不改写已创建支付的归属。时间方式由当前时间和受控起点计算,不写逐次路由日志。
|
||||
|
||||
### 凭证版本与读取一致性
|
||||
商户和微信授权配置各保存递增的凭证版本。凭证更新在事务提交时递增版本;支付创建、回调验签、查单和退款先从主库取得当前版本,再使用“配置 ID + 版本”读取缓存。旧版本缓存提交后可删除,但即使并发残留也不得再被新读取命中;缓存不可用时读取当前数据库记录。历史支付仍读取命中商户的当前有效凭证,以支持轮换。
|
||||
|
||||
### 新旧配置双读切换
|
||||
迁移完成后部署支持 merchant ID 或旧 `payment_config_id` 双读的 API/Worker。三类后续新支付立即只走商户池并在创建事务冻结路由;无可用池、成员或池停用时明确失败,绝不回退旧综合支付配置创建。merchant ID 为空仅表示历史支付,回调验签、查询和已有可达的原路退款继续按 `payment_config_id` 处理,直到独立 Change 根据数据留存期删除旧读取路径。故障只允许在仍支持双读的版本上前向修复;一旦存在 merchant ID 非空支付、成功事实或新配置,禁止部署不识别新路由的旧二进制和执行破坏性 down。不可将现有记录批量回填为新商户,因为其实际历史身份无法保证一致。
|
||||
|
||||
### 敏感配置边界
|
||||
仅超级管理员和平台用户的专用管理列表、详情响应可完整返回凭证,管理写入请求可传递凭证;所有其他 DTO、logger、错误、审计 payload、支付单快照和导出只允许写 ID、名称和脱敏/非敏感身份。
|
||||
|
||||
### 已确认范围
|
||||
本 Change 只为已有且可达的原路退款路径提供冻结商户的凭证加载与能力判定,不新增微信、支付宝、富友或其他渠道退款调用;服务商没有既有退款能力时保持不支持,且不提供人工开关。富友 `CommonQuery` 保持现有请求格式、签名算法、验签、状态映射和恢复语义的兼容基线,只实现本地双读配置来源和商户加载,不改变协议或业务能力,富友退款不新增;未第三方实测可记录但不得作为实施、验证或归档阻塞。零条 active 综合配置时仅创建 Schema 和管理入口,不插入商户、商户池或微信授权配置数据。代理在线充值的全局允许范围、交集计算和对外方式查询由对应代理自充 Change 负责;本 Change 在方式已获准后负责商户池可用性与实际商户冻结。
|
||||
|
||||
## 管理与支付动作契约
|
||||
|
||||
以下路径为本 Change 新增后台管理契约;所有管理写操作仅限超级管理员和平台用户,金额均为分,敏感凭证只在已认证管理请求的写入/详情响应中传输,绝不进入支付快照、审计、日志或导出。
|
||||
|
||||
### 商户
|
||||
|
||||
- `POST /payment-merchants`:请求 `name`、`payment_method`(`wechat`/`alipay`)、`provider_type`、`merchant_identity`、`credentials`、`enabled`、`remark`。同一支付方式下身份标识不得重复;凭证缺失或与服务商类型不匹配时拒绝。
|
||||
- `PUT /payment-merchants/:id`:未被任何支付单引用时可修改全部字段;被引用后仅可改名称、凭证、启停、备注,修改支付方式、服务商类型或商户身份返回“已被支付单引用,不能修改收款身份”。
|
||||
- `DELETE /payment-merchants/:id`:被引用或仍属于任一商户池时拒绝;删除前必须显式二次确认。停用不影响已冻结该商户的查单、验签和退款。
|
||||
|
||||
### 商户池
|
||||
|
||||
- `POST /payment-merchant-pools`:请求 `name`、`payment_method`、`enabled`、`strategy`(金额/笔数/时间)、策略参数和有序 `member_ids`。成员均须存在、启用、与池支付方式一致且不重复;同一支付方式最多一个启用池,冲突返回“该支付方式已有启用商户池”。
|
||||
- `PUT /payment-merchant-pools/:id`:修改阈值只保留当前统计;修改金额/笔数统计周期、时间周期、起始时间或每轮成员排序时开启新统计周期;自然周期仅调整排序时保留未移除成员累计。成员移除后不再选择,但历史支付快照不改写。
|
||||
- `POST /payment-merchant-pools/:id/enable` 与 `/disable`:启用时再次校验唯一启用池和可用成员;停用后新支付创建明确失败,不回退综合支付配置。
|
||||
|
||||
### 微信授权配置
|
||||
|
||||
- `GET /wechat-authorizations`:最多返回一个启用配置;仅管理角色可读取。`PUT /wechat-authorizations/current` 创建或更新唯一配置,写入公众号 H5/JSSDK、小程序登录及支付 AppID 所需字段。
|
||||
- 启用第二个配置返回“平台已有启用微信授权配置”;停用后 C 端微信登录/OpenID/微信支付 AppID 不得静默回退其他支付商户或旧配置。
|
||||
|
||||
### 新旧支付分流
|
||||
|
||||
- C 端套餐订单、资产钱包充值、代理在线预存款创建支付时,在同一事务内读取对应支付方式唯一启用池并冻结 `merchant_id`、`merchant_pool_id`、非敏感收款身份与轮询快照;无可用成员返回“暂无可用商户”。
|
||||
- `merchant_id` 非空的支付,在回调、查单、退款时加载该商户当前凭证;为空的历史支付仅按既有 `payment_config_id` 处理。不得按当前启用池为历史单推断商户。
|
||||
- 首次支付成功消费者以支付 ID 唯一记账金额/笔数统计;重复回调不重复累计。预下单、失败、关闭和退款均不变更统计。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- 并发成功回调超过阈值:这是“只统计成功、不预占”的明确结果,下一次选路才跳过。
|
||||
- 商户停用后的退款:停用不阻断已有历史退款路径,实际调用仍由当前凭证、服务商已有能力和渠道结果决定;本 Change 不新增退款能力。
|
||||
- 零条 active 综合配置:仅创建 Schema 和管理入口,不创建业务配置数据;所有新线上支付明确失败,微信授权相关功能明确返回未配置。一条 active 综合配置中支付凭证不完整的方式不建池,微信授权字段不完整时不建授权配置且相关 C 端微信功能明确失败;多条 active 综合配置无法安全选择来源,迁移必须失败。
|
||||
- 旧回调误入新路径:以支付单 merchant ID 为唯一分流条件,严禁按当前启用池推断。
|
||||
- 富友查单:`CommonQuery` 保持现有请求格式、签名算法、验签、状态映射和恢复语义,只调整本地双读配置来源和商户加载;不改变协议、状态解释、恢复规则或业务能力,不增加富友退款。未第三方实测仅作为记录,不阻塞本 Change。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. 新增成对迁移创建商户/池/成员/微信授权表、唯一启用约束、支付单路由与 `routing_epoch` 列、凭证版本及成功统计唯一索引;迁移编号在实施开始前按当时迁移目录的最大编号确定,本规划不预占编号。
|
||||
2. 迁移遇到一条 active 综合配置时只复制完整支付凭证形成对应单成员池;微信授权字段完整时创建授权配置。零条 active 配置时仅创建 Schema 和管理入口,不插入商户、商户池或微信授权配置数据;多条 active 配置时中止迁移。全过程禁止日志输出密钥/证书。
|
||||
3. 在本地工作区以明确 `DB_*` 指向维护者提供的 `junhong_cmp_test` PostgreSQL 与 Redis DB6,执行 migration up/down/up 和数据行为验证;允许本 Change fixture 创建/删除,禁止重置整个库。完成验证后才提交推送 Iteration/8-11;仅连接、迁移或实际行为失败时阻塞对应验证。
|
||||
4. Iteration/8-11 分支 Gitea 仅以本次提交 SHA 构建/部署 `cmp-test` 测试镜像并检查 migration version,不自动执行 migration up/down 或重置。记录 API/Worker 容器状态、健康检查和 `/opt/junhong_cmp/logs` 的有限日志;不建运行时开关,这不是生产发布。
|
||||
5. 迁移完成后的部署版本支持 merchant ID 或旧 `payment_config_id` 双读;三类后续新支付立即只走商户池并冻结路由,无池、成员或停用池时明确失败且无旧创建回退。验证新支付始终走池、历史 merchant ID 为空支付继续旧读取路径。故障只允许在仍支持双读的版本上前向修复;存在 merchant ID 非空支付、成功事实或新配置时,禁止部署不识别新路由的旧二进制和执行破坏性 down。
|
||||
6. 富友 `CommonQuery` 仅保持现有请求格式、签名算法、验签、状态映射和恢复语义并完成本地双读商户加载,不改变协议或增加退款;未第三方实测仅记录,不阻塞验证或归档。验证覆盖微信直连、富友、支付宝、零/一/多 active 配置、授权或支付凭证缺失、停用历史商户、回调兼容、三种轮询、迟到成功归属、凭证轮换及 up/down/up。
|
||||
@@ -0,0 +1,28 @@
|
||||
## Why
|
||||
|
||||
当前所有线上支付依赖一份综合支付配置,无法按支付方式在多个实际收款商户之间受控轮询;已存在且可达的回调、查单和原路退款路径也缺少冻结商户事实,无法在商户停用后继续安全加载对应凭证。
|
||||
|
||||
本 Change 落实 AUG26-002:将收款商户、支付路由和 C 端微信授权分离,并以商户池快照取代新业务对旧综合支付配置的依赖。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 新增独立商户管理:微信商户与支付宝商户分别建档;商户保存支付能力和敏感凭证,但历史支付单仅保存非敏感身份快照。
|
||||
- 新增每种支付方式至多一个启用商户池,支持按成功收款金额、成功笔数或时间周期轮询;新支付仅从命中池选择实际商户。
|
||||
- 新增全局唯一启用的微信授权配置,专供 C 端公众号 H5/JSSDK、小程序登录、OpenID 和支付 AppID;它不是微信收款商户。
|
||||
- 新建 C 端套餐购买、资产钱包充值、代理在线预存款充值按商户池路由;后台线下/钱包支付不经过商户池。代理全局允许范围、其与商户池方式的交集及对外方式查询仍由对应代理自充 Change 负责;本 Change 只在方式已获准后选择实际商户。无可用商户时失败,不得回退旧配置或自动换商户重试。
|
||||
- 迁移完成后部署支持按 merchant ID 或旧 `payment_config_id` 双读的 API/Worker;三类后续新支付立即只走商户池并冻结路由,无可用池、成员或停用池时明确失败且不回退旧综合支付配置。merchant ID 为空的仅为历史订单,继续处理其自身回调、查单和已有可达的原路退款,直到独立 Change 按数据留存期删除旧读取路径。
|
||||
- 零条当前生效综合支付配置时仅创建新 Schema 和管理入口,不插入空商户、空商户池或空微信授权配置;新线上支付明确失败,微信授权功能明确返回未配置。富友 `CommonQuery` 保持现有请求、签名、验签、状态和恢复兼容基线,只实现本地双读凭证与商户加载,不改协议或退款;未第三方实测可记录但不构成本 Change 实施或归档阻塞。验证在本地工作区以明确 `DB_*` 指向维护者指定的 `junhong_cmp_test` PostgreSQL、Redis DB6 执行;完成后才提交推送 Iteration/8-11,由 Gitea 构建/部署 `cmp-test` 测试镜像并检查迁移版本,日志位于 `/opt/junhong_cmp/logs`,不重置整库。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `merchant-payment-routing`: 商户、商户池、微信授权配置、轮询、历史商户快照和迁移切换行为。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- 无。该能力向既有支付创建、回调、查单和已有原路退款调用链提供已选商户事实,不改变既有资金幂等不变量,也不新增渠道退款能力。
|
||||
|
||||
## Impact
|
||||
|
||||
影响支付配置模型和后台接口、`tb_payment`/订单/充值支付关联、微信/支付宝/富友支付加载和回调、支付与退款审计、敏感信息访问及新增成对迁移。
|
||||
@@ -0,0 +1,89 @@
|
||||
## Purpose
|
||||
|
||||
管理实际收款商户、商户池轮询和全局微信授权配置,使新线上支付的收款身份可冻结、历史支付可继续使用其原商户,并避免凭证泄露或无配置时静默回退;本能力不新增任何支付渠道退款能力。
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 商户与微信授权配置管理
|
||||
系统 SHALL 将实际收款商户与微信授权配置分离。一个商户 MUST 仅对应 `wechat` 或 `alipay` 一种支付方式,并保存名称、支付方式、服务商类型、商户号或应用标识、敏感凭证、状态和备注;微信直连与富友均为微信支付商户。平台最多存在一个启用的微信授权配置,该配置保存 C 端公众号 H5/JSSDK、小程序登录所需参数,C 端微信登录、OpenID 和微信支付 AppID MUST 只读取该配置。
|
||||
|
||||
超级管理员和平台用户可创建、编辑、启用、停用商户、商户池和微信授权配置,其他角色无管理入口。仅上述角色的专用管理列表和详情响应可返回完整凭证;日志、审计快照、错误、导出、支付快照和其他业务响应 MUST NOT 保存或返回敏感凭证。被支付单引用的商户 MUST NOT 删除且其支付方式、服务商类型、商户号/应用标识不得修改;未被引用商户仅可移出所有商户池并经二次确认删除。停用只影响新支付单,历史支付的回调、查单和已有可达的原路退款仍使用该商户当前凭证;服务商无既有退款能力时保持不支持,本能力不得为此新增渠道退款调用或人工开关。
|
||||
|
||||
#### Scenario: 受引用商户停用
|
||||
- **WHEN** 管理员停用已被支付单命中的商户
|
||||
- **THEN** 新支付单不再选择该商户,已命中支付单的回调、查询和原路退款仍按该商户处理
|
||||
|
||||
#### Scenario: 非管理角色读取配置
|
||||
- **WHEN** 不具备超级管理员或平台用户身份的账号请求商户或微信授权配置
|
||||
- **THEN** 系统拒绝访问且不返回任何凭证或身份字段
|
||||
|
||||
#### Scenario: 凭证轮换后处理历史支付
|
||||
- **GIVEN** 已被支付单引用的商户或当前微信授权配置完成凭证更新
|
||||
- **WHEN** 更新提交后创建支付、处理回调、查单或原路退款
|
||||
- **THEN** 系统只使用更新后的当前有效凭证,不得继续使用更新前的缓存凭证
|
||||
|
||||
### Requirement: 商户池唯一性与轮询配置
|
||||
系统 SHALL 为每种支付方式最多启用一个商户池;停用历史池可保留,但不得同时启用多个同支付方式池。商户池成员支付方式 MUST 与池一致,成员按明确顺序排列;金额/笔数方式必须配置 `每轮累计`、`自然日累计` 或 `自然月累计` 统计周期,时间方式必须配置最小为 1 分钟的数值、单位和起始时间。
|
||||
|
||||
金额和笔数轮询只统计已确认支付成功结果,不在预下单时预占,也不因退款回冲。支付创建时 MUST 冻结本次路由所属的统计世代;首次成功只按支付 ID 一次性计入冻结商户和冻结统计世代,当前选路只读取当前统计世代。达到阈值的成员在当前周期跳过;所有成员达到阈值时新支付失败。时间轮询自起始时间按固定时段和成员顺序选择,成员停用即时跳下一个可用成员但不重置时段。预下单失败不得自动切换或重试,失败单不计入统计;客户再次发起时重新选择。修改阈值保留当前统计,修改统计周期、金额/笔数方式、时间周期、起始时间或每轮排序按 PRD 规则开启新周期;自然周期排序调整保留未移除成员累计。
|
||||
|
||||
#### Scenario: 并发预下单未预占额度
|
||||
- **WHEN** 多个客户并发创建金额或笔数轮询支付单且当前成员尚未达到阈值
|
||||
- **THEN** 系统可使这些支付单均命中当前成员,只有后续确认成功的支付才计入累计,已创建支付单不因轮询切换改挂商户
|
||||
|
||||
#### Scenario: 迟到首次成功归属冻结统计世代
|
||||
- **GIVEN** 支付已创建但尚未成功,之后商户池切换统计周期、成员或排序
|
||||
- **WHEN** 该支付首次确认成功
|
||||
- **THEN** 系统仅将金额或笔数写入该支付创建时冻结的商户和统计世代,不得改写当前选路统计或重复累计
|
||||
|
||||
#### Scenario: 当期没有可用商户
|
||||
- **WHEN** 启用商户池中不存在启用且未达阈值的成员,或商户池已停用
|
||||
- **THEN** 系统拒绝创建新支付单并提示暂无可用商户,不回退到旧综合支付配置
|
||||
|
||||
### Requirement: 新支付商户快照与历史兼容
|
||||
C 端套餐购买、C 端资产钱包充值及代理在线预存款充值 SHALL 按支付方式通过对应启用商户池选择实际商户;后台线下订单和钱包余额支付 MUST NOT 经过商户池。代理在线充值的全局允许范围、其与商户池方式的交集及对外方式查询由对应代理自充能力定义;本能力在方式已获准后负责实际商户选择,并在无可用商户时拒绝创建。每笔通过商户池创建的支付单 MUST 保存商户 ID、商户名称/支付方式/服务商类型/商户号或应用标识快照、商户池 ID/名称快照、轮询方式快照及统计世代快照,但不得复制敏感凭证。支付、回调验签、查单和已有可达的原路退款读取该实际商户当前凭证;服务商类型和退款必需凭证完整性只用于已有退款路径的能力判定,不提供人工开关,也不得新增渠道退款能力。
|
||||
|
||||
上线迁移在存在唯一当前生效综合支付配置时复制完整凭证:具备完整凭证的微信/支付宝方式创建商户和各自单成员启用池,微信授权字段完整时创建全局微信授权配置;不完整的支付方式不建池,授权字段不完整时不建授权配置并使相关 C 端微信功能明确失败。没有当前生效综合支付配置时迁移仅创建 Schema 和管理入口,不创建商户、商户池或微信授权配置数据;新订单按暂无可用商户失败,微信授权相关功能明确返回未配置。存在多条当前生效综合支付配置时迁移 MUST 失败。迁移完成后部署支持按 merchant ID 或旧 `payment_config_id` 双读的 API/Worker;三类后续新支付 MUST 立即只走商户池并冻结路由,无可用池、成员或停用池时明确失败且不回退旧综合支付配置。merchant ID 为空仅表示历史订单,继续按 `payment_config_id` 服务历史回调、查询和已有原路退款,直到独立 Change 按数据留存期删除旧读取路径。本 Change MUST NOT 自动删除旧配置或旧读取路径。
|
||||
|
||||
#### Scenario: 新支付冻结实际商户
|
||||
- **WHEN** 客户以微信或支付宝创建覆盖范围内的新线上支付单
|
||||
- **THEN** 系统选择并冻结一个实际商户和商户池路由快照,并使用该商户的服务商凭证发起支付
|
||||
|
||||
#### Scenario: 迁移后的新支付只走商户池
|
||||
- **GIVEN** 迁移完成且已部署支持 merchant ID 或旧 `payment_config_id` 双读的 API/Worker
|
||||
- **WHEN** 客户创建覆盖范围内的后续新线上支付
|
||||
- **THEN** 系统立即经启用商户池选择并冻结实际商户;无可用池、成员或池已停用时明确失败,不得走旧综合支付配置创建
|
||||
|
||||
#### Scenario: 历史支付保留旧读取路径
|
||||
- **GIVEN** 支付单 merchant ID 为空
|
||||
- **WHEN** 该支付经过回调、查单或已有原路退款路径
|
||||
- **THEN** 系统仅按其 `payment_config_id` 处理,不因当前商户池推断或改写其商户,直到独立 Change 按数据留存期删除旧读取路径
|
||||
|
||||
#### Scenario: 旧支付单回调
|
||||
- **WHEN** 商户池切换后收到未带新商户快照的历史支付单回调
|
||||
- **THEN** 系统按既有综合支付配置兼容处理该历史单,不将其改挂到任何新商户
|
||||
|
||||
#### Scenario: 微信授权配置迁移缺失
|
||||
- **GIVEN** 当前生效综合支付配置的微信授权字段不完整
|
||||
- **WHEN** 执行商户池配置迁移后访问 C 端微信登录、OpenID、JSSDK 或微信支付 AppID 功能
|
||||
- **THEN** 系统明确返回微信授权未配置,不得回退旧综合支付配置
|
||||
|
||||
#### Scenario: 多个当前生效综合支付配置
|
||||
- **GIVEN** 存在多条当前生效综合支付配置
|
||||
- **WHEN** 执行商户池配置迁移
|
||||
- **THEN** 迁移失败且不选择任一配置作为复制来源
|
||||
|
||||
#### Scenario: 富友双读兼容基线
|
||||
- **GIVEN** 富友 `CommonQuery` 未经第三方实测
|
||||
- **WHEN** 系统以商户当前凭证完成富友支付的本地双读配置来源和商户加载接线,且不改变现有 `CommonQuery` 请求格式、签名算法、验签、状态映射或恢复语义
|
||||
- **THEN** 系统保留该兼容基线且不新增富友退款;未第三方实测可以记录,但不得作为本 Change 的实施、验证或归档阻塞
|
||||
|
||||
#### Scenario: 维护者指定测试环境的配置迁移验证
|
||||
- **GIVEN** 维护者指定的 `junhong_cmp_test` PostgreSQL、Redis DB6 与当前 Change fixture
|
||||
- **WHEN** 本地工作区以明确 `DB_*` 完成 migration up/down/up 和数据行为验证后提交推送 Iteration/8-11
|
||||
- **THEN** Gitea 只构建/部署 `cmp-test` 测试镜像并检查迁移版本;测试日志保存在 `/opt/junhong_cmp/logs`,不得自动执行迁移或重置整库
|
||||
|
||||
#### Scenario: 历史支付路径保留
|
||||
- **GIVEN** 存在 merchant ID 为空的历史支付
|
||||
- **WHEN** 商户池功能已上线且独立 Change 尚未按数据留存期删除旧读取路径
|
||||
- **THEN** 系统仍按该支付的 `payment_config_id` 处理回调、查询和退款,不自动删除旧配置或将其改挂商户
|
||||
@@ -0,0 +1,21 @@
|
||||
## 1. 数据、配置与双读基础
|
||||
|
||||
- [x] 1.1 冻结现状契约与新旧分流边界:追踪 `tb_wechat_config`、订单/充值、`tb_payment`、支付加载器、微信/支付宝/富友回调、查单和现有退款处理的 `payment_config_id` 读写链路,形成文件/符号级实现清单。明确新单以 `merchant_id` 非空走商户加载,历史 `merchant_id` 为空继续走旧 `payment_config_id`;明确三类新支付仅为 C 端套餐购买、C 端资产钱包充值、代理在线预存款充值,后台线下订单、后台钱包余额支付和员工线下代充值不经过商户池;列出敏感字段、富友 CommonQuery 接缝及退款 A(仅商户加载/能力判定)的边界。不得修改源码。
|
||||
- [x] 1.2 新增成对 Schema 迁移:创建商户、商户池、成员、微信授权配置、凭证版本、成功累计唯一事实及支付路由/统计世代快照所需表列;建立每种支付方式最多一个启用池、成员支付方式一致、历史引用保护、唯一累计事实和查询索引约束。不得修改既有迁移,不把迁移文件编号写死在任务或实现契约中。
|
||||
- [x] 1.3 实现商户、商户池与微信授权配置管理:交付模型、Query、Handler、可执行 RouteSpec 及管理审计;写操作仅限超级管理员和平台用户;实现启停、成员顺序、引用字段锁定、移除后二次确认删除、唯一启用池和全局唯一微信授权配置。C 端微信登录、OpenID、JSSDK 和支付 AppID 只读授权配置;普通 DTO、错误、日志、审计、快照、导出不得返回或保存敏感凭证。
|
||||
- [x] 1.4 先行实现商户双读与凭证版本化加载:在三类新支付接入前,使商户/微信授权凭证更新在事务提交时递增版本;支付创建、回调验签、查单和退款 A 先从主库取得当前版本,再按“配置 ID + 版本”读取缓存,旧版本提交后不得被新读取命中,缓存不可用时回源当前数据库记录。`merchant_id` 非空的新单走商户服务商凭证,空值历史单走 `payment_config_id`;不得按当前启用池推断历史商户。
|
||||
- [x] 1.5 实现上线迁移与零 active A 规则:存在唯一当前生效综合支付配置时,仅复制完整凭证;按完整微信/支付宝凭证分别创建商户及单成员启用池,微信授权字段完整时创建全局授权配置;凭证不完整的方式不建池,授权字段不完整不建授权配置。零条 active 综合支付配置时仅创建新 Schema 和管理入口,绝不插入商户、商户池或微信授权业务行;新线上支付明确按“暂无可用商户”失败且不回退旧配置。多条 active 配置时迁移失败且不选择来源。敏感数据仅在数据库复制,迁移日志不得输出凭证,不批量回填既有支付单 `merchant_id`。
|
||||
|
||||
## 2. 商户池与支付链路
|
||||
|
||||
- [x] 2.1 实现商户池选择器与统计世代规则:支持金额、笔数、时间轮询;金额/笔数支持每轮累计、自然日累计、自然月累计,时间周期最小 1 分钟并带单位和起始时间。支付创建事务内选择启用且未达阈值成员,冻结商户、池、轮询快照和 `routing_epoch`;修改阈值保留当前统计,修改统计周期、策略、时间周期、起始时间或每轮排序按规则开启新世代;自然周期仅排序调整时保留未移除成员累计;停用成员即时跳过但不改写既有支付。不得预下单预占。
|
||||
- [x] 2.2 接入支付成功首次生效统计:以支付 ID 唯一事实按支付创建时冻结的商户和 `routing_epoch` 累计金额/笔数;迟到首次成功仍写入冻结世代,不改写当前选路世代;失败、关闭、预下单和退款不计入;重复回调、重复事件和重复执行不得重复累计。
|
||||
- [x] 2.3 接入三类新支付创建并保持代理边界:在双读/版本加载和选择器完成后,将商户池选择接入 C 端套餐购买、C 端资产钱包充值、代理在线预存款充值。每笔支付在同一业务事务内冻结 merchant/pool/支付方式/服务商/非敏感身份/轮询及统计世代快照,再使用所选商户凭证创建支付。三类后续新支付始终只走商户池;无池、无可用成员或池停用时明确失败,预下单失败不得回退旧综合配置、不得自动换商户重试。后续 Apply 必须删除商户池新支付创建开关、所有引用及任何旧综合配置创建回退。仅代理在线预存款充值走池;员工线下代充值、后台线下订单、后台钱包余额支付及其它代理后台钱包操作不得经过商户池。
|
||||
- [x] 2.4 改造回调、查单及退款 A 的商户加载:微信、支付宝、富友回调和代理在线查单对 `merchant_id` 非空新单使用商户当前凭证,对空值历史单继续按 `payment_config_id` 双读;停用商户不得阻断已冻结历史单的本地回调/查询/退款 A 路径。退款 A 仅实现依据冻结商户加载当前凭证并作退款能力/必需凭证完整性判断、接入既有退款流程;不得新增或验收微信/支付宝/富友实际渠道退款 API、退款请求、渠道退款回调或外部退款成功;保留客户凭证退款、代理钱包回退及既有幂等语义。不得按当前池推断历史商户。
|
||||
|
||||
## 3. 文档、渠道核验与发布控制
|
||||
|
||||
- [x] 3.1 更新支付、商户、商户池和微信授权管理接口 OpenAPI、可执行路由说明及生成入口,覆盖权限、启停、成员排序、引用锁定、二次确认删除、零 active 失败提示、三类新支付和后台排除边界。敏感凭证的脱敏/禁止泄露仅适用于普通 DTO、错误、日志、审计、支付快照、导出及非专用管理响应;超级管理员和平台用户的专用管理列表、详情响应继续允许返回完整凭证。文档与 OpenAPI 示例只能使用占位值,绝不写入真实凭据。不得把具体迁移编号或 candidate 目录写入契约,不新增测试体系;不得暗示本 Change 实现渠道退款 API。
|
||||
- [x] 3.2 富友 B:保持现有 `CommonQuery` 请求格式、签名算法、验签、状态映射和恢复语义;仅改本地双读配置来源与商户加载,使双读查单可按商户当前凭证调用。不得改变协议、验签、状态解释、恢复规则或业务能力,不得增加退款能力;未第三方实测可记录但不得作为任务或归档阻塞。
|
||||
- [x] 3.3 在本地工作区以明确 `DB_*` 指向维护者提供的 `junhong_cmp_test` PostgreSQL 与 Redis DB6,完成 migration up/down/up 和数据行为验证:零/一/多 active、支付/授权凭证缺失、三类新支付始终走商户池、后台 wallet/offline 排除、缺失配置、三种轮询、统计世代与迟到成功、凭证轮换及缓存一致性、并发成功累计、商户停用后的历史回调/查单/退款 A、merchant ID 为空旧单兼容、敏感字段不泄露和重复处理幂等。允许仅为当前 Change 创建/删除 fixtures,禁止重置整个库;仅连接、迁移或实际行为失败时阻塞对应场景。完成后才提交推送 Iteration/8-11;每个场景提供可观察状态/错误/快照/统计事实证据;不新增测试体系,不验收实际渠道退款成功。
|
||||
- [x] 3.4 Iteration/8-11 分支 Gitea 以本次提交 SHA 仅构建/部署 `cmp-test` 测试镜像并检查 migration version,不自动执行 migration up/down 或重置整库;记录 API/Worker 容器状态、健康检查和 `/opt/junhong_cmp/logs` 的有限日志,不建运行时开关且不是生产发布。部署后验证三类后续新支付立即走商户池并冻结路由,无池、成员或停用池明确失败且无旧创建回退,merchant ID 为空历史支付仍读旧路径。故障仅在双读版本上前向修复;存在非空 `merchant_id` 支付、成功事实或新配置时,禁止部署不识别新路由的旧二进制和破坏性 down。后续 Apply 必须删除商户池新支付创建开关、所有引用及任何关闭后恢复旧创建的代码。
|
||||
Reference in New Issue
Block a user