| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- import datetime
- import json
- import logging
- import os
- from sqlalchemy.ext.asyncio import AsyncSession
- from alien_contract.repositories.contract_repo import ContractRepository
- from alien_contract.schemas.request.contract import BundleCreateRequest
- from alien_contract.infrastructure.esign import main as esign_main
- from alien_contract.infrastructure.esign.contract_builder import build_contract_items, ContractBuildError
- from alien_contract.infrastructure.esign.main import sign_url, file_download_url
- LOG_DIR = os.path.join("common", "logs", "alien_contract")
- os.makedirs(LOG_DIR, exist_ok=True)
- def _init_logger():
- logger = logging.getLogger("alien_contract_service")
- if logger.handlers:
- return logger
- logger.setLevel(logging.INFO)
- fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s %(message)s")
- info_handler = logging.FileHandler(os.path.join(LOG_DIR, "info.log"), encoding="utf-8")
- info_handler.setLevel(logging.INFO)
- info_handler.setFormatter(fmt)
- error_handler = logging.FileHandler(os.path.join(LOG_DIR, "error.log"), encoding="utf-8")
- error_handler.setLevel(logging.ERROR)
- error_handler.setFormatter(fmt)
- logger.addHandler(info_handler)
- logger.addHandler(error_handler)
- return logger
- logger = _init_logger()
- BUNDLE_CONFIGS = {
- "STORE_STANDARD": [
- ("store_agreement", "店铺入驻协议", 1),
- ("alipay_auth", "支付宝授权函", 0),
- ("wechat_pay_commitment", "微信支付承诺函", 0),
- ],
- "LAWYER_STANDARD": [
- ("lawyer_agreement", "律所入驻协议", 1),
- ("alipay_auth", "支付宝授权函", 0),
- ("wechat_pay_commitment", "微信支付承诺函", 0),
- ],
- }
- DEFAULT_BUNDLE_BY_SUBJECT = {
- "store": "STORE_STANDARD",
- "lawyer": "LAWYER_STANDARD",
- }
- class ContractCenterService:
- def __init__(self, db: AsyncSession):
- self.repo = ContractRepository(db)
- async def create_bundle(self, req: BundleCreateRequest) -> dict:
- bundle_type = req.bundle_type or DEFAULT_BUNDLE_BY_SUBJECT[req.subject_type]
- configs = BUNDLE_CONFIGS.get(bundle_type)
- if not configs:
- return {"success": False, "message": "不支持的合同包类型", "raw": {"bundle_type": bundle_type}}
- try:
- items = build_contract_items(
- configs=configs,
- template_name=req.subject_name,
- signer_name=req.subject_name,
- signer_id_num=req.ord_id,
- psn_account=req.contact_phone,
- psn_name=req.contact_name,
- )
- except ContractBuildError as exc:
- return {"success": False, "message": exc.message, "raw": exc.raw}
- bundle = await self.repo.create_bundle(
- {
- "subject_type": req.subject_type,
- "subject_id": req.subject_id,
- "subject_name": req.subject_name,
- "business_segment": req.business_segment,
- "contact_name": req.contact_name,
- "contact_phone": req.contact_phone,
- "ord_id": req.ord_id,
- "bundle_type": bundle_type,
- "status": "pending",
- }
- )
- documents = await self.repo.create_documents(bundle.id, items)
- primary_doc = next((doc for doc in documents if doc.is_primary == 1), documents[0])
- await self.repo.set_primary_document(bundle.id, primary_doc.id)
- await self.repo.commit()
- return {
- "success": True,
- "message": "合同包创建成功",
- "bundle_id": bundle.id,
- "primary_sign_flow_id": primary_doc.sign_flow_id,
- "created_contracts": [
- {
- "contract_type": d.contract_type,
- "contract_name": d.contract_name,
- "sign_flow_id": d.sign_flow_id,
- "file_id": d.file_id,
- "contract_url": d.template_url,
- }
- for d in documents
- ],
- }
- async def list_bundles(self, subject_type: str, subject_id: int, page: int, page_size: int) -> dict:
- bundles, total = await self.repo.list_bundles(subject_type, subject_id, page, page_size)
- ids = [b.id for b in bundles]
- docs_map = await self.repo.list_documents_by_bundle_ids(ids)
- items = []
- for b in bundles:
- docs = docs_map.get(b.id, [])
- items.append(
- {
- "id": b.id,
- "subject_type": b.subject_type,
- "subject_id": b.subject_id,
- "subject_name": b.subject_name,
- "business_segment": b.business_segment,
- "contact_name": b.contact_name,
- "contact_phone": b.contact_phone,
- "ord_id": b.ord_id,
- "bundle_type": b.bundle_type,
- "status": b.status,
- "primary_document_id": b.primary_document_id,
- "documents": [
- {
- "id": d.id,
- "contract_type": d.contract_type,
- "contract_name": d.contract_name,
- "is_primary": d.is_primary,
- "status": d.status,
- "sign_flow_id": d.sign_flow_id,
- "file_id": d.file_id,
- "template_url": d.template_url,
- "sign_url": d.sign_url,
- "download_url": d.download_url,
- "signing_time": d.signing_time,
- "effective_time": d.effective_time,
- "expiry_time": d.expiry_time,
- }
- for d in docs
- ],
- }
- )
- total_pages = (total + page_size - 1) // page_size if total > 0 else 0
- return {"items": items, "total": total, "page": page, "page_size": page_size, "total_pages": total_pages}
- async def get_document_detail(self, sign_flow_id: str) -> dict:
- document, bundle = await self.repo.get_document_and_bundle(sign_flow_id)
- if not document:
- return {"success": False, "message": "未找到合同"}
- if document.status == 0:
- return await self._get_pending_detail(document, bundle)
- return await self._get_signed_detail(document, bundle)
- async def _get_pending_detail(self, document, bundle):
- try:
- detail_resp = esign_main.get_contract_detail(document.file_id)
- detail_json = json.loads(detail_resp)
- data = detail_json.get("data") if isinstance(detail_json, dict) else None
- contract_url_val = data.get("fileDownloadUrl") if isinstance(data, dict) else None
- if not contract_url_val and isinstance(detail_json, dict):
- contract_url_val = detail_json.get("fileDownloadUrl")
- except Exception as exc:
- return {"success": False, "message": "获取合同链接失败", "raw": str(exc)}
- if not contract_url_val:
- return {"success": False, "message": "e签宝返回缺少合同链接", "raw": detail_resp}
- await self.repo.update_document_urls(document.id, template_url=contract_url_val)
- try:
- sign_resp = sign_url(document.sign_flow_id, bundle.contact_phone)
- sign_json = json.loads(sign_resp)
- sign_data = sign_json.get("data") if isinstance(sign_json, dict) else None
- result_sign_url = sign_data.get("url") if isinstance(sign_data, dict) else None
- except Exception as exc:
- return {"success": False, "message": "获取签署链接失败", "raw": str(exc)}
- if not result_sign_url:
- return {"success": False, "message": "e签宝返回缺少签署链接", "raw": sign_json}
- await self.repo.update_document_urls(document.id, sign_url=result_sign_url)
- await self.repo.commit()
- return {
- "status": 0,
- "contract_url": contract_url_val,
- "sign_url": result_sign_url,
- "sign_flow_id": document.sign_flow_id,
- }
- 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:
- return {"success": False, "message": "获取合同下载链接失败", "raw": str(exc)}
- await self.repo.update_document_urls(document.id, template_url=contract_download_url, download_url=contract_download_url)
- await self.repo.commit()
- return {
- "status": 1,
- "contract_url": contract_download_url,
- "contract_download_url": contract_download_url,
- "sign_flow_id": document.sign_flow_id,
- }
- async def process_esign_callback(self, payload: dict) -> dict:
- sign_result = payload.get("signResult")
- sign_flow_id = payload.get("signFlowId")
- if not sign_flow_id:
- return {"success": True, "code": "200", "msg": "ignored_missing_signFlowId"}
- document, bundle = await self.repo.get_document_and_bundle(sign_flow_id)
- if not document:
- return {"success": True, "code": "200", "msg": "ignored_unknown_signFlowId"}
- await self.repo.create_event(bundle.id, document.id, sign_flow_id, "esign_callback", payload)
- if sign_result == 2:
- ts_ms = payload.get("operateTime") or payload.get("timestamp")
- signing_dt = None
- if ts_ms:
- try:
- signing_dt = datetime.datetime.fromtimestamp(ts_ms / 1000)
- except Exception:
- signing_dt = None
- effective_dt = expiry_dt = None
- if signing_dt:
- effective_dt = (signing_dt + datetime.timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
- expiry_dt = effective_dt + datetime.timedelta(days=365)
- 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
- 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()
- return {"success": True, "code": "200", "msg": "success"}
- await self.repo.commit()
- return {"success": True, "code": "200", "msg": f"ignored_signResult_{sign_result}"}
|