Ver Fonte

fix(contract): 完善 e签宝签署完成处理

Co-authored-by: Cursor <cursoragent@cursor.com>
天空之城 há 11 horas atrás
pai
commit
953e9b7a47

+ 31 - 1
alien_contract/infrastructure/esign/contract_builder.py

@@ -4,7 +4,7 @@ import re
 import urllib.parse
 from typing import Any
 
-from alien_contract.infrastructure.esign.main import fill_in_template, create_by_file
+from alien_contract.infrastructure.esign.main import SIGN_POSITIONS, apply_platform_seal, fill_in_template, create_by_file
 from alien_contract.infrastructure.esign.esign_config import Config
 
 logger = logging.getLogger(__name__)
@@ -18,6 +18,34 @@ class ContractBuildError(Exception):
         self.raw = raw
 
 
+def _is_esign_success(resp: dict[str, Any]) -> bool:
+    code = resp.get("code")
+    return code in (0, "0") or resp.get("success") is True
+
+
+def _apply_platform_seal_if_required(contract_type: str, contract_name: str, sign_flow_id: str) -> None:
+    positions = SIGN_POSITIONS.get(contract_type, {})
+    if not positions.get("alien"):
+        return
+
+    seal_resp = apply_platform_seal(sign_flow_id)
+    try:
+        seal_json = json.loads(seal_resp)
+    except json.JSONDecodeError:
+        logger.error("apply_platform_seal non-json resp contract_type=%s: %s", contract_type, seal_resp)
+        raise ContractBuildError(
+            message=f"{contract_name}平台自动盖章失败:e签宝返回非 JSON",
+            raw={"contract_type": contract_type, "sign_flow_id": sign_flow_id, "resp": seal_resp},
+        )
+
+    if not isinstance(seal_json, dict) or not _is_esign_success(seal_json):
+        logger.error("apply_platform_seal failed contract_type=%s: %s", contract_type, seal_json)
+        raise ContractBuildError(
+            message=f"{contract_name}平台自动盖章失败",
+            raw={"contract_type": contract_type, "sign_flow_id": sign_flow_id, "resp": seal_json},
+        )
+
+
 def build_contract_items(
     configs: list[tuple[str, str, int]],
     template_name: str,
@@ -109,6 +137,8 @@ def build_contract_items(
                 raw={"contract_type": contract_type, "resp": sign_json},
             )
 
+        _apply_platform_seal_if_required(contract_type, contract_name, sign_id)
+
         items.append(
             {
                 "contract_type": contract_type,

+ 55 - 10
alien_contract/services/contract_server.py

@@ -2,6 +2,7 @@ import datetime
 import json
 import logging
 import os
+from typing import Any
 
 from sqlalchemy.ext.asyncio import AsyncSession
 
@@ -34,13 +35,13 @@ def _init_logger():
 
 logger = _init_logger()
 
-SIGN_SUCCESS_ACTIONS = {
-    "OPERATOR_COMPLETE_SIGN",
+SIGN_FLOW_FINISH_ACTIONS = {
     "SIGN_FLOW_FINISH",
     "SIGN_FLOW_COMPLETE",
 }
 
-NON_SIGNING_ACTIONS = {
+NON_FINAL_SIGN_ACTIONS = {
+    "OPERATOR_COMPLETE_SIGN",
     "OPERATOR_READ",
     "OPERATOR_VIEW",
 }
@@ -64,6 +65,23 @@ DEFAULT_BUNDLE_BY_SUBJECT = {
 }
 
 
+def _extract_download_url(download_resp: str) -> tuple[str | None, Any]:
+    try:
+        download_json = json.loads(download_resp)
+    except json.JSONDecodeError as exc:
+        return None, {"error": f"e签宝返回非 JSON: {exc}", "resp": download_resp}
+
+    data = download_json.get("data") if isinstance(download_json, dict) else None
+    files = data.get("files") if isinstance(data, dict) else None
+    if isinstance(files, list) and files:
+        first_file = files[0]
+        if isinstance(first_file, dict):
+            download_url = first_file.get("downloadUrl")
+            if download_url:
+                return download_url, download_json
+    return None, download_json
+
+
 class ContractCenterService:
     def __init__(self, db: AsyncSession):
         self.repo = ContractRepository(db)
@@ -229,10 +247,32 @@ class ContractCenterService:
     async def _get_signed_detail(self, document, _bundle):
         try:
             download_resp = file_download_url(document.sign_flow_id)
-            download_json = json.loads(download_resp)
-            contract_download_url = download_json["data"]["files"][0]["downloadUrl"]
         except Exception as exc:
+            cached_url = document.download_url or None
+            if cached_url:
+                return {
+                    "status": 1,
+                    "contract_url": cached_url,
+                    "contract_download_url": cached_url,
+                    "sign_flow_id": document.sign_flow_id,
+                }
             return {"success": False, "message": "获取合同下载链接失败", "raw": str(exc)}
+        contract_download_url, raw = _extract_download_url(download_resp)
+        if not contract_download_url:
+            cached_url = document.download_url or None
+            if cached_url:
+                return {
+                    "status": 1,
+                    "contract_url": cached_url,
+                    "contract_download_url": cached_url,
+                    "sign_flow_id": document.sign_flow_id,
+                }
+            logger.error(
+                "file_download_url missing downloadUrl sign_flow_id=%s resp=%s",
+                document.sign_flow_id,
+                download_resp,
+            )
+            return {"success": False, "message": "合同已签署,下载文件生成中,请稍后重试", "raw": raw}
         await self.repo.update_document_urls(document.id, template_url=contract_download_url, download_url=contract_download_url)
         await self.repo.commit()
         return {
@@ -288,7 +328,7 @@ class ContractCenterService:
         event_type = f"esign_callback:{action or 'UNKNOWN'}"
         await self.repo.create_event(bundle.id, document.id, sign_flow_id, event_type[:50], payload)
 
-        mark_signed = bool(sign_result == 2 or (action in SIGN_SUCCESS_ACTIONS and action not in NON_SIGNING_ACTIONS))
+        mark_signed = bool(action in SIGN_FLOW_FINISH_ACTIONS or (sign_result == 2 and action not in NON_FINAL_SIGN_ACTIONS))
 
         logger.info(
             "esign_callback_event %s",
@@ -322,10 +362,15 @@ class ContractCenterService:
             contract_download_url = None
             try:
                 download_resp = file_download_url(sign_flow_id)
-                download_json = json.loads(download_resp)
-                contract_download_url = download_json["data"]["files"][0]["downloadUrl"]
-            except Exception:
-                contract_download_url = None
+                contract_download_url, raw_download_resp = _extract_download_url(download_resp)
+            except Exception as exc:
+                raw_download_resp = str(exc)
+            if not contract_download_url:
+                logger.error(
+                    "file_download_url missing downloadUrl on callback sign_flow_id=%s resp=%s",
+                    sign_flow_id,
+                    raw_download_resp,
+                )
             await self.repo.mark_document_signed(document.id, signing_dt, effective_dt, expiry_dt, contract_download_url)
             await self.repo.recalc_bundle_status(bundle.id)
             await self.repo.commit()