feat(order-v3): 微信商户对账管理端 6 端点 changelog(#8690 / PR #8703)
changelog-filename-gate / validate (push) Failing after 1s

对账汇总/逐笔记录/差异列表/差异详情/处理差异/手动补跑,5824 段错误码 + 4 组枚举全量内联
这个提交包含在:
yaosutu
2026-10-02 11:22:55 +08:00
父节点 cb0ec7f47d
当前提交 26c43ee57c
@@ -0,0 +1,437 @@
---
schema: "hl-changelog/v2"
ticket: "8690"
title: "微信商户对账:账单下载解析比对落库+差异处理+手动补跑(6 端点)"
consumer: "admin"
author: "yst(GIT)"
change_type: "新增接口"
backend_status: "merged"
gateway_status: "not_required"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: ""
updated_at: "2026-10-02"
base: "dev-v3"
---
# 【新增接口·管理后台】微信商户对账(6 端点)(#8690)
> **PR**: #8703 | **服务**: hl-order-service-v3(8086) | **更新时间**: 2026-10-02
## 1. 接口背景
微信支付此前只有「支付回调」这一条正向链路:微信回调成功 → 本地记成功流水。一旦回调丢失、金额异常或微信侧状态变化,本地无任何反向核对手段,资金敞口不可见。
本次建设**微信商户对账**能力:每日 T-1 自动拉取微信商户**交易账单(tradebill)+ 资金账单(fundflowbill)**,下载解析后与本地支付流水逐笔比对落库,差异打标并通过 FINANCE 站内信告警,**不自动调账**(差异一律人工核实处理)。
本 changelog 覆盖管理后台消费的 **6 个新端点**(对账执行本体由定时任务触发,不在本接口面)。菜单归属:**财务管理 → 资金账户 → 微信商户对账**。单视图「对账汇总」台账,逐笔明细 / 差异走抽屉下钻(records / diffs 接口)。
## 2. 变更清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 对账汇总分页列表 | GET | /v3/admin/payment/wx-bill/summaries | 新增接口 | 主视图台账,每日每商户每账单类型一行 |
| 2 | 逐笔原始账单记录 | GET | /v3/admin/payment/wx-bill/records | 新增接口 | 汇总行「明细」抽屉数据源 |
| 3 | 差异分页列表 | GET | /v3/admin/payment/wx-bill/diffs | 新增接口 | 「看差异」抽屉数据源 |
| 4 | 差异详情 | GET | /v3/admin/payment/wx-bill/diffs/{diffId} | 新增接口 | 单条差异完整信息(含处理记录) |
| 5 | 处理差异 | POST | /v3/admin/payment/wx-bill/diffs/{diffId}/handle | 新增接口 | 财务人工确认/忽略 + 备注,不自动调账 |
| 6 | 手动触发对账补跑 | POST | /v3/admin/payment/wx-bill/reconcile/run | 新增接口 | 运维补账入口,与定时任务同逻辑同锁 |
## 3. 接口详情
### 3.1 对账汇总分页列表 GET /v3/admin/payment/wx-bill/summaries
- **使用场景**:主视图台账。每行 = 一个商户 + 一个账单日期 + 一种账单类型的对账结论(总笔数/总金额/对平/差异/结果)。
- **认证**:需管理后台 JWT。
- **幂等性**:只读。
- **限流**:无。
### 3.2 逐笔原始账单记录 GET /v3/admin/payment/wx-bill/records
- **使用场景**:汇总行「明细」抽屉,看该商户该日微信账单原始逐笔(交易账单/资金账单统一出参,billType 区分)。
- **认证**:需管理后台 JWT。
- **幂等性**:只读。
- **限流**:无。
### 3.3 差异分页列表 GET /v3/admin/payment/wx-bill/diffs
- **使用场景**:「看差异」抽屉 / 差异工作台。支持按日期范围、差异类型、处理状态、商户号、账单类型过滤。
- **认证**:需管理后台 JWT。
- **幂等性**:只读。
- **限流**:无。
### 3.4 差异详情 GET /v3/admin/payment/wx-bill/diffs/{diffId}
- **使用场景**:差异行点开的详情(本地金额 vs 账单金额、差异说明、处理人/处理时间/备注)。
- **认证**:需管理后台 JWT。
- **幂等性**:只读。
- **限流**:无。
### 3.5 处理差异 POST /v3/admin/payment/wx-bill/diffs/{diffId}/handle
- **使用场景**:财务人工核实差异后标记「已确认」或「已忽略」并留备注。**只做标记,不自动调账**(调账动作走财务调账域,不在本接口)。
- **认证**:需管理后台 JWT。
- **幂等性**:**否**。仅 PENDING 状态可处理;重复处理报 582404(已处理不可重复处理),天然防重。
- **限流**:无。
### 3.6 手动触发对账补跑 POST /v3/admin/payment/wx-bill/reconcile/run
- **使用场景**:运维补账——定时任务失败 / 账单迟到后,手动对指定账单日期补跑一次。与 internal 定时任务**同逻辑、同一把分布式锁**(key=wx-bill-reconcile:{billDate})。
- **认证**:需管理后台 JWT。
- **幂等性**:**是**(业务侧)。重跑零重复:原始记录按键集只插缺失、汇总查到即覆盖、差异按指纹去重。但**同一账单日期对账执行中时**(锁被占用)不重复执行,返回提示文案且 data=null。
- **限流**:无。
## 4. 接口入参
### 4.1 对账汇总分页列表(Query)
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|------|------|------|------|----------|
| page | Integer | 否 | 页码,默认 1 | 最小 1 |
| pageSize | Integer | 否 | 每页条数,默认 20 | 1-100 |
| billDateFrom | Date | 否 | 账单日期起(yyyy-MM-dd) | — |
| billDateTo | Date | 否 | 账单日期止(yyyy-MM-dd) | — |
| mchId | String | 否 | 微信商户号(精确匹配) | — |
| billType | String | 否 | 账单类型 | 仅 TRADE / FUND_FLOW,非法值报参数校验错误 |
| reconcileStatus | String | 否 | 对账结果 | 仅 OK / HAS_DIFF / FETCH_FAILED,非法值报参数校验错误 |
### 4.2 逐笔原始账单记录(Query)
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|------|------|------|------|----------|
| page | Integer | 否 | 页码,默认 1 | 最小 1 |
| pageSize | Integer | 否 | 每页条数,默认 20 | 1-100 |
| billDate | Date | 否 | 账单日期(yyyy-MM-dd,单日) | — |
| mchId | String | 否 | 微信商户号(精确匹配) | — |
| billType | String | 否 | 账单类型 | 仅 TRADE / FUND_FLOW |
### 4.3 差异分页列表(Query)
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|------|------|------|------|----------|
| page | Integer | 否 | 页码,默认 1 | 最小 1 |
| pageSize | Integer | 否 | 每页条数,默认 20 | 1-100 |
| billDateFrom | Date | 否 | 账单日期起(yyyy-MM-dd) | — |
| billDateTo | Date | 否 | 账单日期止(yyyy-MM-dd) | — |
| mchId | String | 否 | 微信商户号(精确匹配) | — |
| billType | String | 否 | 账单类型 | 仅 TRADE / FUND_FLOW |
| diffType | String | 否 | 差异类型 | 仅 LOCAL_MISSING / BILL_MISSING / AMOUNT_MISMATCH / STATE_MISMATCH / FETCH_FAILED |
| handleStatus | String | 否 | 处理状态 | 仅 PENDING / CONFIRMED / IGNORED |
### 4.4 差异详情(Path)
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| diffId | Long | 是 | 差异ID(path) |
### 4.5 处理差异(Path + Body)
Path:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| diffId | Long | 是 | 差异ID(path) |
请求体:
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|------|------|------|------|----------|
| handleStatus | String | 是 | 处理状态 | 仅 CONFIRMED(已确认)/ IGNORED(已忽略),**不能传 PENDING** |
| remark | String | 否 | 处理备注 | 最长 255 字符 |
### 4.6 手动触发对账补跑(Body)
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|------|------|------|------|----------|
| billDate | Date | 是 | 账单日期(yyyy-MM-dd) | 建议传 T-1 或更早(微信当日账单 T+1 上午才生成) |
| mchId | String | 否 | 微信商户号 | 可空 = 全部已配置商户 |
| billType | String | 否 | 账单类型 | 仅 TRADE / FUND_FLOW;可空 = 交易+资金都跑 |
## 5. 出参(响应)
统一 Result 包装;分页为 PageResult(records / total / page / pageSize)。
> ⚠️ **Long 主键字符串化**:id(汇总/记录/差异)与 localTransactionId 均以 **JSON 字符串**返回(防 JS 精度丢失),前端按字符串处理,回传 path 参数时原样带回即可。handledBy 为普通数字。
### 5.1 对账汇总行 WxBillSummaryRespVO
| 字段 | 类型 | 说明 |
|------|------|------|
| id | String(Long) | 汇总ID |
| mchId | String | 微信商户号 |
| billDate | Date | 账单日期(yyyy-MM-dd) |
| billType | String | 账单类型:TRADE / FUND_FLOW |
| totalCount | Integer | 账单总笔数 |
| totalAmount | BigDecimal | 账单总金额(元) |
| matchedCount | Integer | 对平笔数 |
| diffCount | Integer | 差异笔数 |
| reconcileStatus | String | 对账结果:OK / HAS_DIFF / FETCH_FAILED |
| createTime | DateTime | 创建时间(yyyy-MM-dd HH:mm:ss) |
### 5.2 账单原始记录行 WxBillRecordRespVO
| 字段 | 类型 | 说明 |
|------|------|------|
| id | String(Long) | 记录ID |
| mchId | String | 微信商户号 |
| billDate | Date | 账单日期 |
| billType | String | 账单类型:TRADE / FUND_FLOW |
| wxTransactionId | String | 微信支付单号(交易账单有,资金账单可空) |
| outTradeNo | String | 商户单号(关联本地支付流水) |
| fundFlowId | String | 资金流水单号(资金账单有,交易账单可空) |
| tradeTime | DateTime | 交易/记账时间 |
| tradeType | String | 交易类型/业务类型(如 JSAPI) |
| tradeState | String | 交易状态/收支方向(如 SUCCESS) |
| amount | BigDecimal | 交易金额(元) |
| payerAmount | BigDecimal | 用户实付(元) |
| feeAmount | BigDecimal | 手续费(元) |
### 5.3 差异行 / 差异详情 WxBillDiffRespVO
| 字段 | 类型 | 说明 |
|------|------|------|
| id | String(Long) | 差异ID |
| mchId | String | 微信商户号 |
| billDate | Date | 账单日期 |
| diffType | String | 差异类型(见 6.2) |
| billType | String | 账单类型:TRADE / FUND_FLOW |
| outTradeNo | String | 商户单号 |
| wxTransactionId | String | 微信支付单号 |
| localAmount | BigDecimal | 本地金额(元),本地缺失类差异可空 |
| billAmount | BigDecimal | 账单金额(元),账单缺失类差异可空 |
| localTransactionId | String(Long) | 本地支付流水ID,本地缺失类差异可空 |
| diffDetail | String | 差异说明(人读文案) |
| handleStatus | String | 处理状态:PENDING / CONFIRMED / IGNORED |
| handleRemark | String | 处理备注,未处理为空 |
| handledBy | Long(数字) | 处理人ID,未处理为空 |
| handledAt | DateTime | 处理时间,未处理为空 |
| createTime | DateTime | 创建时间 |
处理差异接口的响应体同为 WxBillDiffRespVO(处理后的最新状态)。
### 5.4 手动触发对账执行结果 WxBillReconcileRunRespVO
| 字段 | 类型 | 说明 |
|------|------|------|
| billDate | Date | 对账账单日期 |
| merchantCount | Integer | 参与对账的商户数 |
| diffCount | Integer | 本次对账差异总笔数(含历史未清) |
| fetchFailedCount | Integer | 账单拉取失败的商户类型数 |
> ⚠️ **锁占用特例**:当日该账单日期对账正在执行中时,本接口返回 code=0、msg="当日对账正在执行中,请稍后重试"、data=null——属正常跳过,非错误,前端按提示文案展示即可。
## 6. 枚举 / 数据字典
### 6.1 billType(账单类型)
| 值 | 中文 | 说明 |
|----|------|------|
| TRADE | 交易账单 | 微信 tradebill,逐笔交易(含支付/退款) |
| FUND_FLOW | 资金账单 | 微信 fundflowbill,逐笔资金收支(含手续费/结算) |
### 6.2 diffType(差异类型)
| 值 | 中文 | 说明 |
|----|------|------|
| LOCAL_MISSING | 本地缺失 | 账单有该单(SUCCESS)但本地无成功流水——**回调丢失,高危**,优先处理 |
| BILL_MISSING | 账单缺失 | 本地有成功流水但账单无该单——可疑(本地虚单/未结算) |
| AMOUNT_MISMATCH | 金额不符 | 两边都有但金额不一致 |
| STATE_MISMATCH | 状态不符 | 账单状态非 SUCCESS(REFUND/CLOSED)但本地仍 SUCCESS |
| FETCH_FAILED | 拉取失败 | 该商户该类型账单下载/申请失败,根本没对上,需重跑 |
### 6.3 handleStatus(处理状态)
| 值 | 中文 | 说明 |
|----|------|------|
| PENDING | 待处理 | 默认状态,仅此状态可调用处理接口 |
| CONFIRMED | 已确认 | 人工已核实确认 |
| IGNORED | 已忽略 | 人工核实后忽略(如已线下解决) |
### 6.4 reconcileStatus(对账结果)
| 值 | 中文 | 说明 |
|----|------|------|
| OK | 对平 | 全部对平,0 差异 |
| HAS_DIFF | 有差异 | 对出差异,需人工处理 |
| FETCH_FAILED | 拉取失败 | 账单没拉到,根本没对上,需重跑 |
## 7. 错误码
| code | 含义 | 触发场景 |
|------|------|----------|
| 400 | 参数校验失败 | 枚举字段传非法值 / 必填缺失 / remark 超 255 字符(HTTP 200 + Result.error(400),msg 为具体校验文案) |
| 582400 | 微信账单申请失败 | 微信侧拒绝 / 无当日账单(对账执行期,管理端查询不直接触发) |
| 582401 | 微信账单下载失败 | 账单下载票/文件拉取失败(对账执行期) |
| 582402 | 微信账单解析失败 | 账单文件解析失败(对账执行期) |
| 582403 | 对账差异记录不存在 | 差异详情 / 处理差异时 diffId 查无此记录 |
| 582404 | 该差异已处理,不可重复处理 | 处理差异时该差异已非 PENDING |
| 582405 | 找不到商户配置 | 手动补跑指定的 mchId 无微信支付商户配置 |
| 582406 | 对账汇总记录不存在 | 汇总记录查无(预留) |
## 8. 示例
### 8.1 典型成功(对账汇总列表 + 差异处理)
请求:
GET /v3/admin/payment/wx-bill/summaries?billDateFrom=2026-09-01&billDateTo=2026-09-30&reconcileStatus=HAS_DIFF&page=1&pageSize=20
Authorization: Bearer <token>
响应:
```json
{
"code": 0,
"data": {
"records": [
{
"id": "1890000000000000001",
"mchId": "1246532201",
"billDate": "2026-09-30",
"billType": "TRADE",
"totalCount": 25,
"totalAmount": 12800.00,
"matchedCount": 24,
"diffCount": 1,
"reconcileStatus": "HAS_DIFF",
"createTime": "2026-10-01 08:30:00"
}
],
"total": 1,
"page": 1,
"pageSize": 20
},
"msg": ""
}
```
处理差异请求:
POST /v3/admin/payment/wx-bill/diffs/1890000000000000009/handle
Authorization: Bearer <token>
Content-Type: application/json
```json
{
"handleStatus": "CONFIRMED",
"remark": "已核实回调丢失,手动补登记"
}
```
处理差异响应:
```json
{
"code": 0,
"data": {
"id": "1890000000000000009",
"mchId": "1246532201",
"billDate": "2026-09-30",
"diffType": "LOCAL_MISSING",
"billType": "TRADE",
"outTradeNo": "HL20260930120000001234",
"wxTransactionId": "4200001234202609301234567890",
"localAmount": null,
"billAmount": 500.00,
"localTransactionId": null,
"diffDetail": "账单SUCCESS本地无成功流水,疑似支付回调丢失",
"handleStatus": "CONFIRMED",
"handleRemark": "已核实回调丢失,手动补登记",
"handledBy": 1,
"handledAt": "2026-10-01 10:00:00",
"createTime": "2026-10-01 08:30:00"
},
"msg": ""
}
```
### 8.2 边界(差异列表空结果 + 手动补跑锁占用)
空差异页请求(对平的日期段):
GET /v3/admin/payment/wx-bill/diffs?billDateFrom=2026-09-01&billDateTo=2026-09-30&handleStatus=PENDING&page=1&pageSize=20
响应(空页为正常结果,非错误):
```json
{
"code": 0,
"data": { "records": [], "total": 0, "page": 1, "pageSize": 20 },
"msg": ""
}
```
手动补跑(当日对账执行中)请求:
POST /v3/admin/payment/wx-bill/reconcile/run
Content-Type: application/json
```json
{ "billDate": "2026-09-30" }
```
响应(锁被占用,data=null,按 msg 提示展示):
```json
{
"code": 0,
"data": null,
"msg": "当日对账正在执行中,请稍后重试"
}
```
### 8.3 业务失败(重复处理差异,582404)
请求(对一条已 CONFIRMED 的差异再次处理):
POST /v3/admin/payment/wx-bill/diffs/1890000000000000009/handle
Content-Type: application/json
```json
{ "handleStatus": "IGNORED", "remark": "重复操作" }
```
响应:
```json
{ "code": 582404, "msg": "该差异已处理,不可重复处理", "data": null }
```
## 9. 业务边界
- 适用:查询/处理**已由对账任务落库**的数据——每日 T-1 定时任务(早晨)跑完后,对应账单日期的汇总/记录/差异才可见;手动补跑成功后立即可见。
- 不适用:
- 当日(T 日)账单——微信当日账单 T+1 上午才生成,对当日日期查/跑只会得到 FETCH_FAILED 或空;
- 期望接口实时向微信拉账单展示——records 展示的是**落库快照**,不是实时微信数据。
- 特殊:
- LOCAL_MISSING(本地缺失)= 回调丢失高危差异,建议财务优先处理;
- 差异**处理只改标记不改账**——确认/忽略不会触发任何资金或订单侧动作;
- FETCH_FAILED 类汇总/差异的正确处理动作是**重跑**(手动补跑接口),不是人工确认。
## 10. 修改前后对比
新增接口,无修改前版本。本组 6 端点全部为首次交付,无旧路径、无字段变更。
## 11. 影响评估 / 回滚
- **是否破坏向后兼容**:否(纯新增端点 + 纯新增表,无既有接口/字段改动)。
- **前端是否必须同步上线**:否(不接入不影响任何既有功能;接入后提供「微信商户对账」视图)。
- **回滚方式**:revert PR #8703 即可下线 6 端点;三张新表(wx_bill_record / wx_bill_diff / wx_bill_summary)为独立新表,不回滚也不影响既有功能。
## 12. 注意事项
- **Long 字符串化**:id / localTransactionId 为 JSON 字符串(见 §5 开头),表格 key、路由参数、处理接口 path 回传时**不要 Number() 转换**。
- **处理差异非幂等但防重**:重复处理报 582404,前端提交后可禁按钮防连点,收到 582404 时刷新该行为宜。
- **手动补跑是重操作**:逐商户下载微信账单 + 全量比对,锁 TTL 30 分钟;触发后建议稍后刷新汇总列表看结果,不要连续点击(锁占用会返回 data=null 提示)。
- **枚举值严格校验**:Query 中的 billType / reconcileStatus / diffType / handleStatus 传非法值会被参数校验拦截(HTTP 200 + code 400),下拉框请只渲染 §6 列出的值。
- 出参中可空字段(如资金账单的 wxTransactionId、交易账单的 fundFlowId、未处理差异的 handleRemark / handledBy / handledAt)返回 null,展示需做空值兜底。
## 13. 关联 / 联系人
- **Issue**: [#8690](https://git.1814.love/wx/HL/issues/8690)
- **PR**: [#8703](https://git.1814.love/wx/HL/pulls/8703)
- **Merge commit**: [faeed6e0a7](https://git.1814.love/wx/HL/commit/faeed6e0a7)
- **后端负责人**: @yst