# 万车充小程序未分账补分账 SOP(adapayMemberId=0) ## 1. 目标与背景 - 目标:将未分账订单批量向汇付发起 `PaymentConfirm` 请求,分账对象固定为 `adapayMemberId=0`(平台默认账户)。 - 数据来源:`doc/万车充小程序-未分账明细.xlsx`。 - 编写日期:2026-03-11(Asia/Shanghai)。 说明:本 SOP 面向“拿到一份未分账明细文件后,批量补分账”的场景。文档中的流程可复用于后续同类任务。 ## 2. 本次文件分析结论 对 `doc/万车充小程序-未分账明细.xlsx` 的 `Sheet1` 统计结果如下: - 总记录数:`42,225` - `paymentId` 去重后重复数:`0` - `app_id`:全部为 `app_d0c80cb1-ffc8-48cb-a030-fe9bec823aaa` - 支付时间范围:`2024-09-07 19:29:00` ~ `2026-03-06 00:11:20` - 未分账金额(列“剩余未分账金额”) - 合计:`171,710.73` - 最小:`0.01` - 中位数:`0.50` - P90:`8.45` - 最大:`268.39` - 金额分布: - `0.01~1`: `34,367` - `1~10`: `4,018` - `10~50`: `3,061` - `50~100`: `507` - `>=100`: `272` - 交易订单号格式:`42,223/42,225` 包含下划线(通常为 `orderCode_时间戳`) 关键结论: 1. 量大(4.2 万+),必须批处理+可重试+可追踪。 2. 文件最晚数据到 `2026-03-06`,执行时必须先查汇付最新状态,不能盲目按文件金额直发。 3. 小额单非常多(0.01~1 占比高),要考虑接口速率与批次执行时长。 ## 3. 现有代码落点(可复用) - 发起汇付分账请求: - `jsowell-pile/src/main/java/com/jsowell/adapay/service/AdapayService.java` - 方法:`createPaymentConfirmRequest(PaymentConfirmParam param)` - 查询支付确认列表: - 同上 `queryPaymentConfirmList(QueryPaymentConfirmDTO dto)` - 分账对象模型: - `DivMember.memberId` 设为 `0`,`feeFlag` 设为 `Y` - 已有“未分账处理”参考(测试代码): - `jsowell-admin/src/test/java/PaymentTestController.java` - 方法:`processUnSettledOrder()` 注意: - `jsowell-quartz` 里的 `processUnSettledOrder()` 当前版本(V2)主要是“查汇付确认信息”,不是生产可直接执行的补分账任务。 ## 4. 推荐执行方案(生产可落地) ## 4.1 方案总览 分三阶段执行: 1. 任务清单准备 2. 批量补分账执行 3. 结果复核与失败重试 建议新增一个“一次性批处理工具(仅本次任务分支)”,不要直接依赖手工接口逐条处理。 ## 4.2 任务清单准备 从 Excel 抽取以下字段形成任务清单(CSV/临时表均可): - `payment_id` ← 交易流水号 - `source_order_no` ← 交易订单号 - `order_code` ← `source_order_no` 按 `_` 分割取前半段;若无 `_` 则回退原值 - `pay_amt` ← 交易订单金额 - `file_confirmed_amt` ← 已确认分账金额 - `file_reserved_amt` ← 支付确认撤销金额 - `file_remaining_amt` ← 剩余未分账金额 - `pay_time` ← 支付时间 过滤条件: - `file_remaining_amt > 0` - `payment_id` 非空 ## 4.3 批量补分账执行(核心) 单条任务处理逻辑(必须幂等): 1. 用 `payment_id` 调 `queryPaymentConfirmList` 查询汇付最新确认信息。 2. 计算“执行时最新可分账金额” `latest_remaining_amt`: - 基线金额使用文件中的 `file_remaining_amt`。 - 若汇付返回了确认信息,使用 `pay_amt - confirmed_amt - reserved_amt` 再算一遍,取两者较小值(且不小于 0)。 3. 若 `latest_remaining_amt <= 0`:跳过(记为 `SKIPPED_NO_REMAINING`)。 4. 构造分账请求: - `paymentId = payment_id` - `confirmAmt = latest_remaining_amt` - `divMemberList = [{ memberId: "0", amount: latest_remaining_amt, feeFlag: "Y" }]` - `orderCode = order_code` - `wechatAppId = 对应 appId` 5. 调用 `createPaymentConfirmRequest(param)`。 6. 按响应记录结果: - 成功:记录 `payment_confirm_id`、`confirm_amt`、`fee_amt` - 失败:记录 `error_code`、`error_msg` 建议执行参数: - 批次:每批 `500`(或 `200` 起步) - QPS:`3~5`(每笔 sleep `200~300ms`) - 失败重试:对可重试错误最多 `2` 次 建议错误处理: - `confirm_amt_over_limit`:立即重查汇付最新金额,按新金额重试一次。 - `payment_over_time_doing` / `refund_repeate_request`:放入重试队列,延迟重跑。 - 其他错误:记录失败,人工复核。 ## 4.4 结果复核与验收 必须输出三类结果文件(CSV): - `success.csv`:成功明细(含 paymentConfirmId) - `skipped.csv`:跳过明细(无剩余可分账) - `failed.csv`:失败明细(含错误码) 验收标准: 1. `success + skipped + failed = 总任务数` 2. 对 `success` 样本抽查 `queryPaymentConfirmList`,确认 `div_members` 包含 `memberId=0` 3. `failed` 有明确可重试策略或人工处理结论 ## 4.5 回滚预案(必要时) 如果误分账到错误对象或金额错误: 1. 从 `success.csv` 取 `payment_confirm_id` 2. 调用 `createConfirmReverse(paymentConfirmId, wechatAppId)` 撤销支付确认 3. 撤销后重新按正确参数执行补分账 代码参考: - `AdapayService.createConfirmReverse(...)` ## 5. 下次同类任务复用清单 执行前 Checklist: 1. 明细文件是否包含 `payment_id`、`remaining_amt`、`pay_amt` 2. 是否确认目标账户(本 SOP 为 `memberId=0`) 3. 是否先做 100 条小批次试跑 4. 是否有完整执行日志与 `success/skipped/failed` 三份结果 5. 是否准备了 `payment_confirm_id` 级别回滚能力 执行后 Checklist: 1. 失败项是否重试完毕 2. 关键样本是否验证到汇付侧最新状态 3. 本次任务结果文件是否归档到 `doc/` 或运维归档目录 ## 6. 当前已落地入口 已新增任务方法(`JsowellTask`): - `importAdapayUnsplitRecordAndCompleteFields()` - `importAdapayUnsplitRecordAndCompleteFields(String filePath)` 实现位置: - `jsowell-quartz/src/main/java/com/jsowell/quartz/task/JsowellTask.java` 方法职责: 1. 从 Excel 导入 `adapay_unsplit_record`(按 `insertOrUpdateSelective`,支持重复执行) 2. 自动补齐 `order_code / due_refund_amount / settle_amount / pile_type` 等缺失字段 3. 输出导入与补齐统计日志 执行示例: - 使用默认路径:`jsowellTask.importAdapayUnsplitRecordAndCompleteFields()` - 指定路径:`jsowellTask.importAdapayUnsplitRecordAndCompleteFields('doc/万车充小程序-未分账明细.xlsx')`