Sfoglia il codice sorgente

fix: 付款方UID彻底不接收客户端参数 - 删除前端输入框与接口参数,恒以企业自身UID为付款方

用户反馈: 付款方 UID 既已自动带出,输入框没必要存在;接口也不应接受该参数以免被破解篡改指向他人账号。

- 前端: BatchPayAuthorize/BatchPayCreate 删除付款方支付宝UID表单项;batch.ts 移除 participant_id/payer_uid 请求参数(VO 响应字段保留)
- 后端: authorizeApply 签名移除 participantId(恒以 enterprise_id 为付款方);BatchCreateDTO 移除 payerUid 字段(payer_info 恒用 enterprise_id)
- 测试: 用例更新为验证恒用企业自身 UID(48 全绿)
alphaH 5 giorni fa
parent
commit
fbf2202e97

+ 4 - 5
frontend/src/api/module_payment/batch.ts

@@ -62,11 +62,10 @@ export interface BatchDetailItem {
   error_msg?: string;
 }
 
-/** 创建批次请求体(BatchCreateDTO,snake_case) */
+/** 创建批次请求体(BatchCreateDTO,snake_case;付款方恒为企业自身 UID,后端以 enterprise_id 为准不接受指定) */
 export interface BatchCreateParams {
   enterprise_id?: string;
   order_title: string;
-  payer_uid: string;
   agreement_no?: string;
   transfer_scene_name?: string;
   transfer_scene_report_infos?: Array<{ info_type: string; info_content: string }>;
@@ -76,12 +75,12 @@ export interface BatchCreateParams {
 }
 
 export const BatchPayAPI = {
-  /** 生成制单授权链接(PC 渠道,付款方 UID 必须 2088 开头) */
-  authorizeApply(enterpriseId: string, participantId: string) {
+  /** 生成制单授权链接(PC 渠道,付款方恒为企业自身 UID,后端不接受指定) */
+  authorizeApply(enterpriseId: string) {
     return request<ApiResponse<{ authorize_link: string; out_biz_no: string; status: string }>>({
       url: `${API_PATH}/authorize/apply`,
       method: "post",
-      data: { enterprise_id: enterpriseId, participant_id: participantId },
+      data: { enterprise_id: enterpriseId },
     });
   },
 

+ 1 - 10
frontend/src/views/module_payment/account/components/BatchPayAuthorize.vue

@@ -6,15 +6,6 @@
       </div>
     </template>
     <el-form label-width="160px">
-      <el-form-item label="付款方支付宝UID">
-        <el-input
-          :model-value="enterpriseId ?? ''"
-          readonly
-          style="max-width: 420px"
-          placeholder="2088 开头"
-        />
-        <div class="form-item-tip">企业自己的支付宝 UID,系统自动带出</div>
-      </el-form-item>
       <el-form-item>
         <el-button
           v-if="!isAuthed"
@@ -142,7 +133,7 @@ async function handleApply() {
   try {
     const res = existingAuthing.value
       ? await BatchPayAPI.authorizeRebind(enterpriseId.value)
-      : await BatchPayAPI.authorizeApply(enterpriseId.value, enterpriseId.value);
+      : await BatchPayAPI.authorizeApply(enterpriseId.value);
     link.value = res.data.data.authorize_link;
     outBizNo.value = res.data.data.out_biz_no;
     status.value = res.data.data.status;

+ 0 - 10
frontend/src/views/module_payment/account/components/BatchPayCreate.vue

@@ -13,15 +13,6 @@
           style="max-width: 420px"
         />
       </el-form-item>
-      <el-form-item label="付款方支付宝UID">
-        <el-input
-          :model-value="enterpriseId ?? ''"
-          readonly
-          style="max-width: 420px"
-          placeholder="2088 开头"
-        />
-        <div class="form-item-tip">企业自己的支付宝 UID,系统自动带出</div>
-      </el-form-item>
       <el-form-item label="协议号">
         <el-input
           v-model="form.agreement_no"
@@ -441,7 +432,6 @@ async function handleSubmit() {
     const res = await BatchPayAPI.batchCreate({
       enterprise_id: enterpriseId.value,
       order_title: form.order_title,
-      payer_uid: enterpriseId.value,
       agreement_no: form.agreement_no || undefined,
       transfer_scene_name: form.transfer_scene_name || undefined,
       transfer_scene_report_infos: sceneReportInfos.length ? sceneReportInfos : undefined,

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

@@ -25,8 +25,8 @@ public class BatchPayController {
     @PreAuthorize("@perm.hasAny('module_payment:account:authorize')")
     @PostMapping("/authorize/apply")
     public Result<Map<String, String>> authorizeApply(@RequestBody Map<String, Object> b) {
-        return Result.ok(batchPayService.authorizeApply(
-                (String) b.get("enterprise_id"), (String) b.get("participant_id")));
+        // 付款方恒为企业自身 UID(enterprise_id),不接受客户端指定 participant_id
+        return Result.ok(batchPayService.authorizeApply((String) b.get("enterprise_id")));
     }
 
     @PreAuthorize("@perm.hasAny('module_payment:account:authorize')")

+ 0 - 4
java/src/main/java/com/payment/platform/module/payment/batch/dto/BatchCreateDTO.java

@@ -27,10 +27,6 @@ public class BatchCreateDTO {
     @Schema(description = "批次标题(展示在付款方账单)")
     private String orderTitle;
 
-    @NotBlank(message = "付款方支付宝UID不能为空")
-    @Schema(description = "付款方支付宝 UID")
-    private String payerUid;
-
     @Schema(description = "制单授权协议号(payer_info.ext_info 传此值则校验指定协议;不传校验任意协议)")
     private String agreementNo;
 

+ 10 - 9
java/src/main/java/com/payment/platform/module/payment/batch/service/AlipayBatchPayService.java

@@ -83,14 +83,16 @@ public class AlipayBatchPayService {
     private final BatchOrderMapper batchOrderMapper;
     private final BatchDetailMapper batchDetailMapper;
 
-    /** alipay.fund.authorize.uni.apply — 生成制单授权短链接(PC 渠道) */
+    /**
+     * alipay.fund.authorize.uni.apply — 生成制单授权短链接(PC 渠道)
+     * <p>
+     * Ruling 19/21: 付款方即企业自己的支付宝 UID(pay_enterprise.enterprise_id),
+     * 不接收客户端传入的 participant_id(防篡改指向他人账号),恒以 enterprise_id 为付款方。
+     */
     @Transactional
-    public Map<String, String> authorizeApply(String enterpriseId, String participantId) {
+    public Map<String, String> authorizeApply(String enterpriseId) {
         requireEnterpriseId(enterpriseId);
-        // Ruling 19/21: 付款方即企业自己的支付宝 UID(pay_enterprise.enterprise_id),
-        // participant_id 为空时默认企业 ID(前端已只读带出,此处防绕过直调)
-        String payerUid = (participantId == null || participantId.isBlank()) ? enterpriseId : participantId;
-        return doAuthorizeApply(enterpriseId, payerUid);
+        return doAuthorizeApply(enterpriseId, enterpriseId);
     }
 
     /**
@@ -296,9 +298,8 @@ 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/21: 付款方即企业自己的 UID,为空时默认 enterprise_id)
-            String payerUid = (dto.getPayerUid() == null || dto.getPayerUid().isBlank())
-                    ? dto.getEnterpriseId() : dto.getPayerUid();
+            // 付款方 + 制单授权协议(Ruling 19/21: 付款方即企业自己的 UID,不接受客户端传入,恒用 enterprise_id)
+            String payerUid = dto.getEnterpriseId();
             Participant payer = new Participant();
             payer.setIdentity(payerUid);
             payer.setIdentityType("ALIPAY_USER_ID");

+ 18 - 26
java/src/test/java/com/payment/platform/module/payment/batch/service/AlipayBatchPayServiceTest.java

@@ -83,7 +83,7 @@ class AlipayBatchPayServiceTest {
         resp.setOutBizNo("A1");
         when(alipayClient.certificateExecute(any(AlipayFundAuthorizeUniApplyRequest.class))).thenReturn(resp);
 
-        Map<String, String> result = service.authorizeApply("E100", "2088123412341234");
+        Map<String, String> result = service.authorizeApply("E100");
 
         assertEquals("https://ur.alipay.com/abc", result.get("authorize_link"));
         assertEquals("AUTHING", result.get("status"));
@@ -95,7 +95,8 @@ class AlipayBatchPayServiceTest {
         assertEquals("STANDARD_CREATE_FUND_ORDER", m.getBizScene());
         assertEquals("SHORT_URL", m.getAuthorizeLinkType());
         assertEquals("pc", m.getChannel());
-        assertEquals("2088123412341234", m.getPrincipalInfo().getParticipantId());
+        // Ruling 19/22: 付款方恒为企业自身 UID(enterprise_id),不接受客户端指定
+        assertEquals("E100", m.getPrincipalInfo().getParticipantId());
 
         ArgumentCaptor<BatchAuthorizeEntity> ent = ArgumentCaptor.forClass(BatchAuthorizeEntity.class);
         verify(batchAuthorizeMapper).insert(ent.capture());
@@ -108,7 +109,7 @@ class AlipayBatchPayServiceTest {
         when(alipayClient.certificateExecute(any(AlipayFundAuthorizeUniApplyRequest.class)))
                 .thenThrow(new AlipayApiException("network error"));
 
-        assertThrows(BusinessException.class, () -> service.authorizeApply("E100", "2088123412341234"));
+        assertThrows(BusinessException.class, () -> service.authorizeApply("E100"));
     }
 
     @Test
@@ -132,7 +133,7 @@ class AlipayBatchPayServiceTest {
         existing.setStatus("AUTHED");
         when(batchAuthorizeMapper.selectOne(any())).thenReturn(existing);
 
-        assertThrows(BusinessException.class, () -> service.authorizeApply("E100", "2088123412341234"));
+        assertThrows(BusinessException.class, () -> service.authorizeApply("E100"));
 
         verify(alipayClient, never()).certificateExecute(any(AlipayFundAuthorizeUniApplyRequest.class));
         verify(batchAuthorizeMapper, never()).insert(any());
@@ -143,7 +144,7 @@ class AlipayBatchPayServiceTest {
         AlipayFundAuthorizeUniApplyResponse resp = new AlipayFundAuthorizeUniApplyResponse();
         when(alipayClient.certificateExecute(any(AlipayFundAuthorizeUniApplyRequest.class))).thenReturn(resp);
 
-        Map<String, String> result = service.authorizeApply("E100", "2088123412341234");
+        Map<String, String> result = service.authorizeApply("E100");
 
         assertEquals("", result.get("authorize_link"));
         assertEquals("AUTHING", result.get("status"));
@@ -177,7 +178,6 @@ class AlipayBatchPayServiceTest {
         dto.setEnterpriseId("E100");
         dto.setOutBatchNo("B1");
         dto.setOrderTitle("202608报销");
-        dto.setPayerUid("2088PAYER");
         dto.setAgreementNo("AGMT001");
         dto.setTransferSceneName("佣金报酬");
         dto.setTransferSceneReportInfos(List.of(Map.of("info_type", "佣金报酬说明", "info_content", "8月家政服务报酬")));
@@ -209,7 +209,6 @@ class AlipayBatchPayServiceTest {
     void batchCreate_detailAmountBelow1_throws() {
         BatchCreateDTO dto = new BatchCreateDTO();
         dto.setOrderTitle("t");
-        dto.setPayerUid("2088PAYER");
         BatchCreateDTO.BatchDetailDTO detail = new BatchCreateDTO.BatchDetailDTO();
         detail.setOutBizNo("D1");
         detail.setAmount(new BigDecimal("0.5"));
@@ -230,7 +229,6 @@ class AlipayBatchPayServiceTest {
         when(batchOrderMapper.selectOne(any())).thenReturn(existing);
         BatchCreateDTO dto = new BatchCreateDTO();
         dto.setOrderTitle("t");
-        dto.setPayerUid("2088PAYER");
         dto.setOutBatchNo("B1");
         BatchCreateDTO.BatchDetailDTO detail = new BatchCreateDTO.BatchDetailDTO();
         detail.setOutBizNo("D1");
@@ -262,7 +260,6 @@ class AlipayBatchPayServiceTest {
         dto.setEnterpriseId("E100");
         dto.setOutBatchNo("B1");
         dto.setOrderTitle("t");
-        dto.setPayerUid("2088PAYER");
         BatchCreateDTO.BatchDetailDTO detail = new BatchCreateDTO.BatchDetailDTO();
         detail.setOutBizNo("D1");
         detail.setAmount(new BigDecimal("10"));
@@ -286,7 +283,6 @@ class AlipayBatchPayServiceTest {
         dto.setEnterpriseId("E100");
         dto.setOutBatchNo("B1");
         dto.setOrderTitle("t");
-        dto.setPayerUid("2088PAYER");
         BatchCreateDTO.BatchDetailDTO d1 = new BatchCreateDTO.BatchDetailDTO();
         d1.setOutBizNo("D1");
         d1.setAmount(new BigDecimal("10"));
@@ -412,7 +408,6 @@ class AlipayBatchPayServiceTest {
         dto.setEnterpriseId("E100");
         dto.setOutBatchNo("B1");
         dto.setOrderTitle("202608报销");
-        dto.setPayerUid("2088PAYER");
         BatchCreateDTO.BatchDetailDTO detail = new BatchCreateDTO.BatchDetailDTO();
         detail.setOutBizNo("D1");
         detail.setAmount(new BigDecimal("20.11"));
@@ -549,7 +544,6 @@ class AlipayBatchPayServiceTest {
         Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
         BatchCreateDTO dto = new BatchCreateDTO();
         dto.setOrderTitle("t");
-        dto.setPayerUid("2088PAYER");
         dto.setTransferSceneName("");
         dto.setTransferSceneReportInfos(List.of(Map.of("info_type", "佣金报酬说明", "info_content", "8月报酬")));
         dto.setDetails(List.of(detail("D1", "10")));
@@ -565,7 +559,6 @@ class AlipayBatchPayServiceTest {
         Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
         BatchCreateDTO dto = new BatchCreateDTO();
         dto.setOrderTitle("t");
-        dto.setPayerUid("2088PAYER");
         dto.setTransferSceneName("佣金报酬");
         dto.setTransferSceneReportInfos(List.of());
         dto.setDetails(List.of(detail("D1", "10")));
@@ -642,7 +635,7 @@ class AlipayBatchPayServiceTest {
         doThrow(new DuplicateKeyException("duplicate key")).when(batchAuthorizeMapper).insert(any());
 
         BusinessException ex = assertThrows(BusinessException.class,
-                () -> service.authorizeApply("E100", "2088123412341234"));
+                () -> service.authorizeApply("E100"));
 
         assertEquals(400, ex.getCode());
         assertTrue(ex.getMessage().contains("请勿重复操作"), ex.getMessage());
@@ -660,7 +653,7 @@ class AlipayBatchPayServiceTest {
         when(batchAuthorizeMapper.selectOne(any())).thenReturn(existing);
 
         BusinessException ex = assertThrows(BusinessException.class,
-                () -> service.authorizeApply("E100", "2088123412341234"));
+                () -> service.authorizeApply("E100"));
 
         assertEquals(400, ex.getCode());
         assertTrue(ex.getMessage().contains("未完成"), ex.getMessage());
@@ -678,7 +671,7 @@ class AlipayBatchPayServiceTest {
         existing.setCreatedTime(OffsetDateTime.now());
         when(batchAuthorizeMapper.selectOne(any())).thenReturn(existing);
 
-        assertThrows(BusinessException.class, () -> service.authorizeApply("E100", "2088123412341234"));
+        assertThrows(BusinessException.class, () -> service.authorizeApply("E100"));
     }
 
     @Test
@@ -696,7 +689,7 @@ class AlipayBatchPayServiceTest {
         resp.setAuthorizeLink("https://ur.alipay.com/abc");
         when(alipayClient.certificateExecute(any(AlipayFundAuthorizeUniApplyRequest.class))).thenReturn(resp);
 
-        Map<String, String> result = service.authorizeApply("E100", "2088123412341234");
+        Map<String, String> result = service.authorizeApply("E100");
 
         assertEquals("AUTHING", result.get("status"));
         // 旧记录作废置 UNBIND(与 uk_batch_authorize_active 的 WHERE status <> 'UNBIND' 协同)
@@ -730,7 +723,7 @@ class AlipayBatchPayServiceTest {
         resp.setAuthorizeLink("https://ur.alipay.com/abc");
         when(alipayClient.certificateExecute(any(AlipayFundAuthorizeUniApplyRequest.class))).thenReturn(resp);
 
-        Map<String, String> result = service.authorizeApply("E100", "2088123412341234");
+        Map<String, String> result = service.authorizeApply("E100");
 
         assertNotNull(result.get("authorize_link"));
         verify(batchAuthorizeMapper).updateById(argThat(e -> "UNBIND".equals(e.getStatus())));
@@ -740,13 +733,13 @@ class AlipayBatchPayServiceTest {
     // ==================== 用户反馈轮 2: 付款方 UID 自动带出 + 授权可重新生成 ====================
 
     @Test
-    void authorizeApply_blankParticipantId_defaultsToEnterpriseId() throws AlipayApiException {
-        // Ruling 19/21: 付款方即企业自己,participant_id 为空时默认企业 enterprise_id(防绕过前端直调
+    void authorizeApply_alwaysUsesEnterpriseAsPayer() throws AlipayApiException {
+        // Ruling 19/22: 付款方恒为企业自身 UID(enterprise_id),方法不接受客户端传入的 participant_id(防篡改指向他人账号
         AlipayFundAuthorizeUniApplyResponse resp = new AlipayFundAuthorizeUniApplyResponse();
         resp.setAuthorizeLink("https://ur.alipay.com/abc");
         when(alipayClient.certificateExecute(any(AlipayFundAuthorizeUniApplyRequest.class))).thenReturn(resp);
 
-        Map<String, String> result = service.authorizeApply("E100", "  ");
+        Map<String, String> result = service.authorizeApply("E100");
 
         assertEquals("AUTHING", result.get("status"));
         ArgumentCaptor<AlipayFundAuthorizeUniApplyRequest> cap = ArgumentCaptor.forClass(AlipayFundAuthorizeUniApplyRequest.class);
@@ -761,7 +754,7 @@ class AlipayBatchPayServiceTest {
     @Test
     void authorizeApply_nullEnterpriseId_throws() {
         // requireEnterpriseId 防御: 企业都不存在时直接拒绝而非默认空串申请
-        assertThrows(BusinessException.class, () -> service.authorizeApply(null, null));
+        assertThrows(BusinessException.class, () -> service.authorizeApply(null));
     }
 
     @Test
@@ -833,8 +826,8 @@ class AlipayBatchPayServiceTest {
     }
 
     @Test
-    void batchCreate_blankPayerUid_defaultsToEnterpriseId() throws AlipayApiException {
-        // Ruling 21: payer_uid 为空时默认企业 enterprise_id(付款方即企业自己)
+    void batchCreate_alwaysUsesEnterpriseAsPayer() throws AlipayApiException {
+        // Ruling 19/22: 付款方恒为企业自身 UID(enterprise_id),DTO 已移除 payer_uid 不接受客户端指定
         AlipayFundBatchCreateResponse resp = new AlipayFundBatchCreateResponse();
         resp.setOutBatchNo("B1");
         when(alipayClient.certificateExecute(any(AlipayFundBatchCreateRequest.class))).thenReturn(resp);
@@ -843,7 +836,6 @@ class AlipayBatchPayServiceTest {
         dto.setEnterpriseId("E100");
         dto.setOutBatchNo("B1");
         dto.setOrderTitle("t");
-        dto.setPayerUid(" ");
         BatchCreateDTO.BatchDetailDTO detail = new BatchCreateDTO.BatchDetailDTO();
         detail.setOutBizNo("D1");
         detail.setAmount(new BigDecimal("10"));
@@ -859,7 +851,7 @@ class AlipayBatchPayServiceTest {
         verify(alipayClient).certificateExecute(cap.capture());
         AlipayFundBatchCreateModel m = (AlipayFundBatchCreateModel) cap.getValue().getBizModel();
         assertEquals("E100", m.getPayerInfo().getIdentity());
-        // DB 回写 payer_uid 同样默认企业 ID
+        // DB 回写 payer_uid 恒为企业自身 UID
         ArgumentCaptor<BatchOrderEntity> order = ArgumentCaptor.forClass(BatchOrderEntity.class);
         verify(batchOrderMapper).insert(order.capture());
         assertEquals("E100", order.getValue().getPayerUid());