批量有密产品原为企业级架构:授权记录绑定企业(pay_batch_authorize.enterprise_id,一企业一协议,uk_batch_authorize_active(enterprise_id, participant_id) 唯一索引),制单付款方恒为企业自身身份,功能挂在「资金专户转账页」菜单的 tab 内。
现调整为账号级架构,与企业管理功能隔离:
pay_service_provider,表单给出服务商下拉框选择(与新增企业的服务商下拉框一致)来源:Playwright 抓取 https://opendocs.alipay.com/pre-open/fc872015_alipay.fund.authorize.uni.apply (2026-08-26,邀测站点「批量付款到户有密」产品)
| 参数 | 必选 | 说明 |
|---|---|---|
product_code |
必选 | 固定 FUND_AUTHORIZATION |
biz_scene |
必选 | 固定 AUTHORIZE_FLOW |
out_biz_no |
必选 | 授权申请外部业务号,幂等控制 |
authorize_link_type |
必选 | SHORT_URL(短链接)/ TOKEN(组件token) |
back_url |
必选 | 回跳地址(示例 alipays://xxxxxxx) |
principal_info |
必选 | AuthParticipantInfo 授权主体:participant_id_type + participant_id + name(示例 ALIPAY_USER_ID / 2088... / 名称) |
channel |
可选 | pc / tinyapp |
sub_biz_scene |
可选 | 示例 COMMON |
apply_expire_time |
可选 | 跳转链接有效期,默认 7 天 |
auth_expire_time |
可选 | 授权失效时间,默认 2115-01-01 |
back_url_type |
可选 | BACK_ALIPAY(默认)/ BACK_OUT_ALIPAY |
partner_info / third_party_info |
可选 | AuthParticipantInfo |
auth_biz_param / authorize_check_rule |
可选 | 本次不使用 |
关键结论:principal_info 主体可任意指定(个人/企业支付宝账号),participant_id 为 uid(ALIPAY_USER_ID)。支持多主体、多协议,即账号级多授权的基础。
| 错误码 | 含义 | 处理 |
|---|---|---|
USER_AUTHORIZATION_EXIST |
已存在授权协议,无需再次授权,请通过查询接口查询授权信息 | 同租户同主体已签约 → 前端提示「该账号已签约」,不重复发起(配合唯一索引预检) |
EXISTS_STOPPED_AUTHORIZE |
存在已失效的授权,请更换 outBizNo 后重试 | 主体解绑后重新授权必须换新 out_biz_no(现有 rebind 逻辑复用) |
USER_NOT_EXIST |
用户不存在,请检查申请账号合法性 | 表单 uid 非法提示 |
UN_SUPPORT_AUTHORIZE_TERMINAL_TYPE |
不支持的授权端 | channel 校验(本产品固定 pc) |
BatchAuthorizeEntity extends PaymentEnterpriseBaseEntity(表 pay_batch_authorize):out_biz_no / participant_id / participant_id_type / agreement_no / status(AUTHING/AUTHED/UNBIND) / authorize_link / authorize_expire_time + 基类 tenant_id / enterprise_id / id / created_timeBatchOrderEntity extends PaymentEnterpriseBaseEntity(表 pay_batch_order):out_batch_no / batch_trans_id / total_amount / total_count / order_title / status(INIT/WAIT_PAY/SUCCESS/DISUSE/FAIL/INVALID) / payer_uid / agreement_no / transfer_scene_name / transfer_scene_report_infos / time_expire / remark / pay_url / error_code / error_msgBatchDetailEntity extends PaymentEnterpriseBaseEntity(表 pay_batch_detail):batch_id / out_biz_no / amount / remark / payee_identity / payee_identity_type / payee_name / status / error_code / error_msguk_batch_authorize_active(enterprise_id, participant_id)TenantInnerInterceptor(MyBatis-Plus TenantLineInnerInterceptor)自动为 pay_* 表追加 tenant_id 条件:
LoginUser.tenant_id 过滤isSuperuser)→ 跳过租户隔离,可看全部tenant_idpay_batch_authorize / pay_batch_order / pay_batch_detail 均不在 IGNORE_TABLES / CONDITIONAL_TABLES 名单 → 已登录即自动租户级隔离。接口无需传租户参数。
batchCreate(BatchCreateDTO):付款方从企业解析(payerIdentity(ent):identity 优先回退 enterprise_id),协议号从该企业+付款方最新 AUTHED 授权记录自动带出,client 用 getClient(enterpriseId, BIZ_TYPE)renderPay / batchQuery / batchClose:显式 .eq(enterpriseId) 条件;client 用 getClient(enterpriseId, BIZ_TYPE)batchList / batchDetail / batchExport / authorizeList:enterprise_id 显式筛选getPendingBatches:全表非终态(无认证,租户拦截器跳过)—— 不改BatchPayHandler(通知):按 out_biz_no / out_batch_no 匹配,无 enterprise 依赖 —— 不改getClient(enterpriseId, bizType):企业 → serviceProviderId → ① profile(pay_service_provider_profile 业务专属凭证)→ ② 服务商默认凭证 → ③ yml fallbackgetClientByProvider(Long providerId):直接按服务商(默认凭证)getClientByProfile(spId, bizType):private,服务商+业务专属凭证改造需要新增 public 入口:getClientByProvider(providerId, bizType)(先 profile 后默认,与现有企业链路同构,仅少企业中间层)。
sys_menu 表下发,permission.store.ts 动态路由),顶层菜单示例:/account → module_payment/account/indexaccount/index.vue 的 batch-pay tab:二级 tabs(制单授权 BatchPayAuthorize / 批次列表 BatchPayList / 创建批次 BatchPayCreate),详情 BatchPayDetail 条件渲染batch.ts:/payment/account/batch 前缀,全部接口带 enterprise_idProviderAPI.options()(EnterpriseForm.vue 同款,返回 { id, scope_label })module_payment:account:authorize / transfer / transfer:list / transfer:detailpay_batch_authorize(授权记录即主体,租户级)实体基类:PaymentEnterpriseBaseEntity → PaymentTenantBaseEntity(移除 enterprise_id 绑定)。
| 字段 | 改动 |
|---|---|
participant_id / participant_id_type |
保留(表单录入的支付宝 uid,ALIPAY_USER_ID) |
participant_name |
新增 —— 主体名称,表单必填,同时作为 principal_info.name |
service_provider_id |
新增 —— 表单服务商下拉 |
out_biz_no / agreement_no / authorize_link / authorize_expire_time / status |
保留,每条主体记录独立维护 |
| 唯一索引 | uk_batch_authorize_active(enterprise_id, participant_id) → (tenant_id, participant_id),防同租户重复签约(对应 USER_AUTHORIZATION_EXIST) |
pay_batch_order / pay_batch_detail(租户级)实体基类同上改为 PaymentTenantBaseEntity。pay_batch_order 新增:
| 字段 | 说明 |
|---|---|
service_provider_id |
制单时冗余主体服务商 —— 主体解绑后批次仍可支付/查询/关闭(client 不依赖主体记录存活) |
payer_uid(= 主体 uid)作为主体标识,制单历史按主体筛选直接用 payer_uid。
不迁移。存量行保留(enterprise_id 非空旧行不再被新查询命中)。索引改为租户+主体唯一,避免与存量冲突。
BatchSubjectService(主体/授权管理):apply / rebind / query / list
apply(participant_name, participant_id, service_provider_id) → principal_info.name = 主体名称(新传字段)、participant_id = 表单 uid;client 用 getClientByProvider(service_provider_id)USER_AUTHORIZATION_EXIST → 提示「该账号已签约,请直接选择使用」(配合唯一索引预检)EXISTS_STOPPED_AUTHORIZE → rebind 换 out_biz_no(现有逻辑复用,每条主体记录独立维护)AlipayBatchPayService 保留批次操作并改造:create / renderPay / batchQuery / batchClose / list / detail / export / getPendingBatches
所有接口移除 enterprise_id 参数,依赖 TenantInnerInterceptor 自动隔离。batchCreate 中 dto.getTenantId() 恒空、由拦截器 insert 自动填充的模式不变。
batchCreate)BatchCreateDTO 新增 participant_id(制单时从「已授权主体」列表选主体,付款方)agreement_no(自动带出,规则不变)alipayClientFactory.getClient(providerId, BIZ_TYPE)(新增 public 方法,见 3.4)order.service_provider_id = 主体服务商;order.payer_uid = 主体 uid;order.enterprise_id 不再设置AUTH_INFO_NOT_EXISTS 兜底保留)renderPay / batchQuery / batchClose:显式 .eq(enterpriseId) 条件删除(依赖租户拦截器);client 用 order.service_provider_id + BIZ_TYPElist / detail / export:enterprise_id 筛选删除;list 新增 participant_id 筛选(= payer_uid)authorizeList:迁至 BatchSubjectService,enterprise_id 筛选删除,新增按租户自动隔离BatchPayHandler:不改(按 out_biz_no / out_batch_no 匹配)getPendingBatches:不改(全表非终态)@RequestMapping:/payment/account/batch → /payment/batch@PreAuthorize 换新权限:module_payment:batch:authorize / create / list / detailsys_menu 顶层菜单「批量付款」:route_path=/payment/batch,component_path=module_payment/batch/index,权限点 module_payment:batch:list(及子权限点)。配套 SQL 见第 7 节。
module_payment/batch/index.vue结构照搬现有「批量付款」tab:一级 tabs「制单授权 / 批量制单 / 制单历史」,详情条件渲染在底部。account/index.vue 移除 batch-pay tab 及其 import / handler(BatchPayAuthorize/BatchPayList/BatchPayCreate/BatchPayDetail 相关),其余转账功能不动。
ProviderAPI.options() 同新增企业页)顶部加「付款主体」下拉(已授权主体:名称(uid),未授权不可选),明细录入不变。
筛选区「企业」下拉 →「主体」下拉(按付款方 uid 筛选)+ 状态/时间筛选不变;平台超管可筛选任意主体。
移除 enterpriseId prop 与相关请求参数,租户自动隔离。
batch.ts/payment/account/batch → /payment/batchenterprise_id 参数authorizeApply / authorizeRebind 参数改为 participant_name / participant_id / service_provider_idauthorizeList 支持主体筛选;batchList 新增 participant_id 筛选v-hasPerm 同步换 module_payment:batch:*-- 1. pay_batch_authorize 主体字段
ALTER TABLE pay_batch_authorize
ADD COLUMN participant_name varchar(128),
ADD COLUMN service_provider_id bigint;
-- 2. 唯一索引改租户级(先删旧)
DROP INDEX IF EXISTS uk_batch_authorize_active;
CREATE UNIQUE INDEX uk_batch_authorize_active
ON pay_batch_authorize (tenant_id, participant_id)
WHERE status <> 'UNBIND'; -- 解绑后可重新签约
-- 3. pay_batch_order 冗余服务商
ALTER TABLE pay_batch_order ADD COLUMN service_provider_id bigint;
-- 4. 新菜单 + 权限点(sys_menu,parent_id 按实际顶层菜单排序取)
INSERT INTO sys_menu (parent_id, title, route_name, route_path, component_path, permission, type, "order", status)
VALUES (NULL, '批量付款', 'payment-batch', '/payment/batch', 'module_payment/batch/index', 'module_payment:batch:list', 2, <order>, '0');
INSERT INTO sys_menu (parent_id, title, permission, type, "order", status)
VALUES (<batchMenuId>, '制单授权', 'module_payment:batch:authorize', 3, 1, '0'),
(<batchMenuId>, '批量制单', 'module_payment:batch:create', 3, 2, '0'),
(<batchMenuId>, '制单历史', 'module_payment:batch:list', 3, 3, '0'),
(<batchMenuId>, '批次详情', 'module_payment:batch:detail', 3, 4, '0');
-- 注:UNBIND 态部分唯一索引需先清存量重复行(联调期数据),执行前确认
说明:唯一索引过滤条件
WHERE status <> 'UNBIND'为建议形态,实现时以存量数据核对为准(联调期数据可直接清理)。
后端(JUnit + Mockito,沿用 AlipayBatchPayServiceTest 模式):
BatchSubjectService:apply 传主体参数(principal_info.name/uid 断言);USER_AUTHORIZATION_EXIST 映射;rebind 换 out_biz_no;list 租户隔离batchCreate:按主体制单(payer=主体 uid、agreement 带出、order.service_provider_id 落库);client 解析走 getClientByProvider;未授权主体预检拦截renderPay / batchQuery / batchClose:不再依赖 enterprise 条件;client 用 order.service_provider_idAlipayClientFactory:新增 getClientByProvider(providerId, bizType) 解析链(profile → 默认)前端:
account/index.vue 不再出现批量付款 tabBatchPayHandler)与定时同步(getPendingBatches)TenantApiKeyAuthFilter 场景的批量接口)—— 批量接口保持管理端认证| # | 决策 | 依据 |
|---|---|---|
| D1 | 授权记录即主体(扩展 pay_batch_authorize,不建主体表) | 用户选择;现有表复用,改动最小 |
| D2 | 新菜单单页多 tab(制单授权/批量制单/制单历史) | 用户选择;与 account/index.vue 现有模式一致 |
| D3 | 存量数据不迁移 | 用户选择;联调期测试数据 |
| D4 | 租户隔离复用 TenantInnerInterceptor,接口去 enterprise_id | 代码实证:paybatch* 三表不在拦截器豁免名单 |
| D5 | order 冗余 service_provider_id | 主体解绑后批次操作仍可用 client |
| D6 | 服务拆分:BatchSubjectService(授权)+ AlipayBatchPayService(批次) | AlipayBatchPayService 已 600+ 行 |
| D7 | 制单历史主体筛选用 payer_uid | order 已有该字段(= 主体 uid) |