Преглед на файлове

feat: 批次操作账号级改造 - 制单按授权主体、去企业ID、按订单服务商解析client

alphaH преди 5 дни
родител
ревизия
4e415a986c

+ 8 - 12
java/src/main/java/com/payment/platform/module/payment/batch/controller/BatchPayController.java

@@ -58,23 +58,20 @@ public class BatchPayController {
     @PreAuthorize("@perm.hasAny('module_payment:account:transfer')")
     @PostMapping("/pay")
     public Result<Map<String, String>> renderPay(@RequestBody Map<String, Object> b) {
-        return Result.ok(batchPayService.renderPay(
-                (String) b.get("enterprise_id"), (String) b.get("out_batch_no")));
+        return Result.ok(batchPayService.renderPay((String) b.get("out_batch_no")));
     }
 
     @PreAuthorize("@perm.hasAny('module_payment:account:transfer:list')")
     @GetMapping("/query")
     public Result<Map<String, Object>> batchQuery(
-            @RequestParam("enterprise_id") String enterpriseId,
             @RequestParam("out_batch_no") String outBatchNo) {
-        return Result.ok(batchPayService.batchQuery(enterpriseId, outBatchNo));
+        return Result.ok(batchPayService.batchQuery(outBatchNo));
     }
 
     @PreAuthorize("@perm.hasAny('module_payment:account:transfer')")
     @PostMapping("/close")
     public Result<Map<String, String>> batchClose(@RequestBody Map<String, Object> b) {
-        return Result.ok(batchPayService.batchClose(
-                (String) b.get("enterprise_id"), (String) b.get("out_batch_no")));
+        return Result.ok(batchPayService.batchClose((String) b.get("out_batch_no")));
     }
 
     @PreAuthorize("@perm.hasAny('module_payment:account:transfer:list')")
@@ -82,21 +79,20 @@ public class BatchPayController {
     public Result<PageResult<BatchOrderEntity>> batchList(
             @RequestParam(name = "page_no", defaultValue = "1") int pageNo,
             @RequestParam(name = "page_size", defaultValue = "20") int pageSize,
-            @RequestParam(name = "enterprise_id", required = false) String enterpriseId,
+            @RequestParam(name = "participant_id", required = false) String participantId,
             @RequestParam(name = "status", required = false) String status,
             @RequestParam(name = "start_time", required = false) String startTime,
             @RequestParam(name = "end_time", required = false) String endTime) {
-        return Result.ok(batchPayService.batchList(enterpriseId, status, startTime, endTime, pageNo, pageSize));
+        return Result.ok(batchPayService.batchList(participantId, status, startTime, endTime, pageNo, pageSize));
     }
 
     @PreAuthorize("@perm.hasAny('module_payment:account:transfer:detail')")
     @GetMapping("/detail")
     public Result<Map<String, Object>> batchDetail(
-            @RequestParam("enterprise_id") String enterpriseId,
             @RequestParam("out_batch_no") String outBatchNo,
             @RequestParam(name = "page_no", defaultValue = "1") int pageNo,
             @RequestParam(name = "page_size", defaultValue = "20") int pageSize) {
-        return Result.ok(batchPayService.batchDetail(enterpriseId, outBatchNo, pageNo, pageSize));
+        return Result.ok(batchPayService.batchDetail(outBatchNo, pageNo, pageSize));
     }
 
     @PreAuthorize("@perm.hasAny('module_payment:account:transfer:list')")
@@ -111,12 +107,12 @@ public class BatchPayController {
     @PreAuthorize("@perm.hasAny('module_payment:account:transfer:list')")
     @GetMapping("/export")
     public void batchExport(
-            @RequestParam(name = "enterprise_id", required = false) String enterpriseId,
+            @RequestParam(name = "participant_id", required = false) String participantId,
             @RequestParam(name = "status", required = false) String status,
             @RequestParam(name = "start_time", required = false) String startTime,
             @RequestParam(name = "end_time", required = false) String endTime,
             HttpServletResponse response) throws IOException {
-        byte[] bytes = batchPayService.batchExport(enterpriseId, status, startTime, endTime);
+        byte[] bytes = batchPayService.batchExport(participantId, status, startTime, endTime);
         response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
         response.setHeader("Content-Disposition", "attachment; filename=batch_pay_report.xlsx");
         response.getOutputStream().write(bytes);

+ 3 - 2
java/src/main/java/com/payment/platform/module/payment/batch/dto/BatchCreateDTO.java

@@ -14,8 +14,9 @@ import java.util.Map;
 @Data
 public class BatchCreateDTO {
 
-    @Schema(description = "企业ID")
-    private String enterpriseId;
+    @NotBlank(message = "请选择付款主体")
+    @Schema(description = "付款主体(授权主体支付宝uid,制单时从已授权主体选择)")
+    private String participantId;
 
     @Schema(description = "租户ID(内部,由 Controller 注入)")
     private Long tenantId;

+ 2 - 2
java/src/main/java/com/payment/platform/module/payment/batch/entity/BatchAuthorizeEntity.java

@@ -2,7 +2,7 @@ package com.payment.platform.module.payment.batch.entity;
 
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.payment.platform.common.base.PaymentEnterpriseBaseEntity;
+import com.payment.platform.common.base.PaymentTenantBaseEntity;
 import com.payment.platform.common.handler.JsonbTypeHandler;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -13,7 +13,7 @@ import java.time.OffsetDateTime;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("pay_batch_authorize")
-public class BatchAuthorizeEntity extends PaymentEnterpriseBaseEntity {
+public class BatchAuthorizeEntity extends PaymentTenantBaseEntity {
     private String outBizNo;
     private String participantId;
     private String participantIdType;

+ 2 - 2
java/src/main/java/com/payment/platform/module/payment/batch/entity/BatchDetailEntity.java

@@ -2,7 +2,7 @@ package com.payment.platform.module.payment.batch.entity;
 
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.payment.platform.common.base.PaymentEnterpriseBaseEntity;
+import com.payment.platform.common.base.PaymentTenantBaseEntity;
 import com.payment.platform.common.handler.JsonbTypeHandler;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -13,7 +13,7 @@ import java.time.OffsetDateTime;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("pay_batch_detail")
-public class BatchDetailEntity extends PaymentEnterpriseBaseEntity {
+public class BatchDetailEntity extends PaymentTenantBaseEntity {
     private Long batchId;
     private String outBizNo;
     private BigDecimal amount;

+ 2 - 2
java/src/main/java/com/payment/platform/module/payment/batch/entity/BatchOrderEntity.java

@@ -2,7 +2,7 @@ package com.payment.platform.module.payment.batch.entity;
 
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.payment.platform.common.base.PaymentEnterpriseBaseEntity;
+import com.payment.platform.common.base.PaymentTenantBaseEntity;
 import com.payment.platform.common.handler.JsonbTypeHandler;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -13,7 +13,7 @@ import java.time.OffsetDateTime;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("pay_batch_order")
-public class BatchOrderEntity extends PaymentEnterpriseBaseEntity {
+public class BatchOrderEntity extends PaymentTenantBaseEntity {
     private String outBatchNo;
     private String batchTransId;
     private BigDecimal totalAmount;

+ 1 - 1
java/src/main/java/com/payment/platform/module/payment/batch/scheduler/BatchStatusPollScheduler.java

@@ -33,7 +33,7 @@ public class BatchStatusPollScheduler {
         int count = 0;
         for (BatchOrderEntity batch : batches) {
             try {
-                batchPayService.batchQuery(batch.getEnterpriseId(), batch.getOutBatchNo());
+                batchPayService.batchQuery(batch.getOutBatchNo());
                 count++;
             } catch (Exception e) {
                 log.error("[批次状态轮询] 轮询批次异常: out_batch_no={}, error={}",

+ 32 - 67
java/src/main/java/com/payment/platform/module/payment/batch/service/AlipayBatchPayService.java

@@ -43,8 +43,6 @@ import java.time.OffsetDateTime;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
-import com.payment.platform.module.payment.enterprise.entity.EnterpriseEntity;
-import com.payment.platform.module.payment.enterprise.mapper.EnterpriseMapper;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -75,7 +73,6 @@ public class AlipayBatchPayService {
     private final BatchAuthorizeMapper batchAuthorizeMapper;
     private final BatchOrderMapper batchOrderMapper;
     private final BatchDetailMapper batchDetailMapper;
-    private final EnterpriseMapper enterpriseMapper;
 
     // ==================== 批次 ====================
 
@@ -139,25 +136,23 @@ public class AlipayBatchPayService {
             model.setOrderTitle(dto.getOrderTitle());
             if (dto.getTimeExpire() != null) model.setTimeExpire(dto.getTimeExpire());
             if (dto.getRemark() != null) model.setRemark(dto.getRemark());
-            // 付款方 + 制单授权协议(Ruling 19/22: 不接受客户端指定,付款方身份遵循系统惯例 identity 优先回退 enterprise_id)
-            EnterpriseEntity ent = requireEnterprise(dto.getEnterpriseId());
-            String payerUid = payerIdentity(ent);
-            // 未完成授权不允许制单: 取该付款方最新生效授权(AUTHED/NORMAL)→ 无则本地预检拦截;
+            // 付款方 + 制单授权协议(账号级: 付款方 = 表单选择的授权主体,spec 5.3)
+            String payerUid = dto.getParticipantId();
+            // 未完成授权不允许制单: 取该主体最新生效授权(AUTHED/NORMAL)→ 无则本地预检拦截;
             // 支付宝侧 AUTH_INFO_NOT_EXISTS 兜底保留(防本地与支付宝状态不一致的竞态)
             BatchAuthorizeEntity authed = batchAuthorizeMapper.selectOne(
                     new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchAuthorizeEntity>()
-                            .eq(BatchAuthorizeEntity::getEnterpriseId, dto.getEnterpriseId())
                             .eq(BatchAuthorizeEntity::getParticipantId, payerUid)
                             .in(BatchAuthorizeEntity::getStatus, "AUTHED", "NORMAL")
                             .orderByDesc(BatchAuthorizeEntity::getId)
                             .last("LIMIT 1"));
             if (authed == null)
-                throw new BusinessException(400, "该企业尚未完成制单授权,请先在「制单授权」中生成授权链接并完成授权");
+                throw new BusinessException(400, "该主体尚未完成制单授权,请先在「制单授权」中生成授权链接并完成授权");
             // 协议号自动带出: 取该笔生效授权的 agreement_no(支付宝生成,不接受客户端指定)
             String agreementNo = authed.getAgreementNo();
             Participant payer = new Participant();
             payer.setIdentity(payerUid);
-            payer.setIdentityType(payerIdentityType(ent));
+            payer.setIdentityType("ALIPAY_USER_ID");
             if (agreementNo != null && !agreementNo.isBlank()) {
                 Map<String, String> ext = new LinkedHashMap<>();
                 ext.put("agreement_no", agreementNo);
@@ -178,8 +173,9 @@ public class AlipayBatchPayService {
 
             AlipayFundBatchCreateRequest request = new AlipayFundBatchCreateRequest();
             request.setBizModel(model);
+            // client 按主体冗余的服务商解析(getClientByProvider: profile → 服务商默认 → yml 回退)
             AlipayFundBatchCreateResponse response =
-                    alipayClientFactory.getClient(dto.getEnterpriseId(), BIZ_TYPE).certificateExecute(request);
+                    alipayClientFactory.getClientByProvider(authed.getServiceProviderId(), BIZ_TYPE).certificateExecute(request);
             if (!response.isSuccess()) {
                 // 幂等兜底: UNIQUE_VIOLATION 说明支付宝侧已受理同单号批次,查库返回已受理信息而非报错
                 if ("UNIQUE_VIOLATION".equals(response.getSubCode())) {
@@ -197,8 +193,8 @@ public class AlipayBatchPayService {
             }
 
             BatchOrderEntity order = new BatchOrderEntity();
-            order.setEnterpriseId(dto.getEnterpriseId());
             order.setTenantId(dto.getTenantId());
+            order.setServiceProviderId(authed.getServiceProviderId());
             order.setOutBatchNo(dto.getOutBatchNo());
             order.setBatchTransId(response.getBatchTransId());
             order.setTotalAmount(total);
@@ -216,7 +212,6 @@ public class AlipayBatchPayService {
 
             for (BatchCreateDTO.BatchDetailDTO d : dto.getDetails()) {
                 BatchDetailEntity de = new BatchDetailEntity();
-                de.setEnterpriseId(dto.getEnterpriseId());
                 de.setTenantId(dto.getTenantId());
                 de.setBatchId(order.getId());
                 de.setOutBizNo(d.getOutBizNo());
@@ -243,13 +238,11 @@ public class AlipayBatchPayService {
                 "status", existing.getStatus());
     }
 
-    /** alipay.fund.trans.render.pay — 生成 PC 支付页链接 */
-    public Map<String, String> renderPay(String enterpriseId, String outBatchNo) {
-        requireEnterpriseId(enterpriseId);
+    /** alipay.fund.trans.render.pay — 生成 PC 支付页链接(租户隔离由拦截器自动追加) */
+    public Map<String, String> renderPay(String outBatchNo) {
         BatchOrderEntity order = batchOrderMapper.selectOne(
                 new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchOrderEntity>()
-                        .eq(BatchOrderEntity::getOutBatchNo, outBatchNo)
-                        .eq(BatchOrderEntity::getEnterpriseId, enterpriseId));
+                        .eq(BatchOrderEntity::getOutBatchNo, outBatchNo));
         if (order == null) throw new BusinessException(404, "批次不存在");
         // INIT=已受理 / WAIT_PAY=等待支付(render 过支付链接但未支付)均可生成(或重新生成)支付链接——
         // 用户实测: 本地被定时同步成 WAIT_PAY 后无法二次发起支付
@@ -264,8 +257,9 @@ public class AlipayBatchPayService {
             model.setInitializeCodeType(RENDER_INITIALIZE_CODE_TYPE);
             AlipayFundTransRenderPayRequest request = new AlipayFundTransRenderPayRequest();
             request.setBizModel(model);
+            // client 按订单冗余的服务商解析(主体解绑后批次仍可支付,spec D5)
             AlipayFundTransRenderPayResponse response =
-                    alipayClientFactory.getClient(enterpriseId, BIZ_TYPE).certificateExecute(request);
+                    alipayClientFactory.getClientByProvider(order.getServiceProviderId(), BIZ_TYPE).certificateExecute(request);
             if (response == null)
                 throw new BusinessException(400, "生成支付页面失败: 无响应");
             // 先校验成功再给前端(同文件其他 5 处调用同款)——业务失败时若直接把 body 当链接返回,
@@ -285,13 +279,11 @@ public class AlipayBatchPayService {
         }
     }
 
-    /** alipay.fund.batch.detail.query — 查询批次+明细状态并回写 DB */
-    public Map<String, Object> batchQuery(String enterpriseId, String outBatchNo) {
-        requireEnterpriseId(enterpriseId);
+    /** alipay.fund.batch.detail.query — 查询批次+明细状态并回写 DB(租户隔离由拦截器自动追加) */
+    public Map<String, Object> batchQuery(String outBatchNo) {
         BatchOrderEntity order = batchOrderMapper.selectOne(
                 new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchOrderEntity>()
-                        .eq(BatchOrderEntity::getOutBatchNo, outBatchNo)
-                        .eq(BatchOrderEntity::getEnterpriseId, enterpriseId));
+                        .eq(BatchOrderEntity::getOutBatchNo, outBatchNo));
         if (order == null) throw new BusinessException(404, "批次不存在");
         try {
             AlipayFundBatchDetailQueryModel model = new AlipayFundBatchDetailQueryModel();
@@ -301,7 +293,7 @@ public class AlipayBatchPayService {
             AlipayFundBatchDetailQueryRequest request = new AlipayFundBatchDetailQueryRequest();
             request.setBizModel(model);
             AlipayFundBatchDetailQueryResponse response =
-                    alipayClientFactory.getClient(enterpriseId, BIZ_TYPE).certificateExecute(request);
+                    alipayClientFactory.getClientByProvider(order.getServiceProviderId(), BIZ_TYPE).certificateExecute(request);
             if (!response.isSuccess())
                 throw new BusinessException(400, "查询批次失败: " + response.getMsg());
             // 批次状态回写(SDK 字段 batch_status,非 status — 已 javap 实证)
@@ -342,13 +334,11 @@ public class AlipayBatchPayService {
         }
     }
 
-    /** alipay.fund.batch.close — 主动关闭未支付批次 */
-    public Map<String, String> batchClose(String enterpriseId, String outBatchNo) {
-        requireEnterpriseId(enterpriseId);
+    /** alipay.fund.batch.close — 主动关闭未支付批次(租户隔离由拦截器自动追加) */
+    public Map<String, String> batchClose(String outBatchNo) {
         BatchOrderEntity order = batchOrderMapper.selectOne(
                 new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchOrderEntity>()
-                        .eq(BatchOrderEntity::getOutBatchNo, outBatchNo)
-                        .eq(BatchOrderEntity::getEnterpriseId, enterpriseId));
+                        .eq(BatchOrderEntity::getOutBatchNo, outBatchNo));
         if (order == null) throw new BusinessException(404, "批次不存在");
         // INIT=已受理 / WAIT_PAY=等待支付(未支付)均可关闭——用户实测: 本地被定时同步成 WAIT_PAY 后关闭按钮直接消失
         if (!"INIT".equals(order.getStatus()) && !"WAIT_PAY".equals(order.getStatus()))
@@ -361,11 +351,11 @@ public class AlipayBatchPayService {
             AlipayFundBatchCloseRequest request = new AlipayFundBatchCloseRequest();
             request.setBizModel(model);
             AlipayFundBatchCloseResponse response =
-                    alipayClientFactory.getClient(enterpriseId, BIZ_TYPE).certificateExecute(request);
+                    alipayClientFactory.getClientByProvider(order.getServiceProviderId(), BIZ_TYPE).certificateExecute(request);
             if (!response.isSuccess()) {
                 // 关闭失败多为支付宝侧批次已不可关闭(如 INVALID 明细全部无效)——回写真实状态,
                 // 避免本地残留 INIT 让用户反复点关闭(用户实测: BATCH_ORDER_STATUS_INVALID)
-                syncBatchStatusFromAlipay(order, enterpriseId);
+                syncBatchStatusFromAlipay(order);
                 throw new BusinessException(400, "关闭批次失败: " + response.getMsg() + " (" + response.getSubCode() + ")");
             }
             order.setStatus("DISUSE");
@@ -377,7 +367,7 @@ public class AlipayBatchPayService {
     }
 
     /** 关闭失败后回写支付宝侧真实批次状态(detail.query),失败静默(不掩盖关闭错误本身) */
-    private void syncBatchStatusFromAlipay(BatchOrderEntity order, String enterpriseId) {
+    private void syncBatchStatusFromAlipay(BatchOrderEntity order) {
         try {
             AlipayFundBatchDetailQueryModel model = new AlipayFundBatchDetailQueryModel();
             model.setOutBatchNo(order.getOutBatchNo());
@@ -386,7 +376,7 @@ public class AlipayBatchPayService {
             AlipayFundBatchDetailQueryRequest request = new AlipayFundBatchDetailQueryRequest();
             request.setBizModel(model);
             AlipayFundBatchDetailQueryResponse response =
-                    alipayClientFactory.getClient(enterpriseId, BIZ_TYPE).certificateExecute(request);
+                    alipayClientFactory.getClientByProvider(order.getServiceProviderId(), BIZ_TYPE).certificateExecute(request);
             if (response.isSuccess() && response.getBatchStatus() != null
                     && !response.getBatchStatus().equals(order.getStatus())) {
                 order.setStatus(response.getBatchStatus());
@@ -415,11 +405,11 @@ public class AlipayBatchPayService {
 
     // ==================== 列表 / 详情 / 导出 ====================
 
-    /** 批次列表(分页,状态/时间筛选)— 时间解析照抄 AccountService.parseDateTime 模式 */
-    public PageResult<BatchOrderEntity> batchList(String enterpriseId, String status,
+    /** 批次列表(分页,付款主体/状态/时间筛选)— 时间解析照抄 AccountService.parseDateTime 模式 */
+    public PageResult<BatchOrderEntity> batchList(String participantId, String status,
             String startTime, String endTime, int pageNo, int pageSize) {
         var w = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchOrderEntity>()
-                .eq(enterpriseId != null && !enterpriseId.isBlank(), BatchOrderEntity::getEnterpriseId, enterpriseId)
+                .eq(participantId != null && !participantId.isBlank(), BatchOrderEntity::getPayerUid, participantId)
                 .eq(status != null && !status.isBlank(), BatchOrderEntity::getStatus, status)
                 .orderByDesc(BatchOrderEntity::getId);
         if (startTime != null && !startTime.isBlank()) {
@@ -451,36 +441,11 @@ public class AlipayBatchPayService {
         }
     }
 
-    /** 企业校验 + 身份解析: 付款方/参与方支付宝身份 = 企业入驻身份 identity(配套 identityType),为空回退 enterprise_id(同 AlipayTransferService.createOnboard 惯例) */
-    private EnterpriseEntity requireEnterprise(String enterpriseId) {
-        requireEnterpriseId(enterpriseId);
-        EnterpriseEntity ent = enterpriseMapper.selectByEnterpriseIdIgnoreTenant(enterpriseId);
-        if (ent == null)
-            throw new BusinessException(400, "企业不存在");
-        return ent;
-    }
-
-    private static String payerIdentity(EnterpriseEntity ent) {
-        return ent.getIdentity() != null ? ent.getIdentity() : ent.getEnterpriseId();
-    }
-
-    private static String payerIdentityType(EnterpriseEntity ent) {
-        return ent.getIdentityType() != null ? ent.getIdentityType() : "ALIPAY_USER_ID";
-    }
-
-    /** 租户隔离: 企业 ID 是业务必需参数,为空直接拒绝(防御 Controller body 路径的零校验) */
-    private static void requireEnterpriseId(String enterpriseId) {
-        if (enterpriseId == null || enterpriseId.isBlank())
-            throw new BusinessException(400, "缺少企业ID");
-    }
-
-    /** 批次详情 + 明细分页(enterprise_id 租户隔离,参照 batchList 过滤写法) */
-    public Map<String, Object> batchDetail(String enterpriseId, String outBatchNo, int pageNo, int pageSize) {
-        requireEnterpriseId(enterpriseId);
+    /** 批次详情 + 明细分页(租户隔离由拦截器自动追加) */
+    public Map<String, Object> batchDetail(String outBatchNo, int pageNo, int pageSize) {
         BatchOrderEntity order = batchOrderMapper.selectOne(
                 new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchOrderEntity>()
-                        .eq(BatchOrderEntity::getOutBatchNo, outBatchNo)
-                        .eq(BatchOrderEntity::getEnterpriseId, enterpriseId));
+                        .eq(BatchOrderEntity::getOutBatchNo, outBatchNo));
         if (order == null) throw new BusinessException(404, "批次不存在");
         Page<BatchDetailEntity> detailPage = batchDetailMapper.selectPage(new Page<>(pageNo, pageSize),
                 new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchDetailEntity>()
@@ -491,10 +456,10 @@ public class AlipayBatchPayService {
     }
 
     /** 批次导出 — 照抄 AccountService.transferExport 的组装写法(ExcelUtil.exportToExcel(listData, mappingDict)) */
-    public byte[] batchExport(String enterpriseId, String status, String startTime, String endTime) {
+    public byte[] batchExport(String participantId, String status, String startTime, String endTime) {
         try {
             var w = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchOrderEntity>()
-                    .eq(enterpriseId != null && !enterpriseId.isBlank(), BatchOrderEntity::getEnterpriseId, enterpriseId)
+                    .eq(participantId != null && !participantId.isBlank(), BatchOrderEntity::getPayerUid, participantId)
                     .eq(status != null && !status.isBlank(), BatchOrderEntity::getStatus, status)
                     .orderByDesc(BatchOrderEntity::getId);
             if (startTime != null && !startTime.isBlank()) {

+ 9 - 10
java/src/test/java/com/payment/platform/module/payment/batch/scheduler/BatchStatusPollSchedulerTest.java

@@ -27,9 +27,8 @@ class BatchStatusPollSchedulerTest {
         scheduler = new BatchStatusPollScheduler(batchPayService);
     }
 
-    private BatchOrderEntity batch(String enterpriseId, String outBatchNo) {
+    private BatchOrderEntity batch(String outBatchNo) {
         BatchOrderEntity b = new BatchOrderEntity();
-        b.setEnterpriseId(enterpriseId);
         b.setOutBatchNo(outBatchNo);
         return b;
     }
@@ -37,15 +36,15 @@ class BatchStatusPollSchedulerTest {
     @Test
     void pollPendingBatches_singleFailure_doesNotInterrupt() {
         // 照抄 F2fTradePollScheduler 模式: 单条异常 catch 记日志不中断
-        BatchOrderEntity b1 = batch("E1", "B1");
-        BatchOrderEntity b2 = batch("E2", "B2");
+        BatchOrderEntity b1 = batch("B1");
+        BatchOrderEntity b2 = batch("B2");
         when(batchPayService.getPendingBatches()).thenReturn(List.of(b1, b2));
-        doThrow(new BusinessException(400, "查询批次失败")).when(batchPayService).batchQuery(eq("E1"), eq("B1"));
+        doThrow(new BusinessException(400, "查询批次失败")).when(batchPayService).batchQuery(eq("B1"));
 
         assertDoesNotThrow(scheduler::pollPendingBatches);
 
         // 第二条仍被执行
-        verify(batchPayService).batchQuery("E2", "B2");
+        verify(batchPayService).batchQuery("B2");
     }
 
     @Test
@@ -54,16 +53,16 @@ class BatchStatusPollSchedulerTest {
 
         scheduler.pollPendingBatches();
 
-        verify(batchPayService, never()).batchQuery(any(), any());
+        verify(batchPayService, never()).batchQuery(any());
     }
 
     @Test
     void pollPendingBatches_allSuccess_pollsEach() {
-        when(batchPayService.getPendingBatches()).thenReturn(List.of(batch("E1", "B1"), batch("E1", "B2")));
+        when(batchPayService.getPendingBatches()).thenReturn(List.of(batch("B1"), batch("B2")));
 
         scheduler.pollPendingBatches();
 
-        verify(batchPayService).batchQuery("E1", "B1");
-        verify(batchPayService).batchQuery("E1", "B2");
+        verify(batchPayService).batchQuery("B1");
+        verify(batchPayService).batchQuery("B2");
     }
 }

+ 66 - 132
java/src/test/java/com/payment/platform/module/payment/batch/service/AlipayBatchPayServiceTest.java

@@ -27,8 +27,6 @@ import com.payment.platform.module.payment.batch.entity.BatchOrderEntity;
 import com.payment.platform.module.payment.batch.mapper.BatchAuthorizeMapper;
 import com.payment.platform.module.payment.batch.mapper.BatchDetailMapper;
 import com.payment.platform.module.payment.batch.mapper.BatchOrderMapper;
-import com.payment.platform.module.payment.enterprise.entity.EnterpriseEntity;
-import com.payment.platform.module.payment.enterprise.mapper.EnterpriseMapper;
 import jakarta.validation.ConstraintViolation;
 import jakarta.validation.Validation;
 import jakarta.validation.Validator;
@@ -59,19 +57,15 @@ class AlipayBatchPayServiceTest {
     @Mock private BatchAuthorizeMapper batchAuthorizeMapper;
     @Mock private BatchOrderMapper batchOrderMapper;
     @Mock private BatchDetailMapper batchDetailMapper;
-    @Mock private EnterpriseMapper enterpriseMapper;
     private AlipayBatchPayService service;
 
     @BeforeEach
     void setUp() {
         service = new AlipayBatchPayService(alipayClientFactory, batchAuthorizeMapper, batchOrderMapper,
-                batchDetailMapper, enterpriseMapper);
-        // lenient: 重复授权预检测试用例在到达 getClient 前即抛异常,该 stub 不会被使用
-        lenient().when(alipayClientFactory.getClient("E100", "BATCH_PAY")).thenReturn(alipayClient);
-        // 付款方身份解析: E100 企业无 identity → 回退 enterprise_id(createOnboard 惯例);identity 有值用例单独 stub
-        EnterpriseEntity ent = new EnterpriseEntity();
-        ent.setEnterpriseId("E100");
-        lenient().when(enterpriseMapper.selectByEnterpriseIdIgnoreTenant("E100")).thenReturn(ent);
+                batchDetailMapper);
+        // lenient: 预检拦截/本地不存在的用例在到达 getClient 前即抛异常,该 stub 不会被使用
+        // 账号级: client 按订单冗余的 service_provider_id 解析(订单 mock 统一 setServiceProviderId(1L))
+        lenient().when(alipayClientFactory.getClientByProvider(1L, "BATCH_PAY")).thenReturn(alipayClient);
         // 初始化 MyBatis-Plus lambda 元数据缓存,使 LambdaQueryWrapper.getSqlSegment() 可在无 Spring 上下文的单测中工作
         MybatisConfiguration configuration = new MybatisConfiguration();
         TableInfoHelper.initTableInfo(new MapperBuilderAssistant(configuration, ""), BatchOrderEntity.class);
@@ -89,11 +83,12 @@ class AlipayBatchPayServiceTest {
         // 制单预检: 已有 AUTHED 授权(协议号由系统自动带出,不接受客户端指定)
         BatchAuthorizeEntity authed = new BatchAuthorizeEntity();
         authed.setStatus("AUTHED");
+        authed.setServiceProviderId(1L);
         authed.setAgreementNo("AGMT001");
         when(batchAuthorizeMapper.selectOne(any())).thenReturn(authed);
 
         BatchCreateDTO dto = new BatchCreateDTO();
-        dto.setEnterpriseId("E100");
+        dto.setParticipantId("2088111122223333");
         dto.setOutBatchNo("B1");
         dto.setOrderTitle("202608报销");
         dto.setTransferSceneName("佣金报酬");
@@ -174,11 +169,12 @@ class AlipayBatchPayServiceTest {
         when(batchOrderMapper.selectOne(any())).thenReturn(existing);
         BatchAuthorizeEntity authed = new BatchAuthorizeEntity();
         authed.setStatus("AUTHED");
+        authed.setServiceProviderId(1L);
         authed.setAgreementNo("AGMT001");
         when(batchAuthorizeMapper.selectOne(any())).thenReturn(authed);
 
         BatchCreateDTO dto = new BatchCreateDTO();
-        dto.setEnterpriseId("E100");
+        dto.setParticipantId("2088111122223333");
         dto.setOutBatchNo("B1");
         dto.setOrderTitle("t");
         BatchCreateDTO.BatchDetailDTO detail = new BatchCreateDTO.BatchDetailDTO();
@@ -201,7 +197,7 @@ class AlipayBatchPayServiceTest {
     void batchCreate_duplicateDetailOutBizNo_throwsBusinessException() throws AlipayApiException {
         // Ruling 5-2: 每笔 out_biz_no 批内唯一
         BatchCreateDTO dto = new BatchCreateDTO();
-        dto.setEnterpriseId("E100");
+        dto.setParticipantId("2088111122223333");
         dto.setOutBatchNo("B1");
         dto.setOrderTitle("t");
         BatchCreateDTO.BatchDetailDTO d1 = new BatchCreateDTO.BatchDetailDTO();
@@ -228,7 +224,7 @@ class AlipayBatchPayServiceTest {
     @Test
     void renderPay_returnsPayUrl() throws AlipayApiException {
         BatchOrderEntity order = new BatchOrderEntity();
-        order.setEnterpriseId("E100");
+        order.setServiceProviderId(1L);
         order.setOutBatchNo("B1");
         order.setBatchTransId("BT1");
         order.setStatus("INIT");
@@ -238,7 +234,7 @@ class AlipayBatchPayServiceTest {
         resp.setInitializeCode("https://p.tb.cn/_2PG4jfHMVvg9vqBUdtx1HZ");
         when(alipayClient.certificateExecute(any(AlipayFundTransRenderPayRequest.class))).thenReturn(resp);
 
-        Map<String, String> result = service.renderPay("E100", "B1");
+        Map<String, String> result = service.renderPay("B1");
 
         // render.pay 返回收银台短链接 initialize_code(用户实测成功响应实证),直接作为 pay_url
         assertEquals("https://p.tb.cn/_2PG4jfHMVvg9vqBUdtx1HZ", result.get("pay_url"));
@@ -260,7 +256,7 @@ class AlipayBatchPayServiceTest {
         // 支付宝业务失败(非 10000)必须抛错且不落库——
         // 否则错误响应会被当 pay_url 返回、前端拿错误内容开新窗口(用户实测: 窗口显示整页 INVALID_PARAMETER JSON)
         BatchOrderEntity order = new BatchOrderEntity();
-        order.setEnterpriseId("E100");
+        order.setServiceProviderId(1L);
         order.setOutBatchNo("B1");
         order.setBatchTransId("BT1");
         order.setStatus("INIT");
@@ -271,7 +267,7 @@ class AlipayBatchPayServiceTest {
         resp.setSubMsg("参数[target_terminal_type]错误, 原因: 必须传入参数[target_terminal_type]");
         when(alipayClient.certificateExecute(any(AlipayFundTransRenderPayRequest.class))).thenReturn(resp);
 
-        BusinessException ex = assertThrows(BusinessException.class, () -> service.renderPay("E100", "B1"));
+        BusinessException ex = assertThrows(BusinessException.class, () -> service.renderPay("B1"));
 
         assertEquals(400, ex.getCode());
         assertTrue(ex.getMessage().contains("INVALID_PARAMETER"), ex.getMessage());
@@ -282,7 +278,7 @@ class AlipayBatchPayServiceTest {
     void renderPay_successWithoutInitializeCode_throws() throws AlipayApiException {
         // 成功响应但支付宝未返回跳转链接(防御: 不把 null 当 pay_url 返回)
         BatchOrderEntity order = new BatchOrderEntity();
-        order.setEnterpriseId("E100");
+        order.setServiceProviderId(1L);
         order.setOutBatchNo("B1");
         order.setBatchTransId("BT1");
         order.setStatus("INIT");
@@ -291,7 +287,7 @@ class AlipayBatchPayServiceTest {
         resp.setCode("10000");
         when(alipayClient.certificateExecute(any(AlipayFundTransRenderPayRequest.class))).thenReturn(resp);
 
-        BusinessException ex = assertThrows(BusinessException.class, () -> service.renderPay("E100", "B1"));
+        BusinessException ex = assertThrows(BusinessException.class, () -> service.renderPay("B1"));
 
         assertEquals(400, ex.getCode());
         assertTrue(ex.getMessage().contains("跳转链接"), ex.getMessage());
@@ -302,7 +298,7 @@ class AlipayBatchPayServiceTest {
     void batchQuery_syncsBatchStatus() throws AlipayApiException {
         BatchOrderEntity order = new BatchOrderEntity();
         order.setId(1L);
-        order.setEnterpriseId("E100");
+        order.setServiceProviderId(1L);
         order.setOutBatchNo("B1");
         order.setBatchTransId("BT1");
         order.setStatus("INIT");
@@ -312,7 +308,7 @@ class AlipayBatchPayServiceTest {
         resp.setBatchStatus("SUCCESS");
         when(alipayClient.certificateExecute(any(AlipayFundBatchDetailQueryRequest.class))).thenReturn(resp);
 
-        Map<String, Object> result = service.batchQuery("E100", "B1");
+        Map<String, Object> result = service.batchQuery("B1");
 
         assertEquals("SUCCESS", result.get("status"));
         assertEquals("SUCCESS", order.getStatus());
@@ -322,7 +318,7 @@ class AlipayBatchPayServiceTest {
     @Test
     void batchClose_callsCloseApi() throws AlipayApiException {
         BatchOrderEntity order = new BatchOrderEntity();
-        order.setEnterpriseId("E100");
+        order.setServiceProviderId(1L);
         order.setOutBatchNo("B1");
         order.setBatchTransId("BT1");
         order.setStatus("INIT");
@@ -330,7 +326,7 @@ class AlipayBatchPayServiceTest {
         when(alipayClient.certificateExecute(any(AlipayFundBatchCloseRequest.class)))
                 .thenReturn(new AlipayFundBatchCloseResponse());
 
-        service.batchClose("E100", "B1");
+        service.batchClose("B1");
         verify(alipayClient).certificateExecute(any(AlipayFundBatchCloseRequest.class));
     }
 
@@ -338,11 +334,11 @@ class AlipayBatchPayServiceTest {
     void batchList_invalidTime_throwsBusinessException() {
         // Ruling 5-3: 非法时间输入应抛 400 而非 DateTimeParseException→500
         BusinessException ex = assertThrows(BusinessException.class,
-                () -> service.batchList("E100", null, "2026-13-45 99:99", null, 1, 20));
+                () -> service.batchList(null, null, "2026-13-45 99:99", null, 1, 20));
         assertEquals(400, ex.getCode());
 
         BusinessException ex2 = assertThrows(BusinessException.class,
-                () -> service.batchList("E100", null, null, "2026-08-25 25:61", 1, 20));
+                () -> service.batchList(null, null, null, "2026-08-25 25:61", 1, 20));
         assertEquals(400, ex2.getCode());
     }
 
@@ -350,7 +346,7 @@ class AlipayBatchPayServiceTest {
     void batchExport_invalidTime_throwsBusinessException() {
         // Ruling 5-3: 导出 catch-all 需区分 DateTimeParseException 抛 400
         BusinessException ex = assertThrows(BusinessException.class,
-                () -> service.batchExport("E100", null, "bad-time", null));
+                () -> service.batchExport(null, null, "bad-time", null));
         assertEquals(400, ex.getCode());
     }
 
@@ -365,11 +361,12 @@ class AlipayBatchPayServiceTest {
         // 本地预检通过(有 AUTHED 记录)后仍触发支付宝侧 AUTH_INFO_NOT_EXISTS 兜底路径
         BatchAuthorizeEntity authed = new BatchAuthorizeEntity();
         authed.setStatus("AUTHED");
+        authed.setServiceProviderId(1L);
         authed.setAgreementNo("AGMT001");
         when(batchAuthorizeMapper.selectOne(any())).thenReturn(authed);
 
         BatchCreateDTO dto = new BatchCreateDTO();
-        dto.setEnterpriseId("E100");
+        dto.setParticipantId("2088111122223333");
         dto.setOutBatchNo("B1");
         dto.setOrderTitle("202608报销");
         BatchCreateDTO.BatchDetailDTO detail = new BatchCreateDTO.BatchDetailDTO();
@@ -385,50 +382,50 @@ class AlipayBatchPayServiceTest {
         assertTrue(ex.getMessage().contains("制单授权"), "应提示先完成制单授权: " + ex.getMessage());
     }
 
-    // ==================== C2: 租户隔离(renderPay/batchQuery/batchClose 缺 enterpriseId 条件) ====================
+    // ==================== C2: 批次不存在(本地查不到 → 404,租户隔离由 TenantInnerInterceptor 自动追加) ====================
 
     @Test
-    void renderPay_otherEnterpriseBatch_notFound() {
-        // 他人批次: selectOne 带 enterpriseId 条件查不到 → 404
+    void renderPay_batchNotFound_throws404() {
+        // 本地批次不存在(他租户批次或被拦截器过滤)→ 404
         when(batchOrderMapper.selectOne(any())).thenReturn(null);
 
-        BusinessException ex = assertThrows(BusinessException.class, () -> service.renderPay("E999", "B1"));
+        BusinessException ex = assertThrows(BusinessException.class, () -> service.renderPay("B1"));
 
         assertEquals(404, ex.getCode());
         @SuppressWarnings({ "unchecked", "rawtypes" })
         ArgumentCaptor<LambdaQueryWrapper<BatchOrderEntity>> cap = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
         verify(batchOrderMapper).selectOne(cap.capture());
-        assertTrue(cap.getValue().getSqlSegment().contains("enterprise_id"),
-                "renderPay 查询必须带 enterpriseId 条件: " + cap.getValue().getSqlSegment());
+        assertTrue(cap.getValue().getSqlSegment().contains("out_batch_no"),
+                "renderPay 查询必须按 out_batch_no: " + cap.getValue().getSqlSegment());
     }
 
     @Test
-    void batchQuery_otherEnterpriseBatch_notFound() {
+    void batchQuery_batchNotFound_throws404() {
         when(batchOrderMapper.selectOne(any())).thenReturn(null);
 
-        BusinessException ex = assertThrows(BusinessException.class, () -> service.batchQuery("E999", "B1"));
+        BusinessException ex = assertThrows(BusinessException.class, () -> service.batchQuery("B1"));
 
         assertEquals(404, ex.getCode());
         @SuppressWarnings({ "unchecked", "rawtypes" })
         ArgumentCaptor<LambdaQueryWrapper<BatchOrderEntity>> cap = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
         verify(batchOrderMapper).selectOne(cap.capture());
-        assertTrue(cap.getValue().getSqlSegment().contains("enterprise_id"),
-                "batchQuery 查询必须带 enterpriseId 条件: " + cap.getValue().getSqlSegment());
+        assertTrue(cap.getValue().getSqlSegment().contains("out_batch_no"),
+                "batchQuery 查询必须按 out_batch_no: " + cap.getValue().getSqlSegment());
     }
 
     @Test
-    void batchClose_otherEnterpriseBatch_notFound() throws AlipayApiException {
+    void batchClose_batchNotFound_throws404() throws AlipayApiException {
         when(batchOrderMapper.selectOne(any())).thenReturn(null);
 
-        BusinessException ex = assertThrows(BusinessException.class, () -> service.batchClose("E999", "B1"));
+        BusinessException ex = assertThrows(BusinessException.class, () -> service.batchClose("B1"));
 
         assertEquals(404, ex.getCode());
         verify(alipayClient, never()).certificateExecute(any());
         @SuppressWarnings({ "unchecked", "rawtypes" })
         ArgumentCaptor<LambdaQueryWrapper<BatchOrderEntity>> cap = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
         verify(batchOrderMapper).selectOne(cap.capture());
-        assertTrue(cap.getValue().getSqlSegment().contains("enterprise_id"),
-                "batchClose 查询必须带 enterpriseId 条件: " + cap.getValue().getSqlSegment());
+        assertTrue(cap.getValue().getSqlSegment().contains("out_batch_no"),
+                "batchClose 查询必须按 out_batch_no: " + cap.getValue().getSqlSegment());
     }
 
     // ==================== I1: 定时同步兜底(getPendingBatches) ====================
@@ -461,7 +458,7 @@ class AlipayBatchPayServiceTest {
         // 两个批次含相同 out_biz_no(out_biz_no 仅批内唯一)→ 不得抛 TooManyResultsException,且只回写本批次明细
         BatchOrderEntity order = new BatchOrderEntity();
         order.setId(1L);
-        order.setEnterpriseId("E100");
+        order.setServiceProviderId(1L);
         order.setOutBatchNo("B1");
         order.setBatchTransId("BT1");
         order.setStatus("INIT");
@@ -485,7 +482,7 @@ class AlipayBatchPayServiceTest {
         own.setStatus("INIT");
         when(batchDetailMapper.selectList(any())).thenReturn(List.of(own));
 
-        Map<String, Object> result = service.batchQuery("E100", "B1");
+        Map<String, Object> result = service.batchQuery("B1");
 
         assertEquals("SUCCESS", result.get("status"));
         assertEquals("SUCCESS", own.getStatus());
@@ -549,7 +546,7 @@ class AlipayBatchPayServiceTest {
     void batchList_pureDateRange_parsesToDayBoundaries() {
         when(batchOrderMapper.selectPage(any(), any())).thenReturn(new Page<>());
 
-        PageResult<BatchOrderEntity> result = service.batchList("E100", null, "2026-08-01", "2026-08-02", 1, 20);
+        PageResult<BatchOrderEntity> result = service.batchList(null, null, "2026-08-01", "2026-08-02", 1, 20);
 
         assertNotNull(result);
         verify(batchOrderMapper).selectPage(any(), any());
@@ -559,7 +556,7 @@ class AlipayBatchPayServiceTest {
     void batchExport_pureDateRange_parses() {
         when(batchOrderMapper.selectList(any())).thenReturn(List.of());
 
-        byte[] bytes = service.batchExport("E100", null, "2026-08-01", "2026-08-02");
+        byte[] bytes = service.batchExport(null, null, "2026-08-01", "2026-08-02");
 
         assertNotNull(bytes);
         assertTrue(bytes.length > 0);
@@ -570,7 +567,7 @@ class AlipayBatchPayServiceTest {
     @Test
     void renderPay_initializeCode_persistsAndReturns() throws AlipayApiException {
         BatchOrderEntity order = new BatchOrderEntity();
-        order.setEnterpriseId("E100");
+        order.setServiceProviderId(1L);
         order.setOutBatchNo("B1");
         order.setBatchTransId("BT1");
         order.setStatus("INIT");
@@ -582,25 +579,28 @@ class AlipayBatchPayServiceTest {
         resp.setInitializeCode("https://p.tb.cn/_2PG4jfHMVvg9vqBUdtx1HZ");
         when(alipayClient.certificateExecute(any(AlipayFundTransRenderPayRequest.class))).thenReturn(resp);
 
-        Map<String, String> result = service.renderPay("E100", "B1");
+        Map<String, String> result = service.renderPay("B1");
 
         assertEquals("https://p.tb.cn/_2PG4jfHMVvg9vqBUdtx1HZ", result.get("pay_url"));
         verify(batchOrderMapper).updateById(order);
     }
 
     @Test
-    void batchCreate_alwaysUsesEnterpriseAsPayer() throws AlipayApiException {
-        // Ruling 19/22: 付款方恒为企业自身 UID(enterprise_id),DTO 已移除 payer_uid 不接受客户端指定
+    void batchCreate_alwaysUsesAuthorizedSubjectAsPayer() throws AlipayApiException {
+        // 账号级: 付款方恒为 DTO 选定的授权主体(participant_id),协议号由授权记录自动带出,不接受客户端指定
         AlipayFundBatchCreateResponse resp = new AlipayFundBatchCreateResponse();
         resp.setOutBatchNo("B1");
         when(alipayClient.certificateExecute(any(AlipayFundBatchCreateRequest.class))).thenReturn(resp);
         BatchAuthorizeEntity authed = new BatchAuthorizeEntity();
+        authed.setParticipantId("2088111122223333");
+        authed.setServiceProviderId(1L);
         authed.setStatus("AUTHED");
+        authed.setServiceProviderId(1L);
         authed.setAgreementNo("AGMT001");
         when(batchAuthorizeMapper.selectOne(any())).thenReturn(authed);
 
         BatchCreateDTO dto = new BatchCreateDTO();
-        dto.setEnterpriseId("E100");
+        dto.setParticipantId("2088111122223333");
         dto.setOutBatchNo("B1");
         dto.setOrderTitle("t");
         BatchCreateDTO.BatchDetailDTO detail = new BatchCreateDTO.BatchDetailDTO();
@@ -617,11 +617,13 @@ class AlipayBatchPayServiceTest {
         ArgumentCaptor<AlipayFundBatchCreateRequest> cap = ArgumentCaptor.forClass(AlipayFundBatchCreateRequest.class);
         verify(alipayClient).certificateExecute(cap.capture());
         AlipayFundBatchCreateModel m = (AlipayFundBatchCreateModel) cap.getValue().getBizModel();
-        assertEquals("E100", m.getPayerInfo().getIdentity());
-        // DB 回写 payer_uid 恒为企业自身 UID
+        assertEquals("2088111122223333", m.getPayerInfo().getIdentity());
+        assertEquals("ALIPAY_USER_ID", m.getPayerInfo().getIdentityType());
+        // DB 回写 payer_uid 恒为授权主体 UID + 冗余服务商(client 解析依据)
         ArgumentCaptor<BatchOrderEntity> order = ArgumentCaptor.forClass(BatchOrderEntity.class);
         verify(batchOrderMapper).insert(order.capture());
-        assertEquals("E100", order.getValue().getPayerUid());
+        assertEquals("2088111122223333", order.getValue().getPayerUid());
+        assertEquals(1L, order.getValue().getServiceProviderId());
     }
 
     @Test
@@ -630,7 +632,7 @@ class AlipayBatchPayServiceTest {
         when(batchAuthorizeMapper.selectOne(any())).thenReturn(null);
 
         BatchCreateDTO dto = new BatchCreateDTO();
-        dto.setEnterpriseId("E100");
+        dto.setParticipantId("2088111122223333");
         dto.setOutBatchNo("B1");
         dto.setOrderTitle("t");
         BatchCreateDTO.BatchDetailDTO detail = new BatchCreateDTO.BatchDetailDTO();
@@ -658,11 +660,12 @@ class AlipayBatchPayServiceTest {
         when(alipayClient.certificateExecute(any(AlipayFundBatchCreateRequest.class))).thenReturn(resp);
         BatchAuthorizeEntity authed = new BatchAuthorizeEntity();
         authed.setStatus("NORMAL");
+        authed.setServiceProviderId(1L);
         authed.setAgreementNo("AGMT001");
         when(batchAuthorizeMapper.selectOne(any())).thenReturn(authed);
 
         BatchCreateDTO dto = new BatchCreateDTO();
-        dto.setEnterpriseId("E100");
+        dto.setParticipantId("2088111122223333");
         dto.setOutBatchNo("B1");
         dto.setOrderTitle("t");
         BatchCreateDTO.BatchDetailDTO detail = new BatchCreateDTO.BatchDetailDTO();
@@ -679,55 +682,17 @@ class AlipayBatchPayServiceTest {
         verify(alipayClient).certificateExecute(any(AlipayFundBatchCreateRequest.class));
     }
 
-    @Test
-    void batchCreate_authorizedIdentity_proceedsWithIdentityAsPayer() throws AlipayApiException {
-        // 反馈轮 4/5 协同: identity 有值时预检与付款方均用 identity(而非 enterprise_id)
-        EnterpriseEntity ent = new EnterpriseEntity();
-        ent.setEnterpriseId("E100");
-        ent.setIdentity("2088IDENTITY");
-        ent.setIdentityType("ALIPAY_OPEN_ID");
-        when(enterpriseMapper.selectByEnterpriseIdIgnoreTenant("E100")).thenReturn(ent);
-        AlipayFundBatchCreateResponse resp = new AlipayFundBatchCreateResponse();
-        resp.setOutBatchNo("B1");
-        when(alipayClient.certificateExecute(any(AlipayFundBatchCreateRequest.class))).thenReturn(resp);
-        BatchAuthorizeEntity authed = new BatchAuthorizeEntity();
-        authed.setStatus("AUTHED");
-        authed.setAgreementNo("AGMT001");
-        when(batchAuthorizeMapper.selectOne(any())).thenReturn(authed);
-
-        BatchCreateDTO dto = new BatchCreateDTO();
-        dto.setEnterpriseId("E100");
-        dto.setOutBatchNo("B1");
-        dto.setOrderTitle("t");
-        BatchCreateDTO.BatchDetailDTO detail = new BatchCreateDTO.BatchDetailDTO();
-        detail.setOutBizNo("D1");
-        detail.setAmount(new BigDecimal("10"));
-        detail.setPayeeIdentity("a@b.com");
-        detail.setPayeeIdentityType("ALIPAY_LOGON_ID");
-        detail.setPayeeName("张三");
-        dto.setDetails(List.of(detail));
-
-        Map<String, Object> result = service.batchCreate(dto);
-
-        assertEquals("B1", result.get("out_batch_no"));
-        ArgumentCaptor<AlipayFundBatchCreateRequest> cap = ArgumentCaptor.forClass(AlipayFundBatchCreateRequest.class);
-        verify(alipayClient).certificateExecute(cap.capture());
-        AlipayFundBatchCreateModel m = (AlipayFundBatchCreateModel) cap.getValue().getBizModel();
-        assertEquals("2088IDENTITY", m.getPayerInfo().getIdentity());
-        assertEquals("ALIPAY_OPEN_ID", m.getPayerInfo().getIdentityType());
-    }
-
     // ==================== M3: batchClose INIT/WAIT_PAY 守卫 ====================
 
     @Test
     void batchClose_nonInitStatus_throwsBusinessException() throws AlipayApiException {
         BatchOrderEntity order = new BatchOrderEntity();
-        order.setEnterpriseId("E100");
+        order.setServiceProviderId(1L);
         order.setOutBatchNo("B1");
         order.setStatus("SUCCESS");
         when(batchOrderMapper.selectOne(any())).thenReturn(order);
 
-        BusinessException ex = assertThrows(BusinessException.class, () -> service.batchClose("E100", "B1"));
+        BusinessException ex = assertThrows(BusinessException.class, () -> service.batchClose("B1"));
 
         assertEquals(400, ex.getCode());
         assertTrue(ex.getMessage().contains("可关闭"), ex.getMessage());
@@ -738,7 +703,7 @@ class AlipayBatchPayServiceTest {
     void batchClose_waitPayStatus_callsCloseApi() throws AlipayApiException {
         // WAIT_PAY=等待支付(render 过链接未支付)应可关闭(用户实测: 本地被同步成 WAIT_PAY 后关闭按钮消失)
         BatchOrderEntity order = new BatchOrderEntity();
-        order.setEnterpriseId("E100");
+        order.setServiceProviderId(1L);
         order.setOutBatchNo("B1");
         order.setBatchTransId("BT1");
         order.setStatus("WAIT_PAY");
@@ -746,7 +711,7 @@ class AlipayBatchPayServiceTest {
         when(alipayClient.certificateExecute(any(AlipayFundBatchCloseRequest.class)))
                 .thenReturn(new AlipayFundBatchCloseResponse());
 
-        Map<String, String> result = service.batchClose("E100", "B1");
+        Map<String, String> result = service.batchClose("B1");
 
         assertEquals("DISUSE", result.get("status"));
         verify(alipayClient).certificateExecute(any(AlipayFundBatchCloseRequest.class));
@@ -757,7 +722,7 @@ class AlipayBatchPayServiceTest {
         // 关闭失败(支付宝侧批次已不可关闭,用户实测 BATCH_ORDER_STATUS_INVALID)→
         // detail.query 回写真实状态到本地,避免残留 INIT 让用户反复点关闭
         BatchOrderEntity order = new BatchOrderEntity();
-        order.setEnterpriseId("E100");
+        order.setServiceProviderId(1L);
         order.setOutBatchNo("B1");
         order.setBatchTransId("BT1");
         order.setStatus("INIT");
@@ -772,7 +737,7 @@ class AlipayBatchPayServiceTest {
         queryResp.setBatchStatus("INVALID");
         when(alipayClient.certificateExecute(any(AlipayFundBatchDetailQueryRequest.class))).thenReturn(queryResp);
 
-        BusinessException ex = assertThrows(BusinessException.class, () -> service.batchClose("E100", "B1"));
+        BusinessException ex = assertThrows(BusinessException.class, () -> service.batchClose("B1"));
 
         assertEquals(400, ex.getCode());
         assertTrue(ex.getMessage().contains("BATCH_ORDER_STATUS_INVALID"), ex.getMessage());
@@ -785,7 +750,7 @@ class AlipayBatchPayServiceTest {
     void renderPay_waitPayStatus_regeneratesPayUrl() throws AlipayApiException {
         // WAIT_PAY=等待支付(render 过链接未支付)应可重新生成支付链接(用户实测: 不能二次发起支付)
         BatchOrderEntity order = new BatchOrderEntity();
-        order.setEnterpriseId("E100");
+        order.setServiceProviderId(1L);
         order.setOutBatchNo("B1");
         order.setBatchTransId("BT1");
         order.setStatus("WAIT_PAY");
@@ -795,43 +760,12 @@ class AlipayBatchPayServiceTest {
         resp.setInitializeCode("https://p.tb.cn/_2PG4jfHMVvg9vqBUdtx1HZ");
         when(alipayClient.certificateExecute(any(AlipayFundTransRenderPayRequest.class))).thenReturn(resp);
 
-        Map<String, String> result = service.renderPay("E100", "B1");
+        Map<String, String> result = service.renderPay("B1");
 
         assertEquals("https://p.tb.cn/_2PG4jfHMVvg9vqBUdtx1HZ", result.get("pay_url"));
         verify(batchOrderMapper).updateById(order);
     }
 
-    // ==================== 二轮复核修复: 空 enterpriseId 拒绝(条件式 eq 的空值漏洞) ====================
-
-    @Test
-    void renderPay_blankEnterpriseId_throws() {
-        // enterprise_id 为空必须拒绝而非静默跳过租户过滤(防御 /pay body 零校验路径)
-        BusinessException ex = assertThrows(BusinessException.class, () -> service.renderPay("", "B1"));
-        assertEquals(400, ex.getCode());
-        verify(batchOrderMapper, never()).selectOne(any());
-    }
-
-    @Test
-    void batchQuery_nullEnterpriseId_throws() {
-        BusinessException ex = assertThrows(BusinessException.class, () -> service.batchQuery(null, "B1"));
-        assertEquals(400, ex.getCode());
-        verify(batchOrderMapper, never()).selectOne(any());
-    }
-
-    @Test
-    void batchClose_blankEnterpriseId_throws() {
-        BusinessException ex = assertThrows(BusinessException.class, () -> service.batchClose(" ", "B1"));
-        assertEquals(400, ex.getCode());
-        verify(batchOrderMapper, never()).selectOne(any());
-    }
-
-    @Test
-    void batchDetail_nullEnterpriseId_throws() {
-        BusinessException ex = assertThrows(BusinessException.class, () -> service.batchDetail(null, "B1", 1, 20));
-        assertEquals(400, ex.getCode());
-        verify(batchOrderMapper, never()).selectOne(any());
-    }
-
     // ==================== 二轮复核修复: parseTimeFilter 边界精确断言 ====================
 
     @Test