ソースを参照

feat(openapi): 转账失败时异步回调通知调用方

alphah 2 週間 前
コミット
6d30d1d60a

+ 2 - 1
backend/app/plugin/module_payment/openapi/controller.py

@@ -43,7 +43,8 @@ async def open_transfer_controller(
     transfer_data.api_key = apikey.api_key
 
     result = await OpenTransferService.open_transfer_service(auth=auth, data=transfer_data)
-    return SuccessResponse(data=result, msg="转账申请已提交")
+    msg = "转账成功" if result.status == "SUCCESS" else "转账失败" if result.status == "FAIL" else "转账申请已提交"
+    return SuccessResponse(data=result, msg=msg)
 
 
 @OpenapiRouter.post(

+ 78 - 15
backend/app/plugin/module_payment/openapi/service.py

@@ -163,24 +163,87 @@ class OpenTransferService:
         if existing:
             raise CustomException("三方订单号已存在")
 
-        # 执行转账记录创建
-        result = await AccountService.transfer_service(auth=auth, data=data)
-        log.info(f"租户资金专户转账发起成功: 企业: {data.enterprise_id}, 金额: {data.amount}")
+        # 执行转账
+        try:
+            result = await AccountService.transfer_service(auth=auth, data=data)
+            log.info(f"租户资金专户转账发起成功: 企业: {data.enterprise_id}, 金额: {data.amount}")
+
+            # 保存三方订单号关联记录
+            create_data = {
+                "third_biz_no": third_biz_no,
+                "out_biz_no": result.out_biz_no,
+                "api_key": data.api_key,
+            }
+            await crud.create(create_data)
+
+            return OpenTransferOutSchema(
+                status=result.status,
+                order_no=result.order_no,
+                third_biz_no=third_biz_no,
+            )
+        except CustomException as e:
+            # 转账失败(支付宝返回错误),通知调用方
+            error_msg = e.args[0] if e.args else "转账失败"
+            log.warning(f"租户资金专户转账失败: third_biz_no={third_biz_no}, error={error_msg}")
+
+            # 异步通知调用方
+            try:
+                await cls._open_return_fail(auth, third_biz_no, error_msg, data)
+            except Exception as notify_err:
+                log.warning(f"发送失败通知异常: {notify_err}")
+
+            return OpenTransferOutSchema(
+                status="FAIL",
+                order_no="",
+                third_biz_no=third_biz_no,
+            )
 
-        # 保存三方订单号关联记录
-        create_data = {
-            "third_biz_no": third_biz_no,
-            "out_biz_no": result.out_biz_no,
-            "api_key": data.api_key,
-        }
+    @classmethod
+    async def _open_return_fail(
+        cls,
+        auth: AuthSchema,
+        third_biz_no: str,
+        error_msg: str,
+        data: OpenTransferSchema,
+    ) -> None:
+        """转账失败时通知调用方"""
+        import time
+        import json
+        import aiohttp
+
+        # 获取回调地址
+        return_url = None
+        if data.api_key:
+            from ..apikey.service import TenantApiKeyService
+            try:
+                apikey_data = await TenantApiKeyService.get_apikey_service(auth=auth, api_key=data.api_key)
+                return_url = apikey_data.return_url
+            except Exception:
+                pass
 
-        await crud.create(create_data)
+        if not return_url:
+            conf = await OpenConfService.get_conf_service(auth)
+            if conf:
+                return_url = conf.return_url
 
-        return OpenTransferOutSchema(
-            status=result.status,
-            order_no=result.order_no,
-            third_biz_no=third_biz_no,
-        )
+        if not return_url:
+            log.warning("转账失败通知: 未配置回调地址")
+            return
+
+        from app.utils.snowflake import get_snowflake_id
+        notify_id = f"n{get_snowflake_id()}"
+        timestamp = int(time.time() * 1000)
+        content = json.dumps({
+            "status": "FAIL",
+            "third_biz_no": third_biz_no,
+            "amount": str(data.amount),
+            "error_msg": error_msg,
+        }, ensure_ascii=False)
+
+        timeout = aiohttp.ClientTimeout(total=30)
+        async with aiohttp.ClientSession(timeout=timeout) as session:
+            log.info(f"转账失败回调: third_biz_no={third_biz_no}, url={return_url}")
+            await fetch_manual_retry(session, return_url, notify_id, timestamp, content)