main.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. import json
  2. import logging
  3. import requests
  4. from common.esigntool.esign_config import Config
  5. from common.esigntool.esign_algorithm import buildSignJsonHeader
  6. from datetime import datetime
  7. logger = logging.getLogger(__name__)
  8. config = Config()
  9. def _request(method: str, api_path: str, body: dict = None) -> str:
  10. """统一的e签宝API请求方法
  11. Args:
  12. method: HTTP方法 (GET/POST)
  13. api_path: API路径
  14. body: 请求体字典
  15. Returns:
  16. 响应文本
  17. """
  18. headers = buildSignJsonHeader(config.appId, config.scert, method, api_path, body=body)
  19. url = config.host + api_path
  20. if body is not None:
  21. data = json.dumps(body, separators=(",", ":"), ensure_ascii=False)
  22. resp = requests.request(method, url, data=data, headers=headers)
  23. else:
  24. resp = requests.request(method, url, headers=headers)
  25. logger.debug("请求 %s %s, 响应: %s", method, api_path, resp.text)
  26. return resp.text
  27. # ==================== 签署流程标题映射 ====================
  28. SIGN_FLOW_TITLES = {
  29. "store_agreement": "商家入驻U店平台协议签署",
  30. "alipay_auth": "支付宝授权函签署",
  31. "wechat_pay_commitment": "微信支付承诺函签署",
  32. "lawyer_agreement": "律所入驻U店平台协议签署",
  33. }
  34. # ==================== 签署位置配置 ====================
  35. SIGN_POSITIONS = {
  36. "store_agreement": {
  37. "alien": {"page": 7, "x": 294, "y": 668},
  38. "signer": {"page": 7, "x": 114, "y": 666},
  39. },
  40. "alipay_auth": {
  41. "alien": {"page": 1, "x": 535, "y": 555},
  42. "signer": {"page": 1, "x": 535, "y": 431},
  43. },
  44. "wechat_pay_commitment": {
  45. "signer": {"page": 1, "x": 535, "y": 515},
  46. },
  47. "lawyer_agreement": {
  48. "alien": {"page": 6, "x": 336, "y": 418},
  49. "signer": {"page": 6, "x": 145, "y": 418},
  50. },
  51. }
  52. # ==================== 模板填充配置 ====================
  53. TEMPLATE_COMPONENT_VALUES = {
  54. "store_agreement": {
  55. "alien_name": "爱丽恩严(大连)商务科技有限公司",
  56. "date": None, # 运行时填充
  57. "one_name": None, # 运行时填充
  58. "store_name": None, # 运行时填充
  59. },
  60. "lawyer_agreement": {
  61. "alien_name": "爱丽恩严(大连)商务科技有限公司",
  62. "alien_name_2": "爱丽恩严(大连)商务科技有限公司",
  63. "law_firm_name": None, # 运行时填充
  64. "law_firm_name_2": None, # 运行时填充
  65. "signing_date": None, # 运行时填充
  66. },
  67. "alipay_auth": {},
  68. "wechat_pay_commitment": {},
  69. }
  70. def get_auth_flow_id(org_name, org_id, legal_rep_name, legal_rep_id):
  71. """获取机构认证&授权页面链接"""
  72. body = {
  73. "clientType": "ALL",
  74. "redirectConfig": {
  75. "redirectUrl": "https://www.baidu.com"
  76. },
  77. "orgAuthConfig": {
  78. "orgName": org_name,
  79. "orgInfo": {
  80. "orgIDCardNum": org_id,
  81. "orgIDCardType": "CRED_ORG_USCC",
  82. "legalRepName": legal_rep_name,
  83. "legalRepIDCardNum": legal_rep_id,
  84. "legalRepIDCardType": "CRED_PSN_CH_IDCARD"
  85. },
  86. "transactorInfo": {
  87. "psnAccount": "17337039317",
  88. "psnInfo": {
  89. "psnName": "孟骞康",
  90. "psnIDCardNum": "411426200308121212",
  91. "psnIDCardType": "CRED_PSN_CH_IDCARD",
  92. "psnMobile": "17337039317"
  93. }
  94. }
  95. },
  96. "notifyUrl": "http://120.26.186.130:33333/api/store/esign/callback_auth",
  97. "transactorUseSeal": True
  98. }
  99. return _request("POST", "/v3/org-auth-url", body)
  100. def get_template_detail():
  101. """查询合同模板中控件详情"""
  102. return _request("GET", f"/v3/doc-templates/{config.templates_id}")
  103. def fill_in_template(name, contract_type="store_agreement"):
  104. """填写模板生成文件
  105. Args:
  106. name: 商家/律所名称
  107. contract_type: 合同类型 (store_agreement/lawyer_agreement/alipay_auth/wechat_pay_commitment)
  108. """
  109. today = datetime.now().strftime("%Y年%m月%d日")
  110. base_values = TEMPLATE_COMPONENT_VALUES.get(contract_type, {})
  111. # 根据合同类型填充动态字段
  112. dynamic_values = {
  113. "store_agreement": {"store_name": name, "one_name": name, "date": today},
  114. "lawyer_agreement": {"law_firm_name": name, "law_firm_name_2": name, "signing_date": today},
  115. }.get(contract_type, {})
  116. components = [
  117. {"componentKey": key, "componentValue": value}
  118. for key, value in {**base_values, **dynamic_values}.items()
  119. ]
  120. body = {
  121. "docTemplateId": config.templates_map.get(contract_type, ""),
  122. "fileName": config.template_file_names.get(contract_type, ""),
  123. "components": components
  124. }
  125. return _request("POST", "/v3/files/create-by-doc-template", body)
  126. def get_contract_detail(file_id):
  127. """查询PDF模板填写后文件"""
  128. return _request("GET", f"/v3/files/{file_id}")
  129. def _build_alien_signer(file_id, position):
  130. """构建平台方(爱丽恩严)签署人配置"""
  131. return {
  132. "signConfig": {"signOrder": 1},
  133. "signerType": 1,
  134. "signFields": [{
  135. "customBizNum": "9527",
  136. "fileId": file_id,
  137. "normalSignFieldConfig": {
  138. "autoSign": True,
  139. "signFieldStyle": 1,
  140. "signFieldPosition": {
  141. "positionPage": str(position["page"]),
  142. "positionX": position["x"],
  143. "positionY": position["y"]
  144. }
  145. }
  146. }]
  147. }
  148. def _build_org_signer(file_id, position, signer_name, signer_id_num, psn_account, psn_name):
  149. """构建签署方(商家/律所)签署人配置"""
  150. return {
  151. "signConfig": {"forcedReadingTime": 10, "signOrder": 2},
  152. "signerType": 1,
  153. "orgSignerInfo": {
  154. "orgName": signer_name,
  155. "orgInfo": {
  156. "orgIDCardNum": signer_id_num,
  157. "orgIDCardType": "CRED_ORG_USCC"
  158. },
  159. "transactorInfo": {
  160. "psnAccount": psn_account,
  161. "psnInfo": {"psnName": psn_name}
  162. }
  163. },
  164. "signFields": [{
  165. "customBizNum": "自定义编码001",
  166. "fileId": file_id,
  167. "normalSignFieldConfig": {
  168. "signFieldStyle": 1,
  169. "signFieldPosition": {
  170. "positionPage": str(position["page"]),
  171. "positionX": position["x"],
  172. "positionY": position["y"]
  173. }
  174. }
  175. }]
  176. }
  177. def create_by_file(file_id, file_name, signer_name, signer_id_num, psn_account, psn_name, contract_type="store_agreement"):
  178. """基于文件发起签署
  179. Args:
  180. file_id: 文件ID
  181. file_name: 文件名
  182. signer_name: 签署方名称
  183. signer_id_num: 签署方证件号
  184. psn_account: 经办人账号标识(手机号/邮箱)
  185. psn_name: 经办人姓名
  186. contract_type: 合同类型 (store_agreement/alipay_auth/wechat_pay_commitment/lawyer_agreement)
  187. """
  188. positions = SIGN_POSITIONS.get(contract_type, SIGN_POSITIONS["store_agreement"])
  189. signers = []
  190. if positions.get("alien"):
  191. signers.append(_build_alien_signer(file_id, positions["alien"]))
  192. if positions.get("signer"):
  193. signers.append(_build_org_signer(file_id, positions["signer"], signer_name, signer_id_num, psn_account, psn_name))
  194. body = {
  195. "docs": [{"fileId": file_id, "fileName": f"{file_name}.pdf"}],
  196. "signFlowConfig": {
  197. "signFlowTitle": SIGN_FLOW_TITLES.get(contract_type, "合同签署"),
  198. "autoFinish": True,
  199. "noticeConfig": {"noticeTypes": "1,2"},
  200. "notifyUrl": "http://120.26.186.130:33333:/api/store/esign/callback",
  201. "redirectConfig": {"redirectUrl": "https://www.esign.cn/"},
  202. },
  203. "signers": signers
  204. }
  205. return _request("POST", "/v3/sign-flow/create-by-file", body)
  206. def sign_url(sign_flow_id, psn_account):
  207. """获取签署页面链接"""
  208. body = {
  209. "signFlowId": sign_flow_id,
  210. "clientType": "ALL",
  211. "needLogin": False,
  212. "operator": {"psnAccount": psn_account},
  213. "urlType": 2
  214. }
  215. return _request("POST", f"/v3/sign-flow/{sign_flow_id}/sign-url", body)
  216. def file_download_url(sign_flow_id):
  217. """下载已签署文件及附属材料"""
  218. body = {"urlAvailableDate": "3600"}
  219. return _request("POST", f"/v3/sign-flow/{sign_flow_id}/file-download-url", body)