Browse Source

feat: 签约通知由/payment/notify/alipay接收 + createAccount内置自动查询兜底

- AccountHandler 新增 alipay.user.agreement.page.sign 通知处理
- 通知到达后自动写入 AccountEntity.agreementNo + signStatus=SIGNED
- createAccount: agreement_no 为空时自动查询支付宝获取,查不到则提示未签约
- 去掉前端 return_url 传参、查询签约结果按钮等多余UI
alphaH 1 ngày trước cách đây
mục cha
commit
a109c1ac44

+ 1 - 26
frontend/src/views/module_payment/account/components/AccountOverview.vue

@@ -59,28 +59,6 @@
           </el-button>
         </div>
 
-        <div
-          class="action-item"
-          :class="{ disabled: authorizeStatus === 'AUTHORIZED' || !!accountData.account_book_id }"
-        >
-          <div class="action-icon">
-            <el-icon :size="32"><Search /></el-icon>
-          </div>
-          <div class="action-title">查询签约结果</div>
-          <div class="action-desc">
-            {{ agreementNo || "获取签约协议号" }}
-          </div>
-          <el-button
-            v-hasPerm="['module_payment:account:authorize']"
-            type="primary"
-            :disabled="authorizeStatus === 'AUTHORIZED' || !!accountData.account_book_id"
-            :loading="queryLoading"
-            @click="$emit('queryAgreement')"
-          >
-            {{ agreementNo ? "已获取" : "查询" }}
-          </el-button>
-        </div>
-
         <div
           class="action-item"
           :class="{ disabled: authorizeStatus === 'AUTHORIZED' || !!accountData.account_book_id }"
@@ -299,19 +277,16 @@
 <script setup lang="ts">
 import { useEnterpriseStore } from "@/store/modules/enterprise.store";
 import AccountAPI from "@/api/module_payment/account";
-import { Refresh, Document, Coin, Wallet, Money, Search } from "@element-plus/icons-vue";
+import { Refresh, Document, Coin, Wallet, Money } from "@element-plus/icons-vue";
 import { ref, computed, watch } from "vue";
 import en from "@/lang/package/en";
 
 const props = defineProps<{
   enterpriseId?: string;
-  agreementNo?: string;
-  queryLoading?: boolean;
 }>();
 
 const emit = defineEmits<{
   (e: "goTab", tab: string): void;
-  (e: "queryAgreement"): void;
   (e: "refresh", data: any): void;
   (e: "withdraw"): void;
 }>();

+ 3 - 15
frontend/src/views/module_payment/account/index.vue

@@ -2,12 +2,8 @@
   <div v-loading="pageLoading" class="app-container" :element-loading-text="loadingText">
     <el-tabs v-model="activeTab" class="account-tabs" type="card">
       <el-tab-pane label="账户概览" name="overview">
-        <AccountOverview ref="overviewRef" :enterprise-id="currentEnterpriseId"
-          :agreement-no="authorizeResult.agreement_no"
-          :query-loading="authorizeResult.queryLoading"
-          @refresh="handleOverviewRefresh"
-          @goTab="handleGoTab" @withdraw="withdrawVisible = true"
-          @query-agreement="handleQueryAgreement" />
+        <AccountOverview ref="overviewRef" :enterprise-id="currentEnterpriseId" @refresh="handleOverviewRefresh"
+          @goTab="handleGoTab" @withdraw="withdrawVisible = true" />
       </el-tab-pane>
 
       <!-- <el-tab-pane label="转账授权签约" name="authorize">
@@ -80,14 +76,6 @@
                   <el-option label="企业信用" value="ENT_CREDIT" />
                 </el-select>
               </el-form-item>
-              <el-form-item label="签约协议号" prop="agreement_no">
-                <el-input v-model="createForm.agreement_no" placeholder="请先点击「查询签约结果」获取协议号" disabled />
-                <template #append>
-                  <el-button :loading="authorizeResult.queryLoading" @click="handleQueryAgreement">
-                    查询签约结果
-                  </el-button>
-                </template>
-              </el-form-item>
               <el-form-item label="备注" prop="remark">
                 <el-input v-model="createForm.remark" type="textarea" placeholder="请输入备注" />
               </el-form-item>
@@ -96,7 +84,7 @@
                   v-hasPerm="['module_payment:account:create']"
                   type="primary"
                   :loading="createLoading"
-                  :disabled="!authorizeResult.agreement_no"
+                  :disabled="authorizeStatus !== 'AUTHORIZED'"
                   @click="handleCreate"
                 >
                   开通专户

+ 11 - 6
java/src/main/java/com/payment/platform/module/payment/account/service/AlipayTransferService.java

@@ -125,14 +125,13 @@ public class AlipayTransferService {
         String externalAgreementNo = enterpriseId + "_agreement";
         try {
             AlipayUserAgreementPageSignModel model = new AlipayUserAgreementPageSignModel();
-            // 安全发固定值(接入指南 §1.1)
             model.setPersonalProductCode(SIGN_PERSONAL_PRODUCT_CODE);
             model.setProductCode(SIGN_PRODUCT_CODE);
             model.setSignScene(SIGN_SCENE);
-            model.setThirdPartyType("PARTNER");               // 必选
-            model.setExternalAgreementNo(externalAgreementNo); // 必选
+            model.setThirdPartyType("PARTNER");
+            model.setExternalAgreementNo(externalAgreementNo);
             AccessParams accessParams = new AccessParams();
-            accessParams.setChannel("QRCODE");                 // 扫码签约
+            accessParams.setChannel("QRCODE");
             model.setAccessParams(accessParams);
 
             AlipayUserAgreementPageSignRequest request = new AlipayUserAgreementPageSignRequest();
@@ -215,9 +214,15 @@ public class AlipayTransferService {
             model.setMerchantUserType("BUSINESS_ORGANIZATION");
             model.setSceneCode(SCENE_CODE_ACCOUNT_BOOK);
 
+            // 自动查询签约协议号(内置到开通接口内部)
+            if (agreementNo == null || agreementNo.isBlank()) {
+                Map<String, String> qr = queryAgreement(enterpriseId, enterpriseId + "_agreement");
+                agreementNo = qr.get("agreement_no");
+                if (agreementNo == null || agreementNo.isBlank())
+                    throw new BusinessException(400, "尚未完成签约,请先在支付宝中完成签约授权");
+                log.info("自动查询签约协议号成功: enterpriseId={}, agreementNo={}", enterpriseId, agreementNo);
+            }
             // 接入指南 §2.1: ext_info 必选,agreement_no 必传
-            if (agreementNo == null || agreementNo.isBlank())
-                throw new BusinessException(400, "请先查询签约结果获取协议号");
             Map<String, String> extInfo = new LinkedHashMap<>();
             extInfo.put("agreement_no", agreementNo);
             try { model.setExtInfo(objectMapper.writeValueAsString(extInfo)); }

+ 35 - 2
java/src/main/java/com/payment/platform/module/payment/notification/handler/AccountHandler.java

@@ -2,7 +2,9 @@ package com.payment.platform.module.payment.notification.handler;
 
 import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.payment.platform.module.payment.account.entity.AccountEntity;
 import com.payment.platform.module.payment.account.entity.TransferEntity;
+import com.payment.platform.module.payment.account.mapper.AccountMapper;
 import com.payment.platform.module.payment.account.mapper.TransferMapper;
 import com.payment.platform.module.payment.enterprise.entity.EnterpriseEntity;
 import com.payment.platform.module.payment.enterprise.mapper.EnterpriseMapper;
@@ -28,6 +30,7 @@ import java.util.Map;
 public class AccountHandler extends BaseNotifyHandler {
 
     private final TransferMapper transferMapper;
+    private final AccountMapper accountMapper;
     private final EnterpriseMapper enterpriseMapper;
     private final PointsService pointsService;
 
@@ -37,7 +40,8 @@ public class AccountHandler extends BaseNotifyHandler {
             "alipay.commerce.ec.trans.account.transfer",
             "alipay.commerce.ec.trans.account.deposit",
             "alipay.commerce.ec.fund.change.notify",
-            "alipay.commerce.ec.trans.authorize.notify"
+            "alipay.commerce.ec.trans.authorize.notify",
+            "alipay.user.agreement.page.sign"   // 安全发签约结果通知
         };
     }
 
@@ -46,7 +50,9 @@ public class AccountHandler extends BaseNotifyHandler {
         log.info("处理资金变动通知: method={}", msgMethod);
 
         try {
-            if ("alipay.commerce.ec.fund.change.notify".equals(msgMethod)) {
+            if ("alipay.user.agreement.page.sign".equals(msgMethod)) {
+                handleAgreementSign(params);
+            } else if ("alipay.commerce.ec.fund.change.notify".equals(msgMethod)) {
                 handleFundChange(params);
             } else if ("alipay.commerce.ec.trans.authorize.notify".equals(msgMethod)) {
                 handleTransAuthorize(params);
@@ -58,6 +64,33 @@ public class AccountHandler extends BaseNotifyHandler {
         }
     }
 
+    /**
+     * 签约结果通知 — 安全发 alipay.user.agreement.page.sign 异步通知
+     * 将 agreement_no 写入 AccountEntity,后续创建记账本时自动使用
+     */
+    private void handleAgreementSign(Map<String, String> params) {
+        String agreementNo = params.get("agreement_no");
+        String externalAgreementNo = params.get("external_agreement_no");
+        String status = params.get("status");
+        log.info("签约结果通知: agreementNo={}, externalAgreementNo={}, status={}", agreementNo, externalAgreementNo, status);
+
+        if (agreementNo == null || agreementNo.isBlank() || externalAgreementNo == null) return;
+        if (!"NORMAL".equals(status)) return;
+
+        String enterpriseId = externalAgreementNo.replace("_agreement", "");
+        var accounts = accountMapper.selectList(
+                new LambdaQueryWrapper<AccountEntity>()
+                        .eq(AccountEntity::getEnterpriseId, enterpriseId));
+        for (AccountEntity acc : accounts) {
+            if (acc.getAgreementNo() == null || acc.getAgreementNo().isBlank()) {
+                acc.setAgreementNo(agreementNo);
+                acc.setSignStatus("SIGNED");
+                accountMapper.updateById(acc);
+                log.info("签约结果写入成功: enterpriseId={}, agreementNo={}", enterpriseId, agreementNo);
+            }
+        }
+    }
+
     /**
      * 资金变更通知 -- 对应 Python _handle_fund_change
      *

+ 1 - 0
logs/payment-platform-error.log

@@ -481,3 +481,4 @@ Caused by: java.net.ConnectException: Connection refused: getsockopt
 	at java.base/java.lang.Thread.run(Thread.java:1583)
 2026-07-15 12:10:37.135 [scheduling-1] ERROR c.p.p.common.utils.RedisLockUtil - 获取分布式锁失败: key=retry:dealing_transfers, error=Unable to connect to Redis
 2026-07-15 12:16:01.927 [tomcat-handler-29] ERROR sdk.biz.err - ErrorScene^_^40004^_^INVALID_PARAMETER^_^null^_^Windows 10^_^2026-07-15 12:16:01^_^ProtocalMustParams:charset=UTF-8&method=alipay.fund.accountbook.create&sign=fR+2FrWv81cQapnqY2ih0PC/v3RPgJYdF+m7qZU4ayQ7+PJ7eSDllUDuMxWnMAhdm1fhdGcKGZiymWZHzOBRmhOcySbECv51GI8uihDckVWI5WopdyxHHSF13wFB4a+Edhr8CSgxygGIA16k2sp8+B4q32UW3pSvC0hq9kzOAmRlBrbFaPf76D4IgfvHAIO4DgjMzH0CP0H74i7nsMEIJ9HG/OR2fubgmi4hbUa9Q6TqOk9Nr7e2u9+TqXER8wJnt224QclhByBKJWk1WJNkugO43h9/xtbc5XA9hvJbkNzbXqg8UG67Mn5paE/swoZfwtdod375fS5+x3WnxgrR4w==&version=1.0&app_id=2021006160682088&sign_type=RSA2&timestamp=2026-07-15 12:16:01^_^ProtocalOptParams:alipay_sdk=alipay-sdk-java-4.39.218.ALL&format=JSON^_^ApplicationParams:traceId=21bbbe9317840889619712186ef878&biz_content={"ext_info":"{\"agreement_no\":\"\"}","merchant_user_id":"2088780324708456","merchant_user_type":"BUSINESS_ORGANIZATION","scene_code":"SATF_FUND_BOOK"}^_^Body:{"alipay_fund_accountbook_create_response":{"msg":"Business Failed","code":"40004","sub_msg":"参数有误该场景码必须在扩展参数中传入协议号","sub_code":"INVALID_PARAMETER"},"sign":"Ow5vsdJ7myAH2TQ1pxdRhYuRK3MLxKo1/Ylc9iBmtgiVpkNCPPiLexhavwdBMZtAROaaSRQssz0Ocin1K7EagVfYbTxaDvolIiBEikWZSmnToIFhO0MYyZMKmXz91hQYhm/PAv6XDK74/geZuTFBzH1WYD0RQvUgNrgvAb/iNq1UaCcQNH6nceldl9P8FFt9tEVFt2ygmCtPwOnQ7spu99YGajNNwsEkOSI3zXPhlGB32CQO4odeVBTGguBECxCZCuOpS4Hv0X2q0UTARhndR+xfaBQ+DU6ulwbfOIb7SINB1yQ0XYOFeTTnRhI9bT2gBv13E4QJuC/It/eADKoLWg=="}^_^9ms,406ms,19ms^_^trace_id:21bbbe9317840889619712186ef878
+2026-07-15 12:19:37.239 [tomcat-handler-37] ERROR sdk.biz.err - ErrorScene^_^40004^_^INVALID_PARAMETER^_^null^_^Windows 10^_^2026-07-15 12:19:37^_^ProtocalMustParams:charset=UTF-8&method=alipay.fund.accountbook.create&sign=N4pvfh+s+lMG2CbuG/mdVK80rgr0NmERQnxH4kpU1iGu+2KstyYXRvK4j4I0JLlge1BeTTofqTYFRv5efwnEDj2PjEuWooAoDLeDnWtKbAlrtR8I46GV1k5K6RSO+ZE0hI7ltm/VcKDQVTcNoplggmDu9kWL6Oo8vOGi4NBABKUMNkU8KuLomoj7RIYcTMOVGK6rnUz0enEshoj8lRxW+dPl/RUF+R2UzpP42wPJjhqTchmbUUnum+0LLAnHoUAq9KXHW43N3A9wO3SE++eScjIJ7U80JLmh1Z4Ku5D5Be8MHAVdhrI/4MCLs/SlvN+Ko+7BoeQfNBsgzQcpjWPIJw==&version=1.0&app_id=2021006160682088&sign_type=RSA2&timestamp=2026-07-15 12:19:37^_^ProtocalOptParams:alipay_sdk=alipay-sdk-java-4.39.218.ALL&format=JSON^_^ApplicationParams:traceId=0b46b99417840891772684617e5c74&biz_content={"ext_info":"{\"agreement_no\":\"\"}","merchant_user_id":"2088780324708456","merchant_user_type":"BUSINESS_ORGANIZATION","scene_code":"SATF_FUND_BOOK"}^_^Body:{"alipay_fund_accountbook_create_response":{"msg":"Business Failed","code":"40004","sub_msg":"参数有误该场景码必须在扩展参数中传入协议号","sub_code":"INVALID_PARAMETER"},"sign":"Ow5vsdJ7myAH2TQ1pxdRhYuRK3MLxKo1/Ylc9iBmtgiVpkNCPPiLexhavwdBMZtAROaaSRQssz0Ocin1K7EagVfYbTxaDvolIiBEikWZSmnToIFhO0MYyZMKmXz91hQYhm/PAv6XDK74/geZuTFBzH1WYD0RQvUgNrgvAb/iNq1UaCcQNH6nceldl9P8FFt9tEVFt2ygmCtPwOnQ7spu99YGajNNwsEkOSI3zXPhlGB32CQO4odeVBTGguBECxCZCuOpS4Hv0X2q0UTARhndR+xfaBQ+DU6ulwbfOIb7SINB1yQ0XYOFeTTnRhI9bT2gBv13E4QJuC/It/eADKoLWg=="}^_^12ms,211ms,10ms^_^trace_id:0b46b99417840891772684617e5c74

+ 16 - 0
logs/payment-platform.log

@@ -800,3 +800,19 @@ Caused by: java.net.ConnectException: Connection refused: getsockopt
 2026-07-15 12:17:32.751 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 无待轮询交易
 2026-07-15 12:18:32.762 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 定时任务开始执行
 2026-07-15 12:18:32.804 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 无待轮询交易
+2026-07-15 12:19:32.819 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 定时任务开始执行
+2026-07-15 12:19:32.876 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 无待轮询交易
+2026-07-15 12:19:37.239 [tomcat-handler-37] ERROR sdk.biz.err - ErrorScene^_^40004^_^INVALID_PARAMETER^_^null^_^Windows 10^_^2026-07-15 12:19:37^_^ProtocalMustParams:charset=UTF-8&method=alipay.fund.accountbook.create&sign=N4pvfh+s+lMG2CbuG/mdVK80rgr0NmERQnxH4kpU1iGu+2KstyYXRvK4j4I0JLlge1BeTTofqTYFRv5efwnEDj2PjEuWooAoDLeDnWtKbAlrtR8I46GV1k5K6RSO+ZE0hI7ltm/VcKDQVTcNoplggmDu9kWL6Oo8vOGi4NBABKUMNkU8KuLomoj7RIYcTMOVGK6rnUz0enEshoj8lRxW+dPl/RUF+R2UzpP42wPJjhqTchmbUUnum+0LLAnHoUAq9KXHW43N3A9wO3SE++eScjIJ7U80JLmh1Z4Ku5D5Be8MHAVdhrI/4MCLs/SlvN+Ko+7BoeQfNBsgzQcpjWPIJw==&version=1.0&app_id=2021006160682088&sign_type=RSA2&timestamp=2026-07-15 12:19:37^_^ProtocalOptParams:alipay_sdk=alipay-sdk-java-4.39.218.ALL&format=JSON^_^ApplicationParams:traceId=0b46b99417840891772684617e5c74&biz_content={"ext_info":"{\"agreement_no\":\"\"}","merchant_user_id":"2088780324708456","merchant_user_type":"BUSINESS_ORGANIZATION","scene_code":"SATF_FUND_BOOK"}^_^Body:{"alipay_fund_accountbook_create_response":{"msg":"Business Failed","code":"40004","sub_msg":"参数有误该场景码必须在扩展参数中传入协议号","sub_code":"INVALID_PARAMETER"},"sign":"Ow5vsdJ7myAH2TQ1pxdRhYuRK3MLxKo1/Ylc9iBmtgiVpkNCPPiLexhavwdBMZtAROaaSRQssz0Ocin1K7EagVfYbTxaDvolIiBEikWZSmnToIFhO0MYyZMKmXz91hQYhm/PAv6XDK74/geZuTFBzH1WYD0RQvUgNrgvAb/iNq1UaCcQNH6nceldl9P8FFt9tEVFt2ygmCtPwOnQ7spu99YGajNNwsEkOSI3zXPhlGB32CQO4odeVBTGguBECxCZCuOpS4Hv0X2q0UTARhndR+xfaBQ+DU6ulwbfOIb7SINB1yQ0XYOFeTTnRhI9bT2gBv13E4QJuC/It/eADKoLWg=="}^_^12ms,211ms,10ms^_^trace_id:0b46b99417840891772684617e5c74
+2026-07-15 12:19:37.274 [tomcat-handler-37] WARN  c.p.p.c.e.GlobalExceptionHandler - 业务异常 [/api/v1/payment/account] 400: 开通资金记账本失败: Business Failed
+2026-07-15 12:20:32.889 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 定时任务开始执行
+2026-07-15 12:20:32.937 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 无待轮询交易
+2026-07-15 12:21:32.946 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 定时任务开始执行
+2026-07-15 12:21:32.989 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 无待轮询交易
+2026-07-15 12:22:32.998 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 定时任务开始执行
+2026-07-15 12:22:33.042 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 无待轮询交易
+2026-07-15 12:23:33.052 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 定时任务开始执行
+2026-07-15 12:23:33.096 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 无待轮询交易
+2026-07-15 12:24:33.097 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 定时任务开始执行
+2026-07-15 12:24:33.141 [scheduling-1] INFO  c.p.p.m.p.f.s.F2fTradePollScheduler - [收款轮询] 无待轮询交易
+2026-07-15 12:24:46.623 [SpringApplicationShutdownHook] INFO  c.alibaba.druid.pool.DruidDataSource - {dataSource-1} closing ...
+2026-07-15 12:24:46.625 [SpringApplicationShutdownHook] INFO  c.alibaba.druid.pool.DruidDataSource - {dataSource-1} closed