diff --git a/changelogs-v2/2026-10/01_8690_微信商户对账-新增接口-管理后台.md b/changelogs-v2/2026-10/01_8690_微信商户对账-新增接口-管理后台.md new file mode 100644 index 00000000..8fb7300e --- /dev/null +++ b/changelogs-v2/2026-10/01_8690_微信商户对账-新增接口-管理后台.md @@ -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 + +响应: + +```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 + 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