docs(finance): define credit and wallet hold contract

This commit is contained in:
2026-09-05 11:54:56 +08:00
parent d6472c48b0
commit 08a80e7b44
3 changed files with 132 additions and 7 deletions
+2 -2
View File
@@ -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 冒烟和领导页面验收仍需单独执行
+129 -4
View File
@@ -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 }