--- schema: "hl-changelog/v2" ticket: "8690" title: "微信商户对账:账单下载解析比对落库+差异处理+手动补跑(6 端点)" consumer: "admin" author: "yst(GIT)" change_type: "新增接口" backend_status: "merged" gateway_status: "not_required" frontend_status: "implemented" frontend_owner: "mmg" frontend_ref: "8175316c88d4f555538d7e65d3744102bccfee90" target_release: "v2.1" verified_at: "2026-10-02" status_note: "前端交付(2026-10-02,用户拍板已部署):新建 finance/wx-bill 页(hiddenRoute 先行,sys_menu 未下挂;汇总台账 dateRange 拆 billDateFrom/To/差异笔数>0 飘红/手动补跑 billDate 必填+锁占用 data=null 按契约文案提示)+BillRecordsDrawer 逐笔快照+BillDiffsDrawer 差异处理(先拉详情防 582404/结论仅 CONFIRMED·IGNORED/只改标记不调账)+api/finance/wx-bill.js(分页 page 非 pageNo 已钉),spec 8 例全绿,提交 8175316c。" 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