From 08a80e7b44015fc50d84dc2f075814b60a8b7c45 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Sat, 5 Sep 2026 11:54:56 +0800 Subject: [PATCH] docs(finance): define credit and wallet hold contract --- 开发环境独立任务协作登记.md | 2 +- 财务管理/README.md | 4 +- 财务管理/openapi/finance-wallet-v1.yaml | 133 +++++++++++++++++++++++- 3 files changed, 132 insertions(+), 7 deletions(-) diff --git a/开发环境独立任务协作登记.md b/开发环境独立任务协作登记.md index 28fa204..68264d7 100644 --- a/开发环境独立任务协作登记.md +++ b/开发环境独立任务协作登记.md @@ -47,7 +47,7 @@ | 工作包 | 执行任务 | 写入范围 | 交付目标 | 依赖与集成边界 | |---|---|---|---|---| -| `WALLET-BE-FINANCE` | 【开发总控】全流程开发 | `baiyue-firstleg/finance/`、Finance OpenAPI、模块 SQL 与统一增量 SQL、必要 Finance/File 内部客户端 | 新增客户资金账户、充值申请和资金流水主账;提供客户账户/流水/充值申请接口及平台账户列表/详情、待审充值、审核、手工充值/扣款接口;实现客户隔离、金额校验、权限、幂等、乐观并发与测试 | Finance 拥有余额和流水;付款截图经 Finance 调用 File 内部 `FINANCE_VOUCHER` 保存;不接支付回调、不接订单消费 | +| `WALLET-BE-FINANCE` | 【开发总控】全流程开发 | `baiyue-firstleg/finance/`、Finance OpenAPI、模块 SQL 与统一增量 SQL、必要 Finance/File 内部客户端 | 新增客户资金账户、充值申请、授信额度、资金冻结和流水主账;提供客户账户/流水/充值申请接口及平台账户列表/详情、待审充值、审核、手工充值/扣款/授信接口,并提供业务模块使用的冻结、结算和释放内部接口;实现客户隔离、金额校验、权限、服务令牌、幂等、乐观并发与测试 | Finance 拥有余额、授信、冻结和流水;扣款先使用实际资金再使用授信,充值先归还已用授信;付款截图经 Finance 调用 File 内部 `FINANCE_VOUCHER` 保存;不接第三方支付回调,具体一件代发业务调用后续接线 | | `WALLET-FE-CLIENT` | 【开发全流程前端】Client | `apps/client-web/src/modules/finance/` 及该模块路由、导航和测试 | 新增客户资金账户页,显示实际/冻结/可用余额和流水;按原型完成金额选择、微信/支付宝静态二维码、付款截图上传、提交待审核及状态反馈 | 客户范围只取登录态;只调用 `/api/client/finance/v1/**`,无二维码配置时明确不可提交,不模拟支付成功 | | `WALLET-FE-ADMIN` | 【开发全流程前端】Admin | `apps/admin-web/src/modules/finance/` 及对应测试 | 将客户账户页收敛为三余额账户;详情中待审核客户充值置顶并提供审核,保留流水;列表/详情提供手工充值和手工扣款,操作列固定首列 | 只消费 Finance 正式 API;审核、充值、扣款分别受写权限控制;不在前端计算或兜底余额 | | `WALLET-BE-IAM` | 【开发全流程后端】IAM权限 | `baiyue-firstleg/iam/` 及必要权限初始化 SQL、OpenAPI 和测试 | 新增并初始化客户账户查看/充值申请和平台充值审核/账户调账权限,保持列表只读与写操作分离 | 企业负责人默认获得客户账户查看和充值申请权限;平台财务角色按现有初始化规则获得审核与调账权限,不修改 Finance 业务 | diff --git a/财务管理/README.md b/财务管理/README.md index 2684465..0154479 100644 --- a/财务管理/README.md +++ b/财务管理/README.md @@ -2,9 +2,9 @@ ## 当前确认:一件代发客户资金账户(2026-09-05) -当前对话已将资金账户作为一件代发的前置节点重新纳入范围,并覆盖本文后续历史章节中“钱包不做”的旧结论。首批固定为 CNY,满足 `实际余额 = 冻结金额 + 可用余额`;冻结金额本批只持久化和展示,暂不接消费。 +当前对话已将资金账户作为一件代发的前置节点重新纳入范围,并覆盖本文后续历史章节中“钱包不做”的旧结论。首批固定为 CNY。账户分别保存实际余额、冻结金额、授信额度、已用授信和冻结授信;`总可用余额 = 实际余额 + 授信额度 - 已用授信 - 冻结金额`,`可用授信 = 授信额度 - 已用授信 - 冻结授信`。扣款及业务冻结先使用未冻结的实际资金,再使用授信;充值先归还已用授信,剩余金额增加实际余额。 -客户选择固定或自定义金额,查看后台配置的微信/支付宝静态收款码,付款后上传 JPG/PNG 截图形成待审核申请。平台在客户账户明细顶部审核或驳回,也可手工充值、扣款;每次变动写不可变账户流水。审核、账户更新、流水和幂等结果在 Finance 单事务内完成,文件内容仍归 File 服务。接口契约见 [客户资金账户 OpenAPI](openapi/finance-wallet-v1.yaml)。 +客户选择固定或自定义金额,查看后台配置的微信/支付宝静态收款码,付款后上传 JPG/PNG 截图形成待审核申请。平台在客户账户明细顶部审核或驳回,也可手工充值、扣款和设置客户授信额度;每次变动写不可变账户流水。新账户授信额度读取 `baiyue.finance.wallet.default-credit-limit`,默认 `0.00`,平台设置的客户额度随后独立持久化。Finance 还提供仅服务令牌可访问、按业务类型和业务单号幂等的冻结、结算和释放接口,供一件代发等模块接入。审核、账户更新、流水、冻结记录和幂等结果在 Finance 单事务内完成,文件内容仍归 File 服务。接口契约见 [客户资金账户 OpenAPI](openapi/finance-wallet-v1.yaml)。 > 盘点日期:2026-08-10 > 当前状态:F0/F1 已有 Finance 服务、数据库、权限、Admin/Client 页面和基础接口;订单与客户 11 个 P0 operation 的目标契约已冻结,本地实现验收按独立 G1-G4 证据矩阵均为 `PASS`;真实 MySQL、测试环境 HTTP 冒烟和领导页面验收仍需单独执行 diff --git a/财务管理/openapi/finance-wallet-v1.yaml b/财务管理/openapi/finance-wallet-v1.yaml index 4ab8178..01e4cbf 100644 --- a/财务管理/openapi/finance-wallet-v1.yaml +++ b/财务管理/openapi/finance-wallet-v1.yaml @@ -1,14 +1,22 @@ openapi: 3.0.3 info: - title: 客户资金账户与充值 API - version: 1.0.0 - description: 静态收款码、付款截图申请、平台审核及手工充值扣款。当前币种固定 CNY,消费暂不接入。 + title: 客户资金账户、授信与充值 API + version: 1.1.0 + description: | + CNY 资金账户支持静态收款码、付款截图申请、平台审核、手工充扣、授信额度及业务冻结/结算/释放。 + 总可用余额 = 实际余额 + 授信额度 - 已用授信 - 冻结金额;业务扣款先使用未冻结的实际资金,再使用授信。 + 充值先归还已用授信,剩余部分增加实际余额。新账户授信额度由 + `baiyue.finance.wallet.default-credit-limit` 初始化,默认 `0.00`,平台可按客户调整。 paths: /api/client/finance/v1/account: get: summary: 查询当前客户资金账户 responses: - '200': { description: 成功 } + '200': + description: 成功 + content: + application/json: + schema: { $ref: '#/components/schemas/WalletAccountEnvelope' } /api/client/finance/v1/account/ledger: get: summary: 查询当前客户账户流水 @@ -91,6 +99,71 @@ paths: schema: { $ref: '#/components/schemas/AccountAdjustmentRequest' } responses: '200': { description: 成功 } + /api/platform/finance/v1/accounts/{customerId}/credit-limit: + put: + summary: 设置客户授信额度 + parameters: + - $ref: '#/components/parameters/CustomerId' + - $ref: '#/components/parameters/IdempotencyKey' + requestBody: + required: true + content: + application/json: + schema: { $ref: '#/components/schemas/CreditLimitRequest' } + responses: + '200': { description: 成功并生成授信调整流水 } + /internal/finance/v1/wallet/accounts/{customerId}: + get: + summary: 业务模块查询客户资金账户 + parameters: + - $ref: '#/components/parameters/InternalServiceToken' + - $ref: '#/components/parameters/CustomerId' + responses: + '200': { description: 成功 } + /internal/finance/v1/wallet/accounts/{customerId}/holds: + post: + summary: 按业务单冻结资金,先冻结实际资金再冻结授信 + parameters: + - $ref: '#/components/parameters/InternalServiceToken' + - $ref: '#/components/parameters/IdempotencyKey' + - $ref: '#/components/parameters/CustomerId' + requestBody: + required: true + content: + application/json: + schema: { $ref: '#/components/schemas/WalletFreezeRequest' } + responses: + '200': { description: 成功;同一客户、业务类型和业务单号只允许一条冻结记录 } + /internal/finance/v1/wallet/accounts/{customerId}/holds/{businessType}/{businessNo}/settle: + post: + summary: 结算已冻结资金 + parameters: + - $ref: '#/components/parameters/InternalServiceToken' + - $ref: '#/components/parameters/IdempotencyKey' + - $ref: '#/components/parameters/CustomerId' + - $ref: '#/components/parameters/BusinessType' + - $ref: '#/components/parameters/BusinessNo' + requestBody: + content: + application/json: + schema: { $ref: '#/components/schemas/WalletHoldActionRequest' } + responses: + '200': { description: 成功;实际冻结转为实际扣减,授信冻结转为已用授信 } + /internal/finance/v1/wallet/accounts/{customerId}/holds/{businessType}/{businessNo}/release: + post: + summary: 释放已冻结资金 + parameters: + - $ref: '#/components/parameters/InternalServiceToken' + - $ref: '#/components/parameters/IdempotencyKey' + - $ref: '#/components/parameters/CustomerId' + - $ref: '#/components/parameters/BusinessType' + - $ref: '#/components/parameters/BusinessNo' + requestBody: + content: + application/json: + schema: { $ref: '#/components/schemas/WalletHoldActionRequest' } + responses: + '200': { description: 成功;实际资金和授信恢复为可用 } components: parameters: IdempotencyKey: @@ -98,6 +171,11 @@ components: in: header required: true schema: { type: string, maxLength: 128 } + InternalServiceToken: + name: X-Internal-Service-Token + in: header + required: true + schema: { type: string, writeOnly: true } CustomerId: name: customerId in: path @@ -108,7 +186,35 @@ components: in: path required: true schema: { type: string, pattern: '^[1-9][0-9]*$' } + BusinessType: + name: businessType + in: path + required: true + schema: { type: string, pattern: '^[A-Z][A-Z0-9_]{1,31}$' } + BusinessNo: + name: businessNo + in: path + required: true + schema: { type: string, minLength: 1, maxLength: 128 } schemas: + WalletAccountEnvelope: + type: object + properties: + data: { $ref: '#/components/schemas/WalletAccount' } + WalletAccount: + type: object + required: [currencyCode, actualBalance, frozenBalance, availableBalance, creditLimit, creditUsed, creditFrozen, availableCredit, version] + properties: + currencyCode: { type: string, enum: [CNY] } + actualBalance: { type: number, format: decimal, minimum: 0 } + frozenBalance: { type: number, format: decimal, minimum: 0, description: 实际资金冻结与授信冻结合计 } + availableBalance: { type: number, format: decimal, minimum: 0, description: 实际可用资金与可用授信合计 } + creditLimit: { type: number, format: decimal, minimum: 0 } + creditUsed: { type: number, format: decimal, minimum: 0 } + creditFrozen: { type: number, format: decimal, minimum: 0 } + availableCredit: { type: number, format: decimal, minimum: 0 } + version: { type: integer, minimum: 0 } + updatedAt: { type: string, format: date-time, nullable: true } RechargeReviewRequest: type: object required: [decision, expectedVersion] @@ -127,3 +233,22 @@ components: referenceNo: { type: string, maxLength: 128 } remark: { type: string, maxLength: 500 } expectedAccountVersion: { type: integer, minimum: 0 } + CreditLimitRequest: + type: object + required: [creditLimit, expectedAccountVersion] + properties: + creditLimit: { type: number, format: decimal, minimum: 0 } + remark: { type: string, maxLength: 500 } + expectedAccountVersion: { type: integer, minimum: 0 } + WalletFreezeRequest: + type: object + required: [amount, businessType, businessNo] + properties: + amount: { type: number, format: decimal, minimum: 0.01 } + businessType: { type: string, minLength: 1, maxLength: 64 } + businessNo: { type: string, minLength: 1, maxLength: 128 } + remark: { type: string, maxLength: 500 } + WalletHoldActionRequest: + type: object + properties: + remark: { type: string, maxLength: 500 }