Procházet zdrojové kódy

docs: 批量付款到户有密接入设计文档

alphaH před 6 dny
rodič
revize
03bae85dd3
1 změnil soubory, kde provedl 214 přidání a 0 odebrání
  1. 214 0
      .claude/plan/2026-08-25-alipay-batch-pay-design.md

+ 214 - 0
.claude/plan/2026-08-25-alipay-batch-pay-design.md

@@ -0,0 +1,214 @@
+# 批量付款到户有密 接入设计
+
+日期:2026-08-25
+状态:已与用户确认(2026-08-25)
+
+## 1. 背景与目标
+
+将支付宝邀测产品「**批量付款到户有密**」(批量转账到支付宝账户)接入 xjz-payment-platform。
+
+**业务目标**:赋能企业客户批量付款 —— 平台代企业制单,企业财务在自己的支付宝端内输入一次支付密码确认,批量付款给外部支付宝账号(发薪、报销、佣金、供应商结算等)。资金**不经过平台账户**,付款方(企业)掌控资金,安全性高于现有记账本直付链路。
+
+**与现有体系的关系**:现有资金链路是「支付宝安全发」(签约→进件→开通记账本→充值→单笔转账),与本产品是**两个独立产品**,并行共存,互不影响。
+
+**接入角色**:付款方仅限平台入驻的企业客户(企业码场景);平台以企业绑定的服务商 AppID 调用 API(沿用现有 `AlipayClientFactory.getClient(enterpriseId, bizType)` 模式,资金接口证书加签)。
+
+**操作渠道**:PC 网页版(制单授权 channel=pc、支付页 PC 版),每批最多 1000 笔。
+
+**收款方来源**:仅外部支付宝账号(手填或 Excel 导入),不关联平台员工体系。
+
+## 2. 产品关键约束(来自支付宝邀测文档)
+
+### 2.1 API 清单
+
+| 环节 | 接口 | 用途 |
+|---|---|---|
+| 制单授权 | `alipay.fund.authorize.uni.apply` | 生成授权短链接(PC/小程序),付款方端内授权 |
+| 授权查询 | `alipay.fund.authorize.uni.query` | 查询单一协议授权状态 |
+| 授权通知 | `alipay.fund.authorize.status.notify` | 签约/解约异步通知(订阅 from 蚂蚁消息) |
+| 批次下单 | `alipay.fund.batch.create` | 创建批量付款单据 |
+| 渲染支付 | `alipay.fund.trans.render.pay` | 生成 PC/小程序支付页,付款方输密码完成 |
+| 批次通知 | `alipay.fund.batch.order.changed` | 批次状态异步通知 |
+| 批次查询 | `alipay.fund.batch.detail.query` | 批次+明细状态查询(兜底) |
+| 批次关单 | `alipay.fund.batch.close` | 主动关闭未支付批次 |
+| 电子回单(**二期**) | `alipay.data.bill.ereceiptagent.apply` / `alipay.data.bill.accountbookereceipt.query` | 申请/查询回单,本期不做
+
+### 2.2 关键参数
+
+**授权申请(uni.apply)**:
+- `product_code` = `TRANSFER_API_STANDARD_AUTHORIZATION`(固定)
+- `biz_scene` = `STANDARD_CREATE_FUND_ORDER`(固定)
+- `authorize_link_type` = `SHORT_URL`(固定)
+- `channel` = `pc`(PC 网页版)
+- `principal_info.participant_id` = 付款方支付宝 uid;`participant_id_type` = `ALIPAY_USER_ID`
+- 出参:`authorize_link`(短链接)
+
+**批次下单(batch.create)**:
+- `product_code` = `BATCH_API_TO_ACC`(固定);`biz_scene` = `STANDARD_MESSAGE_BATCH_PAY`(固定)
+- `out_batch_no` 商户唯一;`total_trans_amount`、`total_count`
+- `order_title` 必填(展示在付款方账单);`time_expire`(绝对时间,格式 `yyyy-MM-dd HH:mm`)
+- `payer_info`:付款方 uid + `ext_info.agreement_no`(制单授权协议号,传则校验该协议)
+- `trans_order_list[]`:每笔 `out_biz_no`(批内唯一)、`trans_amount`(≥1 元)、`payee_info`(identity + identity_type,LOGON_ID 时 name 必填且校验姓名一致)、`remark`
+- **转账场景报备(26 年新接入商户必传)**:`transfer_scene_name` + `transfer_scene_report_infos[]`(`info_type` + `info_content`)
+- 出参:`batch_trans_id`(支付宝批次号,有效期 30 天)
+
+**渲染支付(render.pay)**:入参 `batch_trans_id`,出参支付 URL(PC 版浏览器打开)。
+
+### 2.3 额度与限制
+
+- 默认:单笔 5000 / 日 20000 / 月 600000 元;自助提额上限:单笔 50000 / 日 100000 / 月 1000000 元
+- 每批最多 **1000 笔**(PC 渠道),明细金额 ≥1 元
+- 部分明细因收款账户问题转账失败时,支付宝原路退回该明细金额(明细级别状态跟踪)
+
+### 2.4 授权特性
+
+- 授权一旦完成**永久生效**;终止只能付款方在支付宝 APP 端手动解约
+- 授权链接**一次有效**,重新授权需换外部单号
+- 同一付款方与平台间已存在授权协议则不允许重复新增
+- 付款方和请求方一致时,跳过制单授权校验
+
+### 2.5 接入前置条件
+
+- 邀测产品,需联系支付宝业务人员开通「批量付款到户有密」;商家平台开通「商家转账」产品
+- 应用网关:指向平台统一通知入口(现有 `/payment/notify`)
+- 订阅 from 蚂蚁消息:`alipay.fund.authorize.status.notify`、`alipay.fund.batch.order.changed`
+- 资金支出接口强制证书加签(现有服务商资金类客户端已是证书模式)
+- 不支持服务商代开发 —— 由企业绑定服务商 AppID 模式接入(平台代持,已确认)
+
+## 3. 业务流程
+
+```
+企业财务(PC)                     平台 (Java 后端)                        支付宝
+   │                                  │                                     │
+   │ ① 打开「制单授权」页面            │                                     │
+   │◄─ 授权短链(PC) ──────────────────│─ fund.authorize.uni.apply ──────────►│
+   │ 浏览器打开链接                    │                                     │
+   │◄════════════ 支付宝授权页 ════════╪══════════════════════════════════════►│
+   │ 输密码授权(永久生效,一次一链)   │                                     │
+   │◄════════ 授权完成 ────────────────│─ status.notify 异步通知 ────────────►│
+   │ ② 创建批次(≤1000笔外部账号)     │  fund.batch.create ─────────────────►│
+   │◄─ 支付URL ────────────────────── │─ fund.trans.render.pay(PC) ─────────►│
+   │ ③ 新窗口打开支付页                │                                     │
+   │◄══════════ 支付宝端输一次密码 ════╪══════════════════════════════════════►│
+   │ ④ 查批次列表/明细                 │◄─ fund.batch.order.changed 通知 ─────│
+   │                                  │◄─ fund.batch.detail.query 兜底 ──────│
+```
+
+状态流转:批次 `INIT`(已受理)→ 支付后 `SUCCESS`(终态)/ 部分明细失败原路退回;`DISUSE`(关闭,终态,换外部单号重试);批次创建后 30 天未支付自动关单(或 `time_expire` 控制)。
+
+## 4. 数据模型(3 张新表)
+
+沿用现有 base entity 体系(`PaymentEnterpriseBaseEntity`:tenant_id、enterprise_id 等)。
+
+### 4.1 `pay_batch_authorize` — 制单授权记录
+
+| 字段 | 类型 | 说明 |
+|---|---|---|
+| out_biz_no | varchar(64) | 授权外部单号(雪花),唯一 |
+| enterprise_id | varchar(64) | 所属企业 |
+| participant_id | varchar(64) | 付款方支付宝 uid |
+| participant_id_type | varchar(16) | ALIPAY_USER_ID |
+| agreement_no | varchar(64) | 制单授权协议号(授权完成后回填) |
+| status | varchar(32) | AUTHING / AUTHED / UNBIND |
+| authorize_link | varchar(512) | 生成的短链接 |
+| authorize_expire_time | datetime | 链接超时时间 |
+
+### 4.2 `pay_batch_order` — 批次主表
+
+| 字段 | 类型 | 说明 |
+|---|---|---|
+| out_batch_no | varchar(32) | 商户批次唯一单号 |
+| batch_trans_id | varchar(32) | 支付宝批次订单号(唯一索引) |
+| total_amount | decimal(16,2) | 批次总金额 |
+| total_count | int | 总笔数 |
+| order_title | varchar(64) | 批次标题(账单展示) |
+| status | varchar(32) | INIT / SUCCESS / DISUSE / FAIL |
+| time_expire | datetime | 超时关单时间 |
+| payer_uid | varchar(64) | 付款方 uid |
+| agreement_no | varchar(64) | 制单授权协议号 |
+| transfer_scene_name | varchar(64) | 转账场景(26 年新接入必传) |
+| transfer_scene_report_infos | jsonb | 场景报备信息 [{info_type, info_content}] |
+| remark | varchar(200) | 业务备注 |
+| pay_url | varchar(1024) | render.pay 生成的支付 URL |
+| receipt_file_id / receipt_status | — | 电子回单(二期可做) |
+| error_code / error_msg | varchar | 错误信息 |
+
+### 4.3 `pay_batch_detail` — 批次明细表
+
+| 字段 | 类型 | 说明 |
+|---|---|---|
+| batch_id | bigint | 关联批次主键 |
+| out_biz_no | varchar(64) | 明细外部单号(批内唯一) |
+| amount | decimal(16,2) | 明细金额(≥1 元) |
+| remark | varchar(100) | 转账备注(展示在收款方账单) |
+| payee_identity | varchar(64) | 收款方账号(uid/账号/openid) |
+| payee_identity_type | varchar(32) | ALIPAY_LOGON_ID / ALIPAY_USER_ID / ALIPAY_OPEN_ID |
+| payee_name | varchar(256) | 收款方姓名(LOGON_ID 必填,校验姓名一致) |
+| status | varchar(32) | INIT / SUCCESS / FAIL |
+| error_code / error_msg | varchar | 明细级错误 |
+
+## 5. 后端设计(Java,`module/payment/batch/` 新包)
+
+### 5.1 `AlipayBatchPayService`
+
+复用 `AlipayClientFactory.getClient(enterpriseId, "BATCH_PAY")`(服务商证书模式,bizType 走 `pay_service_provider_profile` 专属凭证,无专属配置回退服务商默认)。
+
+- `authorizeApply(enterpriseId, participantId)` → 调 uni.apply,落授权记录,返回短链接
+- `queryAuthorize(enterpriseId, outBizNo)` → 调 uni.query,更新授权状态
+- `batchCreate(BatchCreateDTO)` → 校验(授权存在、笔数 ≤1000、金额 ≥1 元、场景报备)→ 调 batch.create → 落批次+明细
+  - 幂等:`out_batch_no` 唯一;返回 `UNIQUE_VIOLATION` 时查询批次后再决定
+- `renderPay(outBatchNo)` → 调 render.pay,回填 pay_url
+- `batchQuery(outBatchNo)` → 调 batch.detail.query,同步批次+明细状态(定时/手动兜底,沿用现有 transferSyncAllService 定时同步模式)
+- `batchClose(outBatchNo)` → 调 batch.close
+- 通知处理(供 handler 调用):授权状态更新、批次状态更新、明细逐笔更新
+
+### 5.2 `BatchPayController` — `/payment/account/batch/*`(挂在现有资金账户模块下)
+
+| 接口 | 权限 | 说明 |
+|---|---|---|
+| POST `/authorize/apply` | module_payment:account:authorize | 生成授权链接 |
+| GET `/authorize/query` | module_payment:account:authorize | 查询授权状态 |
+| GET `/authorize/list` | module_payment:account:transfer:list | 授权列表 |
+| POST `/create` | module_payment:account:transfer | 创建批次 |
+| POST `/pay` | module_payment:account:transfer | 生成支付 URL |
+| GET `/list` | module_payment:account:transfer:list | 批次列表(分页/状态/时间筛选) |
+| GET `/detail` | module_payment:account:transfer:detail | 批次详情+明细 |
+| POST `/close` | module_payment:account:transfer | 关闭批次 |
+| GET `/export` | module_payment:account:transfer:list | 导出(复用现有 transferExport 模式) |
+
+### 5.3 异步通知(复用现有入口,零新增端点)
+
+- `AlipayNotifyMethod` 枚举新增:`FUND_AUTHORIZE_STATUS_NOTIFY("alipay.fund.authorize.status.notify")`、`FUND_BATCH_ORDER_CHANGED("alipay.fund.batch.order.changed")`
+- 新增 `BatchPayHandler extends BaseNotifyHandler`,注册上述两个 method,通过 `NotifyContext` 注入 batch mapper;处理:授权签约/解约更新授权表、批次状态更新 + 明细逐笔更新 + 记 `pay_alipay_notify_log`
+- 验签沿用现有 `/payment/notify` 入口的验签机制(实现时确认 NotificationService 按 app_id 解析公钥的细节)
+- 通知处理幂等:同一 notify_id / 终态不重复更新
+
+## 6. 前端设计(资金账户模块新增「批量付款」)
+
+- **制单授权页**:生成授权短链接(PC 复制/二维码),授权状态展示,重新授权按钮
+- **批次列表页**:分页、状态筛选、时间筛选、导出;行操作:支付(打开支付 URL)、查看详情、关闭
+- **创建批次页**:手填明细 / Excel 导入(模板下载:收款账号、类型、姓名、金额、备注);转账场景选择(现金营销/企业退款/佣金报酬/二手回收/业务结算/公益补助/行政补贴退款/保险理赔)+ 报备信息录入
+- **批次详情页**:批次信息 + 明细表格(逐笔状态)+ 支付按钮
+- 支付页:`pay_url` 新窗口打开(支付宝 PC 支付页)
+
+## 7. 错误处理与幂等
+
+- 批次创建:`out_batch_no` 唯一索引;系统异常/未明确错误码时保持原单号重试;`UNIQUE_VIOLATION` → 查批次状态,已受理则不再重复创建
+- 通知重复:以终态和 notify_id 判重
+- 明细部分失败:支付宝原路退回,明细级状态跟踪,批次状态为 SUCCESS(部分明细 FAIL),列表展示明细级失败原因
+- 授权链接过期/重复授权:提示重新生成(换 out_biz_no)
+- 批次超时:`time_expire` 控制,DISUSE 终态提示换单重试
+
+## 8. 测试
+
+- **Java 单测**:`AlipayBatchPayService`(Mock 支付宝 SDK 响应):授权申请/查询、批次创建幂等(UNIQUE_VIOLATION)、状态同步、通知处理幂等
+- **前端 Playwright**:走通「生成授权链接 → 创建批次(导入明细)→ 生成支付 URL」链路,批次列表/详情渲染
+- 通知 handler 单测:两个新 msg_method 分发正确
+
+## 9. 前置条件与风险
+
+- **邀测产品**:需联系支付宝业务人员开通;未开通前联调不可行 → 联调用沙箱/文档响应 Mock
+- 转账场景报备为 26 年新接入必传 —— 前端录入 + 后端校验必填
+- 服务商需配置应用网关 + 订阅 from 蚂蚁消息(运营操作,需与客户确认)
+- 授权为永久性协议,平台不可解约 —— 授权管理页需明确提示
+- 额度限制(单笔 5000 默认)可能导致大额批次失败 —— 页面提示查看/调整转账额度