按 PRD 2.3/2.4/2.5 落地套餐退款的方式矩阵与原路渠道退款: - 退款申请派生并冻结权威实收金额(线上取原成功支付记录,钱包/线下取订单实际收款), 提交人不可填写或修改;按来源支付方式生成可选方式矩阵并在创建、提交、执行前重复校验。 - 审批切换为「每次提交一条不可变审批尝试记录 + 独立企业微信审批实例」,业务标识取尝试 记录主键;终态消费按尝试记录优先、退款申请兜底双读,兼容存量无实例与已关联实例申请。 新增活动退款部分唯一索引 (order_id) WHERE status IN (1,5,6)。 - 本地人工终审保持既有开关,补齐通过入口的 approval_instance_id IS NULL 守卫,使三个 入口一致拒绝已关联审批实例的申请;重提按尝试模式重写(仅已拒绝/已退回/原路失败且无异常)。 - 权益时点:企微通过事务写退款终态、按方式确定的订单态、钱包回款、员工账单冲销与可靠 失效事实;套餐失效/接续/停机仍由既有可靠机制最终一致执行,不把外部调用放入资金事务。 订单支付状态按方式置位:凭证退款与退回原钱包在企微通过时置已退款,原路须渠道明确成功。 - 按官方契约实现微信直连 v3、微信 v2(双向证书)、富友(/commonRefund 与 /refundQuery)、 支付宝四类原路退款;能力只由服务商类型与退款必需凭证完整性决定,无人工开关。 渠道请求号在提交时冻结到尝试记录,并以 channel_submitted_at 条件认领保证资金动作至多 提交一次(重复投递只查询不二次提交);不向任何渠道传递退款结果通知地址。 - 新增 refund:channel:recovery 恢复任务只查询回填;本地查询窗口超期(富友 72 小时、 微信 v2 7 天)转原路退款失败、渠道状态已失败、分类超时未知并置异常转人工,不放行自动 重提以避免重复退款。 - 同步退款 DTO/导出/审计资源与审计查询关联、商户凭证文档,并修正 fuiou 集成契约文档。 迁移 000218(退款尝试与渠道退款事实)、000219(微信 v2 客户端证书凭证)成对提供, 未修改既有迁移;测试库 junhong_cmp_test 完成 up/down/up 与行为核对,未调用真实渠道。
9.1 KiB
MODIFIED Requirements
Requirement: 商户与微信授权配置管理
系统 SHALL 将实际收款商户与微信授权配置分离。一个商户 MUST 仅对应 wechat 或 alipay 一种支付方式,并保存名称、支付方式、服务商类型、商户号或应用标识、敏感凭证、状态和备注;微信直连与富友均为微信支付商户。创建或更新商户时 credentials MUST 为扁平 JSON 对象,必填键由支付方式与服务商类型组合确定:wechat/wechat 为 wx_mch_id、wx_api_v3_key、wx_cert_content、wx_key_content、wx_serial_no、wx_notify_url;wechat/wechat_v2 为 wx_mch_id、wx_api_v2_key、wx_notify_url,并可选 wx_client_cert_content、wx_client_key_content(API 客户端证书与私钥,为 v2 原路退款的双向证书所需);wechat/fuiou 为 fy_mchnt_cd、fy_ins_cd、fy_term_id、fy_private_key、fy_public_key、fy_api_url、fy_notify_url;alipay/alipay 为 ali_app_id、ali_private_key、ali_public_key、ali_notify_url、ali_return_url。凭证值 MUST 为字符串,仅可选的 ali_production(布尔)与 ali_pay_expire_minutes(整数)例外;merchant_identity MUST 分别等于 wx_mch_id、fy_mchnt_cd 或 ali_app_id。平台最多存在一个启用的微信授权配置,该配置保存 C 端公众号 H5/JSSDK、小程序登录所需参数,C 端微信登录、OpenID 和微信支付 AppID MUST 只读取该配置。
超级管理员和平台用户可创建、编辑、启用、停用商户、商户池和微信授权配置,其他角色无管理入口。仅上述角色的专用管理列表和详情响应可返回完整凭证;日志、审计快照、错误、导出、支付快照和其他业务响应 MUST NOT 保存或返回敏感凭证。被支付单引用的商户 MUST NOT 删除且其支付方式、服务商类型、商户号/应用标识不得修改;未被引用商户仅可移出所有商户池并经二次确认删除。停用只影响新支付单,历史支付的回调、查单和原路退款仍使用该商户当前凭证;原路退款的实际渠道调用由 order-refund-exchange 能力按该商户当前凭证执行,本能力 MUST NOT 保存渠道退款流水或推进退款状态。可退性只依据服务商类型与该服务商类型退款必需凭证的完整性判定,本能力 MUST NOT 提供人工退款能力开关。wechat/wechat_v2 的 wx_client_cert_content 与 wx_client_key_content 为可选键:不填不影响该商户的支付、查单与回调,只使原路退款按其凭证完整性判定为不可用。
Scenario: 受引用商户停用
- WHEN 管理员停用已被支付单命中的商户
- THEN 新支付单不再选择该商户,已命中支付单的回调、查询和原路退款仍按该商户处理
Scenario: 非管理角色读取配置
- WHEN 不具备超级管理员或平台用户身份的账号请求商户或微信授权配置
- THEN 系统拒绝访问且不返回任何凭证或身份字段
Scenario: 凭证轮换后处理历史支付
- GIVEN 已被支付单引用的商户或当前微信授权配置完成凭证更新
- WHEN 更新提交后创建支付、处理回调、查单或原路退款
- THEN 系统只使用更新后的当前有效凭证,不得继续使用更新前的缓存凭证
Scenario: 凭证键不完整或身份不一致被拒
- WHEN 管理员创建或更新商户时提交缺少该支付方式与服务商类型必填键的
credentials,或merchant_identity与凭证中的商户号或应用标识不一致 - THEN 系统拒绝写入并返回参数错误,不创建或修改商户记录
Scenario: 商户凭证不足以执行退款
- GIVEN 已被支付单命中的商户缺少其服务商类型执行原路退款所需的凭证
- WHEN 系统判定该支付单原路退款的可退性
- THEN 判定结果为不可用且只允许客户收款信息退款,系统不因该结果新增商户凭证键或人工退款开关
Requirement: 新支付商户快照与历史兼容
C 端套餐购买、C 端资产钱包充值及代理在线预存款充值 SHALL 按支付方式通过对应启用商户池选择实际商户;后台线下订单和钱包余额支付 MUST NOT 经过商户池。代理在线充值的全局允许范围、其与商户池方式的交集及对外方式查询由对应代理自充能力定义;本能力在方式已获准后负责实际商户选择,并在无可用商户时拒绝创建。每笔通过商户池创建的支付单 MUST 保存商户 ID、商户名称/支付方式/服务商类型/商户号或应用标识快照、商户池 ID/名称快照、轮询方式快照及统计世代快照,但不得复制敏感凭证。支付、回调验签、查单和原路退款读取该实际商户当前凭证;服务商类型和退款必需凭证完整性只用于原路退款路径的可退性判定,不提供人工开关;实际渠道退款调用与退款终态由 order-refund-exchange 能力实现,本能力 MUST NOT 发起渠道退款请求。
上线迁移在存在唯一当前生效综合支付配置时复制完整凭证:具备完整凭证的微信/支付宝方式创建商户和各自单成员启用池,微信授权字段完整时创建全局微信授权配置;不完整的支付方式不建池,授权字段不完整时不建授权配置并使相关 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 系统保留该兼容基线;富友原路退款由
order-refund-exchange能力按/commonRefund与/refundQuery契约单独实现,未第三方实测可以记录,但不得作为实施、验证或归档阻塞
Scenario: 维护者指定测试环境的配置迁移验证
- GIVEN 维护者指定的
junhong_cmp_testPostgreSQL、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处理回调、查询和退款,不自动删除旧配置或将其改挂商户