Files
junhong_cmp_fiber/openspec/changes/add-payment-config-management/specs/agent-recharge/spec.md
huang 63ca12393b docs: 新增 OpenSpec 提案 add-payment-config-management
包含 proposal.md、design.md、tasks.md 及各模块 spec 文件(微信配置管理、富友支付、代理充值、订单支付、资产充值适配、微信支付留桩)

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-16 23:30:39 +08:00

15 KiB
Raw Blame History

代理充值管理 API 规范

ADDED Requirements


Requirement: 创建代理充值订单

接口描述:代理或平台账号发起代理余额钱包充值,创建充值订单。

HTTP 方法与路径

POST /api/admin/agent-recharges

鉴权

  • 需要登录态Bearer Token
  • 代理账号只能为自己所属店铺的主钱包wallet_type=main充值
  • 平台账号:可指定任意店铺

请求体示例(在线充值 - 微信)

{
  "shop_id": 101,
  "amount": 50000,
  "payment_method": "wechat"
}

请求体示例(线下充值 - 仅平台)

{
  "shop_id": 101,
  "amount": 200000,
  "payment_method": "offline"
}

请求字段说明

字段名 类型 必填 说明
shop_id integer 目标店铺 ID。代理账号只能填写自己所属店铺 ID
amount integer 充值金额单位。范围10000100000000即 100 元100 万元)
payment_method string 支付方式。可选值:wechat(在线微信支付)、offline(线下转账,仅平台可用)

业务规则

  • amount 最小值为 AgentRechargeMinAmount10000 分 = 100 元),最大值为 AgentRechargeMaxAmount100000000 分 = 100 万元)
  • payment_method=wechat 时,系统根据当前激活的支付配置自动路由至微信直连或富友通道,并记录 payment_config_id客户端发起支付的具体流程本期暂不实现Stub
  • payment_method=offline 仅平台账号可使用,代理账号调用此方式将返回 1005 CodeForbidden
  • 订单创建后状态为 1(待支付)
  • 充值单号前缀为 ARCH,全局唯一

成功响应示例

{
  "code": 0,
  "msg": "success",
  "data": {
    "id": 88,
    "recharge_no": "ARCH20260316100001",
    "shop_id": 101,
    "amount": 50000,
    "payment_method": "wechat",
    "payment_channel": "wechat_direct",
    "payment_config_id": 3,
    "status": 1,
    "created_at": "2026-03-16T10:00:00+08:00"
  },
  "timestamp": "2026-03-16T10:00:00+08:00"
}

响应字段说明

字段名 类型 说明
id integer 充值记录 ID
recharge_no string 充值单号ARCH 前缀)
shop_id integer 店铺 ID
amount integer 充值金额(分)
payment_method string 支付方式
payment_channel string 实际支付通道wechat_direct / fuyou / offline
payment_config_id integer|null 关联的支付配置 ID线下充值为 null
status integer 订单状态1=待支付2=已完成3=已取消
created_at string 创建时间RFC3339

错误响应示例

金额超出范围:

{
  "code": 1001,
  "msg": "充值金额超出允许范围100元~100万元",
  "data": null,
  "timestamp": "2026-03-16T10:00:00+08:00"
}

代理账号使用线下充值:

{
  "code": 1005,
  "msg": "只有平台账号可以使用线下充值",
  "data": null,
  "timestamp": "2026-03-16T10:00:00+08:00"
}

钱包不存在:

{
  "code": 1053,
  "msg": "钱包不存在",
  "data": null,
  "timestamp": "2026-03-16T10:00:00+08:00"
}

无可用支付配置:

{
  "code": 1175,
  "msg": "当前无可用的支付配置,请联系管理员",
  "data": null,
  "timestamp": "2026-03-16T10:00:00+08:00"
}

越权访问(代理操作他人店铺):

{
  "code": 1005,
  "msg": "无权限操作该资源或资源不存在",
  "data": null,
  "timestamp": "2026-03-16T10:00:00+08:00"
}

Requirement: 线下充值确认

接口描述:平台账号确认线下转账已到账,完成充值并为代理钱包增加余额。

HTTP 方法与路径

POST /api/admin/agent-recharges/:id/offline-pay

鉴权

  • 需要登录态Bearer Token
  • 仅平台账号可调用,其他账号类型返回 1005 CodeForbidden

请求体示例

{
  "operation_password": "Abc123456"
}

请求字段说明

字段名 类型 必填 说明
operation_password string 操作密码,用于二次身份验证

路径参数说明

参数名 类型 说明
id integer 充值记录 ID

业务规则

  • 操作密码验证失败返回 1043 CodeInvalidOldPassword
  • 充值记录必须存在且 payment_method=offline,否则返回 1121 CodeRechargeNotFound
  • 充值记录状态必须为 1(待支付),否则返回 1050 CodeInvalidStatus
  • 确认成功后:
    1. 充值记录状态更新为 2(已完成),记录 paid_atcompleted_at
    2. 代理主钱包余额增加对应金额(使用乐观锁 version 字段防并发)
    3. 创建钱包流水记录
    4. 记录审计日志(操作人、操作前后数据)

成功响应示例

{
  "code": 0,
  "msg": "success",
  "data": {
    "id": 88,
    "recharge_no": "ARCH20260316100001",
    "shop_id": 101,
    "amount": 200000,
    "payment_method": "offline",
    "payment_channel": "offline",
    "payment_config_id": null,
    "status": 2,
    "paid_at": "2026-03-16T11:00:00+08:00",
    "completed_at": "2026-03-16T11:00:00+08:00",
    "created_at": "2026-03-16T10:00:00+08:00"
  },
  "timestamp": "2026-03-16T11:00:00+08:00"
}

错误响应示例

操作密码错误:

{
  "code": 1043,
  "msg": "操作密码错误",
  "data": null,
  "timestamp": "2026-03-16T11:00:00+08:00"
}

充值记录不存在:

{
  "code": 1121,
  "msg": "充值记录不存在",
  "data": null,
  "timestamp": "2026-03-16T11:00:00+08:00"
}

充值记录状态不允许操作:

{
  "code": 1050,
  "msg": "当前充值记录状态不允许此操作",
  "data": null,
  "timestamp": "2026-03-16T11:00:00+08:00"
}

非平台账号调用:

{
  "code": 1005,
  "msg": "只有平台账号可以使用线下充值",
  "data": null,
  "timestamp": "2026-03-16T11:00:00+08:00"
}

Requirement: 代理充值查询

接口一:充值记录列表

接口描述:分页查询代理充值记录,支持按店铺、状态、日期范围过滤。

HTTP 方法与路径

GET /api/admin/agent-recharges

鉴权

  • 需要登录态Bearer Token
  • 代理账号:只能查看自己所属店铺的充值记录
  • 平台账号:可查看所有店铺的充值记录

请求参数Query String

GET /api/admin/agent-recharges?page=1&page_size=20&shop_id=101&status=2&start_date=2026-03-01&end_date=2026-03-31

请求参数说明

参数名 类型 必填 说明
page integer 页码,默认 1
page_size integer 每页条数,默认 20最大 100
shop_id integer 按店铺 ID 过滤(平台账号可用)
status integer 按状态过滤1=待支付2=已完成3=已取消
start_date string 创建时间起始日期,格式 YYYY-MM-DD
end_date string 创建时间截止日期,格式 YYYY-MM-DD

成功响应示例

{
  "code": 0,
  "msg": "success",
  "data": {
    "total": 56,
    "page": 1,
    "page_size": 20,
    "list": [
      {
        "id": 88,
        "recharge_no": "ARCH20260316100001",
        "shop_id": 101,
        "shop_name": "测试店铺A",
        "amount": 50000,
        "payment_method": "wechat",
        "payment_channel": "wechat_direct",
        "payment_config_id": 3,
        "status": 2,
        "paid_at": "2026-03-16T10:05:00+08:00",
        "completed_at": "2026-03-16T10:05:00+08:00",
        "created_at": "2026-03-16T10:00:00+08:00"
      },
      {
        "id": 87,
        "recharge_no": "ARCH20260315090001",
        "shop_id": 101,
        "shop_name": "测试店铺A",
        "amount": 200000,
        "payment_method": "offline",
        "payment_channel": "offline",
        "payment_config_id": null,
        "status": 2,
        "paid_at": "2026-03-15T11:00:00+08:00",
        "completed_at": "2026-03-15T11:00:00+08:00",
        "created_at": "2026-03-15T09:00:00+08:00"
      }
    ]
  },
  "timestamp": "2026-03-16T12:00:00+08:00"
}

列表项字段说明

字段名 类型 说明
id integer 充值记录 ID
recharge_no string 充值单号
shop_id integer 店铺 ID
shop_name string 店铺名称
amount integer 充值金额(分)
payment_method string 支付方式
payment_channel string 实际支付通道
payment_config_id integer|null 关联支付配置 ID
status integer 状态1=待支付2=已完成3=已取消
paid_at string|null 支付时间
completed_at string|null 完成时间
created_at string 创建时间

错误响应示例

参数错误:

{
  "code": 1001,
  "msg": "参数验证失败",
  "data": null,
  "timestamp": "2026-03-16T12:00:00+08:00"
}

接口二:充值记录详情

接口描述:查询单条充值记录的完整详情。

HTTP 方法与路径

GET /api/admin/agent-recharges/:id

鉴权

  • 需要登录态Bearer Token
  • 代理账号:只能查看自己所属店铺的充值记录,否则返回 1121 CodeRechargeNotFound
  • 平台账号:可查看任意充值记录

路径参数说明

参数名 类型 说明
id integer 充值记录 ID

成功响应示例

{
  "code": 0,
  "msg": "success",
  "data": {
    "id": 88,
    "recharge_no": "ARCH20260316100001",
    "shop_id": 101,
    "shop_name": "测试店铺A",
    "agent_wallet_id": 55,
    "amount": 50000,
    "payment_method": "wechat",
    "payment_channel": "wechat_direct",
    "payment_config_id": 3,
    "payment_transaction_id": "wx_txn_20260316_abc123",
    "status": 2,
    "paid_at": "2026-03-16T10:05:00+08:00",
    "completed_at": "2026-03-16T10:05:00+08:00",
    "created_at": "2026-03-16T10:00:00+08:00",
    "updated_at": "2026-03-16T10:05:00+08:00"
  },
  "timestamp": "2026-03-16T12:00:00+08:00"
}

详情字段说明

字段名 类型 说明
id integer 充值记录 ID
recharge_no string 充值单号
shop_id integer 店铺 ID
shop_name string 店铺名称
agent_wallet_id integer 代理钱包 ID
amount integer 充值金额(分)
payment_method string 支付方式
payment_channel string 实际支付通道
payment_config_id integer|null 关联支付配置 ID
payment_transaction_id string|null 第三方支付流水号
status integer 状态1=待支付2=已完成3=已取消
paid_at string|null 支付时间
completed_at string|null 完成时间
created_at string 创建时间
updated_at string 最后更新时间

错误响应示例

充值记录不存在或无权限:

{
  "code": 1121,
  "msg": "充值记录不存在",
  "data": null,
  "timestamp": "2026-03-16T12:00:00+08:00"
}

Requirement: 代理充值回调处理

接口描述:接收第三方支付平台(微信直连 / 富友)的异步支付结果通知,完成充值订单状态更新和钱包余额增加。

HTTP 方法与路径

回调地址由支付配置中的 notify_url 字段决定,格式示例:

POST /api/payment/callback/agent-recharge/{payment_channel}

其中 payment_channelwechat_directfuyou

鉴权

  • 无需登录态
  • 通过签名验证确认请求来源合法性

处理流程

1. 接收回调请求
2. 根据 payment_channel 确定验签方式
3. 通过 recharge_no充值单号查找充值记录
4. 幂等性检查:若记录状态已为 2已完成直接返回成功
5. 使用充值记录中的 payment_config_id 查找对应支付配置
6. 使用支付配置的密钥验证签名
7. 验签通过后,在事务中执行:
   a. 更新充值记录状态为 2已完成记录 payment_transaction_id、paid_at、completed_at
   b. 代理主钱包余额增加充值金额(乐观锁 version 字段防并发)
   c. 创建钱包流水记录(类型:充值入账)
8. 返回支付平台要求的成功响应格式

幂等性保障

  • 使用充值记录状态作为幂等判断依据(状态条件更新:WHERE status = 1
  • RowsAffected == 0 时说明已被处理,直接返回成功,不重复入账

签名验证

  • 根据充值记录的 payment_config_id 查找对应支付配置
  • 使用该配置的密钥(api_key / app_secret)按对应通道规则验签
  • 验签失败时记录错误日志,返回失败响应(不更新订单状态)

回调响应

  • 微信直连:返回 {"code": "SUCCESS", "message": "成功"}
  • 富友:按富友协议返回对应成功标识
  • 处理失败时返回对应通道的失败标识,触发第三方平台重试

异常处理

  • 充值记录不存在:记录警告日志,返回失败(触发重试,等待数据一致)
  • 签名验证失败:记录错误日志(含完整请求体),返回失败
  • 钱包余额更新失败(乐观锁冲突):最多重试 3 次,仍失败则记录告警日志并返回失败

Requirement: 权限控制

账号类型与操作权限矩阵

操作 平台账号 代理账号 企业账号
创建充值订单(在线) 任意店铺 仅自己店铺
创建充值订单(线下) 任意店铺
线下充值确认
查询充值列表 全部 仅自己店铺
查询充值详情 全部 仅自己店铺

越权防护规则

  1. 路由层:企业账号访问代理充值相关接口,统一返回 1005 CodeForbidden
  2. Service 层
    • 代理账号创建充值时,验证 shop_id 必须属于自己所属店铺
    • 代理账号查询详情时,验证充值记录的 shop_id 必须属于自己所属店铺
  3. 越权统一响应:不区分"不存在"和"无权限",统一返回 1005 或对应资源不存在错误,防止信息泄露

线下充值操作密码

  • 平台账号执行线下充值确认时,必须提供操作密码
  • 操作密码验证失败返回 1043 CodeInvalidOldPassword
  • 操作密码不在响应中返回,不记录到日志明文中

数据模型补充说明

tb_agent_recharge_record 新增字段

字段名 类型 可空 说明
payment_config_id bigint 关联支付配置 ID线下充值为 NULL在线充值记录实际使用的支付配置

充值状态枚举

含义
1 待支付(订单已创建,等待支付)
2 已完成(支付成功,余额已到账)
3 已取消(超时未支付或主动取消)

支付方式枚举

含义
wechat 微信在线支付(自动路由至微信直连或富友)
offline 线下转账(仅平台账号可用)

支付通道枚举

含义
wechat_direct 微信直连通道
fuyou 富友通道
offline 线下转账