Ver Fonte

feat: 转账回单接口切换为ISV协议模式(alipay.data.bill.ereceiptagent) + 修复轮询undefined

- 后端: alipay.commerce.ec.trans.receipt.apply/query 切换为
  alipay.data.bill.ereceiptagent.apply + alipay.data.bill.accountbookereceipt.query
  (type=FUND_DETAIL, key=pay_fund_order_id, agreement_no); 前端仍传
  enterprise_id+order_no, 后端从转账记录/签约记录映射, 未签约商户明确报错
- 前端: 回单轮询改为仅依赖 order_no (后端 downloadReceiptByOrderNo 推导
  enterprise_id), 避免未选企业时 enterprise_id 为 undefined 拼出
  /receipt/undefined/{file_id}; downloadReceipt 类型签名 enterprise_id 改为可选
alphaH há 2 semanas atrás
pai
commit
cf68dbf411

+ 2 - 1
frontend/src/api/module_payment/account.ts

@@ -241,7 +241,8 @@ export const AccountAPI = {
   },
 
   downloadReceipt(params: {
-    enterprise_id: string;
+    // enterprise_id 可选 — 后端支持仅凭 order_no 查库推导(downloadReceiptByOrderNo)
+    enterprise_id?: string;
     order_no: string;
   }) {
     return request<ApiResponse<ReceiptQueryOutSchema>>({

+ 5 - 6
frontend/src/views/module_payment/account/index.vue

@@ -1904,7 +1904,7 @@ async function handleDownloadReceipt(row: any) {
     } else if (status === "FAIL") {
       ElMessage.error(`回单生成失败: ${error_message || "未知错误"}`);
     } else if (status === "PROCESS" || status === "INIT") {
-      await pollForReceipt(file_id);
+      await pollForReceipt(row.order_no);
     } else {
       ElMessage.info("回单状态未知,请稍后尝试");
     }
@@ -1915,7 +1915,9 @@ async function handleDownloadReceipt(row: any) {
   }
 }
 
-async function pollForReceipt(file_id: string) {
+// 轮询回单状态 — 只依赖 order_no(后端 downloadReceiptByOrderNo 自动推导 enterprise_id,
+// 避免未选企业时 enterprise_id 为 undefined 拼出 /receipt/undefined/{file_id})
+async function pollForReceipt(order_no: string) {
   const maxRetries = 3;
   const retryInterval = 3000;
   let retryCount = 0;
@@ -1925,10 +1927,7 @@ async function pollForReceipt(file_id: string) {
     retryCount++;
 
     try {
-      const res = await AccountAPI.queryReceipt({
-        enterprise_id: currentEnterpriseId.value!,
-        file_id,
-      });
+      const res = await AccountAPI.downloadReceipt({ order_no });
 
       const { status, download_url, error_message } = res.data.data;
 

+ 36 - 11
java/src/main/java/com/payment/platform/module/payment/account/service/AccountService.java

@@ -381,8 +381,13 @@ public class AccountService {
     }
 
     /**
-     * 申请回单 — 对应 Python apply_receipt_service (L824-893)
-     * 调用 alipay.commerce.ec.trans.receipt.apply
+     * 申请回单 — ISV 模式(对应 Python apply_receipt_service L824-893)
+     * 调用 alipay.data.bill.ereceiptagent.apply(基于协议获取账单回单)
+     *
+     * 2026-08 从 alipay.commerce.ec.trans.receipt.apply(商户直连企业码模式)切换为
+     * ISV 协议代理模式。前端仍传 enterprise_id + order_no,后端内部映射:
+     *   key = TransferEntity.fundOrderId(支付宝 pay_fund_order_id)
+     *   agreement_no = AccountEntity.agreementNo(ISV 与商户授权协议号)
      */
     public Map<String, String> receiptApply(Map<String, Object> body) {
         String enterpriseId = (String) body.get("enterprise_id");
@@ -390,6 +395,15 @@ public class AccountService {
         if (enterpriseId == null || orderNo == null)
             throw new BusinessException(400, "enterprise_id 和 order_no 不能为空");
 
+        // 从转账记录取支付宝资金单号 pay_fund_order_id 作为申请 key
+        TransferEntity transfer = transferMapper.selectOne(
+                new LambdaQueryWrapper<TransferEntity>().eq(TransferEntity::getOrderNo, orderNo));
+        if (transfer == null)
+            throw new BusinessException(400, "未找到对应转账记录: " + orderNo);
+        String fundOrderId = transfer.getFundOrderId();
+        if (fundOrderId == null || fundOrderId.isBlank())
+            throw new BusinessException(400, "该转账记录缺少支付宝资金单号(pay_fund_order_id),无法申请回单");
+
         // Redis 缓存 key
         String cacheKey = "receipt:" + enterpriseId + ":" + orderNo;
         String cachedFileId = (String) redisTemplate.opsForValue().get(cacheKey);
@@ -399,11 +413,12 @@ public class AccountService {
         }
 
         try {
-            var model = new com.alipay.api.domain.AlipayCommerceEcTransReceiptApplyModel();
-            model.setEnterpriseId(enterpriseId);
-            model.setOrderNo(orderNo);
+            var model = new com.alipay.api.domain.AlipayDataBillEreceiptagentApplyModel();
+            model.setType("FUND_DETAIL");
+            model.setKey(fundOrderId);
+            model.setAgreementNo(getAgreementNo(enterpriseId));
 
-            var request = new com.alipay.api.request.AlipayCommerceEcTransReceiptApplyRequest();
+            var request = new com.alipay.api.request.AlipayDataBillEreceiptagentApplyRequest();
             request.setBizModel(model);
 
             var response = alipayClientFactory.getClient(enterpriseId).certificateExecute(request);
@@ -426,16 +441,17 @@ public class AccountService {
     }
 
     /**
-     * 查询回单状态 — 对应 Python query_receipt_service (L896-944)
-     * 调用 alipay.commerce.ec.trans.receipt.query
+     * 查询回单状态 — ISV 模式(对应 Python query_receipt_service L896-944)
+     * 调用 alipay.data.bill.accountbookereceipt.query
+     * 2026-08 从 alipay.commerce.ec.trans.receipt.query 切换,参数由 enterprise_id+file_id 映射为 agreement_no+file_id
      */
     public Map<String, Object> queryReceipt(String enterpriseId, String fileId) {
         try {
-            var model = new com.alipay.api.domain.AlipayCommerceEcTransReceiptQueryModel();
-            model.setEnterpriseId(enterpriseId);
+            var model = new com.alipay.api.domain.AlipayDataBillAccountbookereceiptQueryModel();
             model.setFileId(fileId);
+            model.setAgreementNo(getAgreementNo(enterpriseId));
 
-            var request = new com.alipay.api.request.AlipayCommerceEcTransReceiptQueryRequest();
+            var request = new com.alipay.api.request.AlipayDataBillAccountbookereceiptQueryRequest();
             request.setBizModel(model);
 
             var response = alipayClientFactory.getClient(enterpriseId).certificateExecute(request);
@@ -455,6 +471,15 @@ public class AccountService {
         }
     }
 
+    /** 获取商户签约协议号(ISV 与商户授权协议号),未签约时抛错 */
+    private String getAgreementNo(String enterpriseId) {
+        AccountEntity account = accountMapper.selectOne(
+                new LambdaQueryWrapper<AccountEntity>().eq(AccountEntity::getEnterpriseId, enterpriseId));
+        if (account == null || account.getAgreementNo() == null || account.getAgreementNo().isBlank())
+            throw new BusinessException(400, "该商户未完成签约,缺少协议号 agreement_no");
+        return account.getAgreementNo();
+    }
+
     /**
      * 获取回单下载链接(封装 apply + query)
      * 对应 Python receipt/download 端点 (controller L521-553)