Ver código fonte

fix: 批量付款 - 授权查询回写条件与重复授权预检

alphaH 6 dias atrás
pai
commit
1365572b2a

+ 16 - 2
java/src/main/java/com/payment/platform/module/payment/batch/service/AlipayBatchPayService.java

@@ -60,6 +60,14 @@ public class AlipayBatchPayService {
     public Map<String, String> authorizeApply(String enterpriseId, String participantId) {
         if (participantId == null || participantId.isBlank())
             throw new BusinessException(400, "付款方支付宝账号不能为空");
+        // 重复新增防护: 同付款方已有非 UNBIND 状态的授权记录时不允许重复申请(设计文档 2.4)
+        BatchAuthorizeEntity existing = batchAuthorizeMapper.selectOne(
+                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchAuthorizeEntity>()
+                        .eq(BatchAuthorizeEntity::getEnterpriseId, enterpriseId)
+                        .eq(BatchAuthorizeEntity::getParticipantId, participantId)
+                        .ne(BatchAuthorizeEntity::getStatus, "UNBIND"));
+        if (existing != null)
+            throw new BusinessException(400, "该付款方已存在制单授权申请,无需重复授权");
         String outBizNo = SnowflakeIdGenerator.nextIdStr();
         try {
             AlipayFundAuthorizeUniApplyModel model = new AlipayFundAuthorizeUniApplyModel();
@@ -89,7 +97,8 @@ public class AlipayBatchPayService {
             entity.setAuthorizeLink(response.getAuthorizeLink());
             batchAuthorizeMapper.insert(entity);
 
-            return Map.of("authorize_link", response.getAuthorizeLink(),
+            return Map.of("authorize_link",
+                    response.getAuthorizeLink() != null ? response.getAuthorizeLink() : "",
                     "out_biz_no", outBizNo, "status", "AUTHING");
         } catch (AlipayApiException e) {
             throw new BusinessException(400, "生成授权链接失败: " + e.getMessage());
@@ -111,7 +120,9 @@ public class AlipayBatchPayService {
             if (!response.isSuccess())
                 throw new BusinessException(400, "查询授权状态失败: " + response.getMsg());
 
-            if (response.getAgreementNo() != null && !response.getAgreementNo().isBlank()) {
+            // 仅 AUTHED 才回写: UNBIND 也返回协议号, 直接回写会破坏本地状态机(UNBIND 由异步通知回写)
+            if ("AUTHED".equals(response.getStatus())
+                    && response.getAgreementNo() != null && !response.getAgreementNo().isBlank()) {
                 BatchAuthorizeEntity entity = batchAuthorizeMapper.selectOne(
                         new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BatchAuthorizeEntity>()
                                 .eq(BatchAuthorizeEntity::getOutBizNo, outBizNo));
@@ -119,6 +130,9 @@ public class AlipayBatchPayService {
                     entity.setAgreementNo(response.getAgreementNo());
                     entity.setStatus("AUTHED");
                     batchAuthorizeMapper.updateById(entity);
+                } else {
+                    log.warn("制单授权查询成功但本地记录不存在, enterpriseId={}, outBizNo={}, agreementNo={}",
+                            enterpriseId, outBizNo, response.getAgreementNo());
                 }
             }
             return Map.of("agreement_no", response.getAgreementNo() != null ? response.getAgreementNo() : "",

+ 43 - 1
java/src/test/java/com/payment/platform/module/payment/batch/service/AlipayBatchPayServiceTest.java

@@ -36,7 +36,8 @@ class AlipayBatchPayServiceTest {
     @BeforeEach
     void setUp() {
         service = new AlipayBatchPayService(alipayClientFactory, batchAuthorizeMapper);
-        when(alipayClientFactory.getClient("E100", "BATCH_PAY")).thenReturn(alipayClient);
+        // lenient: 重复授权预检测试用例在到达 getClient 前即抛异常,该 stub 不会被使用
+        lenient().when(alipayClientFactory.getClient("E100", "BATCH_PAY")).thenReturn(alipayClient);
     }
 
     @Test
@@ -86,4 +87,45 @@ class AlipayBatchPayServiceTest {
         assertEquals("AGMT001", result.get("agreement_no"));
         assertEquals("AUTHED", result.get("status"));
     }
+
+    @Test
+    void authorizeApply_existingAuthorization_throwsBusinessException() throws AlipayApiException {
+        BatchAuthorizeEntity existing = new BatchAuthorizeEntity();
+        existing.setEnterpriseId("E100");
+        existing.setParticipantId("2088123412341234");
+        existing.setStatus("AUTHED");
+        when(batchAuthorizeMapper.selectOne(any())).thenReturn(existing);
+
+        assertThrows(BusinessException.class, () -> service.authorizeApply("E100", "2088123412341234"));
+
+        verify(alipayClient, never()).certificateExecute(any(AlipayFundAuthorizeUniApplyRequest.class));
+        verify(batchAuthorizeMapper, never()).insert(any());
+    }
+
+    @Test
+    void authorizeApply_nullAuthorizeLink_returnsEmptyString() throws AlipayApiException {
+        AlipayFundAuthorizeUniApplyResponse resp = new AlipayFundAuthorizeUniApplyResponse();
+        when(alipayClient.certificateExecute(any(AlipayFundAuthorizeUniApplyRequest.class))).thenReturn(resp);
+
+        Map<String, String> result = service.authorizeApply("E100", "2088123412341234");
+
+        assertEquals("", result.get("authorize_link"));
+        assertEquals("AUTHING", result.get("status"));
+    }
+
+    @Test
+    void queryAuthorize_unbind_doesNotOverwriteLocalStatus() throws AlipayApiException {
+        AlipayFundAuthorizeUniQueryResponse resp = new AlipayFundAuthorizeUniQueryResponse();
+        resp.setAgreementNo("AGMT001");
+        resp.setStatus("UNBIND");
+        when(alipayClient.certificateExecute(any(AlipayFundAuthorizeUniQueryRequest.class))).thenReturn(resp);
+
+        Map<String, String> result = service.queryAuthorize("E100", "A1");
+
+        assertEquals("AGMT001", result.get("agreement_no"));
+        assertEquals("UNBIND", result.get("status"));
+        // UNBIND 下协议号非空也不得回写本地状态
+        verify(batchAuthorizeMapper, never()).selectOne(any());
+        verify(batchAuthorizeMapper, never()).updateById(any());
+    }
 }