Files
jsowell-charger-web/doc/万车充小程序未分账补分账SOP.md
2026-03-11 18:12:35 +08:00

306 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 万车充小程序未分账补分账 SOPadapayMemberId=0
## 1. 目标与背景
- 目标:将未分账订单批量向汇付发起 `PaymentConfirm` 请求,分账对象固定为 `adapayMemberId=0`(平台默认账户)。
- 数据来源:`doc/万车充小程序-未分账明细.xlsx`
- 编写日期2026-03-11Asia/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')`
## 7. 完整执行流程(落地版)
以下流程是当前代码已支持、可直接按顺序执行的标准操作。
### 7.1 执行前检查
1. 使用 JDK8。
2. 编译通过:
```bash
mvn -pl jsowell-quartz -am -DskipTests compile
```
3. 确认 Excel 文件就绪:
- 默认路径:`doc/万车充小程序-未分账明细.xlsx`
- 或你自己的绝对路径/相对路径
### 7.2 第一步:导入并补齐缺失字段
执行任务方法(任选其一):
1. 默认路径:
```text
jsowellTask.importAdapayUnsplitRecordAndCompleteFields()
```
2. 指定路径:
```text
jsowellTask.importAdapayUnsplitRecordAndCompleteFields('doc/万车充小程序-未分账明细.xlsx')
```
本步骤会自动完成:
1. Excel -> `adapay_unsplit_record` 导入(`insertOrUpdateSelective`,可重复执行)
2. 自动补齐 `order_code / due_refund_amount / settle_amount / pile_type`
3. 输出导入统计与补齐统计日志
### 7.3 第二步:导入后校验
建议执行以下 SQL
```sql
-- 总量
SELECT COUNT(*) AS total_cnt
FROM adapay_unsplit_record;
-- 核心补齐字段缺失情况
SELECT COUNT(*) AS missing_cnt
FROM adapay_unsplit_record
WHERE order_code IS NULL
OR settle_amount IS NULL
OR due_refund_amount IS NULL
OR pile_type IS NULL;
```
`missing_cnt` 理想结果应接近 `0`(少量异常数据可人工排查)。
### 7.4 第三步:执行未分账处理(分账到 memberId=0
执行任务方法(任选其一):
1. 使用默认 appId`Constants.DEFAULT_APP_ID`+ pageSize=500
```text
jsowellTask.processUnsplitRecordToDefaultMember()
```
2. 指定 appId 和分页大小:
```text
jsowellTask.processUnsplitRecordToDefaultMember('你的wechatAppId', 200)
```
建议先用 `200` 小批量试跑,再提升到 `500`
本步骤会自动完成:
1. 分页读取 `queryList()` 的待分账记录
2. 先查汇付最新确认信息,计算实时剩余可分账金额
3.`min(数据库待分账金额, 汇付实时剩余金额)` 作为本次 `confirmAmt`
4.`PaymentConfirm` 分账,分账对象固定 `memberId=0`
5. 回写结果:
- 成功:更新 `confirmed_split_amount``split_flag=SUCCESS`
- 失败:`split_flag=FAILED`
### 7.5 第四步:循环执行直到待分账清零
每轮执行后,建议跑以下 SQL
```sql
-- 按当前业务口径,仍待处理数量
SELECT COUNT(*) AS remain_cnt
FROM adapay_unsplit_record
WHERE (settle_amount > confirmed_split_amount - payment_revoke_amount)
OR (due_refund_amount > refund_amount);
-- 分账结果分布
SELECT split_flag, COUNT(*) AS cnt
FROM adapay_unsplit_record
GROUP BY split_flag;
```
`remain_cnt = 0` 可视为本次补分账完成。
### 7.6 失败重试与回滚
1. 失败重试:重新执行 `processUnsplitRecordToDefaultMember(...)` 即可。
2. 回滚(如金额或目标账户异常):
-`paymentId` 查询对应 `payment_confirm_id`
-`createConfirmReverse(paymentConfirmId, wechatAppId)` 撤销
- 重新执行正确参数的分账流程
## 8. 一键操作建议(便于下次复用)
按下面顺序执行,基本可覆盖同类任务:
1. `jsowellTask.importAdapayUnsplitRecordAndCompleteFields('文件路径')`
2. 校验 `missing_cnt`
3. `jsowellTask.processUnsplitRecordToDefaultMember('appId', 200)`
4. 查看 `remain_cnt`
5.`remain_cnt > 0`,重复第 3-4 步,最后切 `pageSize=500` 提速