ESignContractServer.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. <?php
  2. namespace App\Servers\ESign;
  3. use App\Models\FactoringContract;
  4. use App\Models\FactoringContractFile;
  5. use App\Models\FactoringContractSign;
  6. use App\Models\FactoringContractText;
  7. use App\Models\FactoringProperty;
  8. use App\Models\Firm;
  9. use App\Models\Member;
  10. use App\Models\SignContract;
  11. use App\Models\SignFirm;
  12. use App\Models\SignMember;
  13. use App\Models\SignSeal;
  14. use App\Servers\ESign\comm\EsignHttpHelper;
  15. use App\Servers\ESign\comm\EsignLogHelper;
  16. use App\Servers\ESign\comm\EsignUtilHelper;
  17. use App\Servers\ESign\emun\HttpEmun;
  18. use App\Servers\Financing\ContractServer;
  19. use App\Servers\Financing\FinancingServer;
  20. use App\Servers\System\CommonServer;
  21. use App\Servers\System\SmsServer;
  22. class ESignContractServer
  23. {
  24. static private $server = null;
  25. public $config = [
  26. 'eSignAppId'=>'', //app id
  27. 'eSignAppSecret'=>'', // app key
  28. 'eSignHost'=>'https://smlopenapi.esign.cn', //模拟环境
  29. // 'url'=>'https://openapi.esign.cn' //正式环境
  30. ];
  31. function __construct()
  32. {
  33. $this->config['eSignAppId']=env('E_SIGN_APP_ID');
  34. $this->config['eSignAppSecret']=env('E_SIGN_APP_SECRET');
  35. $this->config['eSignPublicKey']=env('E_SIGN_PUBLIC_KEY');
  36. $this->config['eSignHost']=env('E_SIGN_HOST');
  37. }
  38. /**
  39. * 创建对象
  40. * @return ESignContractServer
  41. */
  42. static function creatServer()
  43. {
  44. if (empty(self::$server)) self::$server = new ESignContractServer();
  45. return self::$server;
  46. }
  47. function contentMd5($filePath){
  48. if(!file_exists($filePath)){
  49. EsignLogHelper::printMsg($filePath."文件不存在");exit;
  50. }
  51. return EsignUtilHelper::getContentBase64Md5($filePath);
  52. }
  53. /**
  54. * 上传合同文件至E签宝
  55. * @param FactoringContractFile $file
  56. * @return mixed
  57. */
  58. function fileUploadUrl(FactoringContractFile $file){
  59. $config=$this->config;
  60. $file_pdf=$file->{'file_pdf'};
  61. $app_url=env('APP_URL');
  62. $file_pdf=str_replace($app_url,'',$file_pdf);
  63. $file_name=CommonServer::creatServer()->getFileName($file_pdf);
  64. $filePath=public_path($file_pdf);
  65. $contentMd5=$this->contentMd5($filePath);
  66. $apiaddr="/v3/files/file-upload-url";
  67. $requestType = HttpEmun::POST;
  68. $data=[
  69. "contentMd5" =>$contentMd5,
  70. "contentType" =>"application/pdf",
  71. "convertToPDF" =>false,
  72. "fileName" =>$file_name,
  73. "fileSize" =>filesize($filePath)
  74. ];
  75. $paramStr = json_encode($data);
  76. //生成签名验签+json体的header
  77. $signAndBuildSignAndJsonHeader = EsignHttpHelper::signAndBuildSignAndJsonHeader($config['eSignAppId'], $config['eSignAppSecret'], $paramStr, $requestType, $apiaddr);
  78. //获取文件上传地址
  79. EsignLogHelper::printMsg($signAndBuildSignAndJsonHeader);
  80. $response = EsignHttpHelper::doCommHttp($config['eSignHost'], $apiaddr, $requestType, $signAndBuildSignAndJsonHeader, $paramStr);
  81. EsignLogHelper::printMsg($response->getStatus());
  82. EsignLogHelper::printMsg($response->getBody());
  83. $fileUploadUrl=json_decode($response->getBody())->data->fileUploadUrl;
  84. $fileId=json_decode($response->getBody())->data->fileId;
  85. //文件流put上传
  86. $response=EsignHttpHelper::upLoadFileHttp($fileUploadUrl,$filePath,"application/pdf");
  87. EsignLogHelper::printMsg($response->getStatus());
  88. EsignLogHelper::printMsg($response->getBody());
  89. $file->update(['sign_file_id'=>$fileId]);
  90. return $fileId;
  91. }
  92. function getKeywordPositions($fileId,$keywords)
  93. {
  94. $config=$this->config;
  95. $apiaddr="/v3/files/{$fileId}/keyword-positions";
  96. $requestType = HttpEmun::POST;
  97. $data=[
  98. "keywords" =>$keywords
  99. ];
  100. $paramStr = json_encode($data);
  101. //生成签名验签+json体的header
  102. $signAndBuildSignAndJsonHeader = EsignHttpHelper::signAndBuildSignAndJsonHeader($config['eSignAppId'], $config['eSignAppSecret'], $paramStr, $requestType, $apiaddr);
  103. //获取文件上传地址
  104. EsignLogHelper::printMsg($signAndBuildSignAndJsonHeader);
  105. $response = EsignHttpHelper::doCommHttp($config['eSignHost'], $apiaddr, $requestType, $signAndBuildSignAndJsonHeader, $paramStr);
  106. EsignLogHelper::printMsg($response->getStatus());
  107. EsignLogHelper::printMsg($response->getBody());
  108. $ret=json_decode($response->getBody(),true);
  109. if(isset($ret['code']) && $ret['code']==0){
  110. return ['code'=>'1','msg'=>'ok','data'=>$ret['data']['keywordPositions']];
  111. }else{
  112. return ['code'=>'0','msg'=>$ret['message']];
  113. }
  114. }
  115. function getFileStatus($fileId)
  116. {
  117. $config=$this->config;
  118. $apiaddr="/v3/files/{$fileId}";
  119. $requestType = HttpEmun::GET;
  120. //生成签名验签+json体的header
  121. $signAndBuildSignAndJsonHeader = EsignHttpHelper::signAndBuildSignAndJsonHeader($config['eSignAppId'], $config['eSignAppSecret'], "", $requestType, $apiaddr);
  122. //发起接口请求
  123. // EsignLogHelper::printMsg($signAndBuildSignAndJsonHeader);
  124. $response = EsignHttpHelper::doCommHttp($config['eSignHost'], $apiaddr, $requestType, $signAndBuildSignAndJsonHeader, "");
  125. // EsignLogHelper::printMsg($response->getStatus());
  126. // EsignLogHelper::printMsg($response->getBody());
  127. $ret=json_decode($response->getBody(),true);
  128. if(isset($ret['code']) && $ret['code']==0){
  129. return ['code'=>'1','msg'=>'ok','data'=>$ret['data']];
  130. }else{
  131. return ['code'=>'0','msg'=>$ret['message']];
  132. }
  133. }
  134. /**
  135. * 生成待电签记录
  136. * @param $contractId
  137. * @return void
  138. */
  139. function addSignContract($contractId)
  140. {
  141. $num=SignContract::where('contract_id',$contractId)->count();
  142. if($num<=0){
  143. SignContract::create(['contract_id'=>$contractId,'status' => 0,'file_id'=>'','msg'=>'']);
  144. }
  145. }
  146. function setContractContract()
  147. {
  148. $list=SignContract::where('status',0)->limit(10)->get();
  149. foreach ($list as $k=>$v){
  150. $contract=FactoringContract::where('id',$v->{'contract_id'})->select(['id','file_no','name','status','signatory','property','factoring_id','firm_id','factor_id'])->first();
  151. if(empty($contract)){
  152. $v->update(['status' => 3,'msg'=>'合同信息不存在']);
  153. }else{
  154. $file=FactoringContractFile::where('file_no',$contract->{'file_no'})->where('type',2)->orderBy('id','desc')->select(['id','file_pdf','file_name','file_no','file_path','version'])->first();
  155. if(empty($file)){
  156. sleep(1);
  157. continue;
  158. }
  159. if(empty($v['file_id'])){
  160. //上传E签宝文件模板
  161. $fileId=$this->fileUploadUrl($file);
  162. if(empty($fileId)){
  163. $v->update(['status' => 3,'msg'=>'合同模板上传失败']);
  164. }else{
  165. $v->update(['file_id' => $fileId,'contract_file_id'=>$file['id']]);
  166. }
  167. $file->update(['sign_file_id'=>$fileId]);
  168. }else{
  169. $fileId=$v['file_id'];
  170. }
  171. //校验盖章区域
  172. $signatory=$contract->{'signatory'};
  173. $keywords=[];
  174. if(in_array(1,$signatory)){
  175. $keywords[]=FactoringContractText::where('id',2)->value('custom');
  176. }
  177. if(in_array(5,$signatory)){
  178. $keywords[]=FactoringContractText::where('id',3)->value('custom');
  179. }
  180. if(empty($keywords)){
  181. $v->update(['status' => 3,'msg'=>'无需要电签']);
  182. continue;
  183. }
  184. $positionRet=ESignContractServer::creatServer()->getKeywordPositions($fileId,$keywords);
  185. if(empty($positionRet['code'])){
  186. $v->update(['status' => 3,'msg'=>'签署区域定位失败']);
  187. continue;
  188. }else{
  189. $positionData=$positionRet['data'];
  190. }
  191. $propertyIds=$contract->{'property'};
  192. $propertyItem=FactoringProperty::whereIn('id',$propertyIds)->select(['id','firm_id','factor_id'])->first();
  193. if(empty($propertyItem)){
  194. $v->update(['status' => 3,'msg'=>'资产信息异常']);
  195. continue;
  196. }
  197. $signers=[];
  198. $isAuto=1;
  199. $signUrl=[];
  200. foreach ($positionData as $item){
  201. if(count($item['positions'])<=0){
  202. continue;
  203. }
  204. if($item['keyword']=='#转让方盖章处#'){
  205. $signType=1;
  206. $firmId=$contract['firm_id'];
  207. }else{
  208. $firmId=$contract['factor_id'];
  209. $signType=5;
  210. }
  211. $orgId=SignFirm::where('firm_id',$firmId)->value('org_id');
  212. $seal=SignSeal::where('firm_id',$firmId)->where('seal_type','CONTRACT')->select(['id','seal_id','is_auth'])->first();
  213. dump($seal);
  214. if(empty($orgId) || empty($seal)){
  215. continue;
  216. }
  217. $assignedSealId=$seal->{'seal_id'};
  218. $position=end($item['positions']);
  219. $coordinate=end($position['coordinates']);
  220. if($seal->{'is_auth'}){
  221. $sysOrgId=CommonServer::creatServer()->getConfigValue('sys_org_id');
  222. $signer=[
  223. "orgSignerInfo" => [
  224. "orgId" => $sysOrgId //其他企业自动签署,设置orgId平台方id
  225. ],
  226. "signFields" =>[
  227. [
  228. "fileId" =>$fileId,
  229. "normalSignFieldConfig" => [
  230. "autoSign" =>true,
  231. "signFieldStyle" =>1,
  232. "assignedSealId"=>$assignedSealId, // 在signFields中设置 assignedSealId为授权企业印章id
  233. "signFieldPosition" => [
  234. "positionPage" =>$position['pageNum'].'',
  235. "positionX" =>(int)$coordinate['positionX'],
  236. "positionY" =>(int)$coordinate['positionY'],
  237. ]
  238. ]
  239. ]
  240. ],
  241. "signerType" =>1
  242. ];
  243. }else{
  244. $isAuto=0;
  245. $member=Member::where('firm_id',$firmId)->where('is_admin',1)->select(['id','phone','promissory_name','promissory_id_number'])->first();
  246. if(empty($member)){
  247. continue;
  248. }
  249. $firm=Firm::where('id',$firmId)->select(['id','firm_name','license_number'])->first();
  250. if(empty($firm)){
  251. continue;
  252. }
  253. $psnId=SignMember::where('m_id',$member->id)->value('psnId');
  254. if(empty($psnId)){
  255. continue;
  256. }
  257. //记录需要手动签署的
  258. $signUrl[]=[
  259. 'promissoryName'=>$member['promissory_name'],
  260. 'psnAccount'=>$member['phone'],
  261. 'orgName'=>$firm->{'firm_name'},
  262. 'type'=>$signType,
  263. 'is_auto'=>0
  264. ];
  265. $signer=[
  266. "orgSignerInfo" => [
  267. "orgName" => $firm->{'firm_name'},
  268. 'orgInfo'=>[
  269. 'orgIDCardNum'=>$firm->{'license_number'},
  270. 'orgIDCardType'=>'CRED_ORG_USCC',
  271. ],
  272. "transactorInfo"=>[
  273. "psnAccount"=>$member['phone'],
  274. 'psnInfo'=>[
  275. 'psnIDCardNum'=>$member['promissory_id_number'],
  276. 'psnIDCardType'=>'CRED_PSN_CH_IDCARD'
  277. ]
  278. ]
  279. ],
  280. "signFields" =>[
  281. [
  282. "fileId" =>$fileId,
  283. "normalSignFieldConfig" => [
  284. "autoSign" =>false,
  285. "signFieldStyle" =>1,
  286. "assignedSealId"=>$assignedSealId, // 在signFields中设置 assignedSealId为授权企业印章id
  287. "signFieldPosition" => [
  288. "positionPage" =>$position['pageNum'].'',
  289. "positionX" =>(int)$coordinate['positionX'],
  290. "positionY" =>(int)$coordinate['positionY'],
  291. ]
  292. ]
  293. ]
  294. ],
  295. "signerType" =>1
  296. ];
  297. }
  298. $signers[]=$signer;
  299. }
  300. if(empty($signers)){
  301. $v->update(['status' => 3,'msg'=>'暂无可执行电签印章']);
  302. continue;
  303. }
  304. // dd($signers);
  305. $flowId= $this->createByFile($fileId,$contract['name'].'.pdf',$contract['name'],$signers);
  306. if($flowId){
  307. $v->update(['status' => $isAuto==1?2:1,'flow_id'=>$flowId]);
  308. if($signUrl){
  309. foreach ($signUrl as $item){
  310. // if($item['is_auto']){
  311. // FactoringContractSign::where('contract_id',$v['contract_id'])->where('signatory',$item['type'])->update(['sign_url'=>'','status' => 1]);
  312. // FinancingServer::creatServer()->signFinancingContract($v['factoring_id'],$v['id'],$item['type']);
  313. // }else{
  314. //
  315. // }
  316. $urlRet= $this->getSignUrl($flowId,$item['psnAccount'],$item['orgName']);
  317. if($urlRet){
  318. $content='尊敬的'.$item['promissoryName'].',您有待签署合同《'.$contract['name'].'》,点击签署 '.$urlRet;
  319. SmsServer::creatServer()->sendContent($item['psnAccount'],$content);
  320. FactoringContractSign::where('contract_id',$v['contract_id'])->where('signatory',$item['type'])->update(['sign_url'=>$urlRet]);
  321. }
  322. }
  323. }else{
  324. // $this->downloadFile($flowId);
  325. ContractServer::creatServer()->setContractFile($flowId);
  326. }
  327. }else{
  328. $v->update(['status' => 3,'msg'=>'合同生成失败']);
  329. }
  330. }
  331. }
  332. return $list->count();
  333. }
  334. function getSignUrl($flowId,$psnAccount,$orgName){
  335. EsignLogHelper::printMsg("**********获取合同文件签署链接开始**********");
  336. $config=$this->config;
  337. $apiaddr="/v3/sign-flow/%s/sign-url";
  338. $apiaddr = sprintf($apiaddr,$flowId);
  339. $requestType = HttpEmun::POST;
  340. $data = [
  341. "signFlowId" =>$flowId,
  342. "clientType" =>"ALL",
  343. "needLogin" =>false,
  344. "operator" => [
  345. "psnAccount" =>$psnAccount
  346. ],
  347. 'organization'=>[
  348. 'orgName'=>$orgName
  349. ],
  350. "urlType" =>2
  351. ];
  352. $paramStr = json_encode($data);
  353. $signAndBuildSignAndJsonHeader = EsignHttpHelper::signAndBuildSignAndJsonHeader($config['eSignAppId'], $config['eSignAppSecret'], $paramStr, $requestType, $apiaddr);
  354. EsignLogHelper::printMsg($signAndBuildSignAndJsonHeader);
  355. $response = EsignHttpHelper::doCommHttp($config['eSignHost'], $apiaddr, $requestType, $signAndBuildSignAndJsonHeader, $paramStr);
  356. EsignLogHelper::printMsg($response->getStatus());
  357. EsignLogHelper::printMsg($response->getBody());
  358. $url= null;
  359. if($response->getStatus() == 200){
  360. $url = json_decode($response->getBody())->data->shortUrl;
  361. EsignLogHelper::printMsg("获取合同文件签署链接调用成功,url: ".$url);
  362. }else{
  363. EsignLogHelper::printMsg("获取合同文件签署链接接口调用失败,HTTP错误码".$response->getStatus());
  364. }
  365. EsignLogHelper::printMsg("**********获取合同文件签署链接调用结束**********");
  366. return $url;
  367. }
  368. /**
  369. * 合同签署
  370. * @param $fileId
  371. * @return false
  372. */
  373. function createByFile($fileId,$fileName,$signFlowTitle,$signers)
  374. {
  375. EsignLogHelper::printMsg("**********基于文件发起签署调用开始**********");
  376. $config=$this->config;
  377. $apiaddr="/v3/sign-flow/create-by-file";
  378. $requestType = HttpEmun::POST;
  379. $data = [
  380. "docs" =>[
  381. [
  382. "fileId" =>$fileId,
  383. "fileName" =>$fileName
  384. ]
  385. ],
  386. "signFlowConfig" => [
  387. "signFlowTitle" =>$signFlowTitle,
  388. "autoStart"=>true,
  389. "autoFinish"=>true,
  390. 'notifyUrl'=>route('wps.esignNotify')
  391. ],
  392. "signers"=>$signers
  393. ];
  394. // dd($data);
  395. $paramStr = json_encode($data);
  396. $signAndBuildSignAndJsonHeader = EsignHttpHelper::signAndBuildSignAndJsonHeader($config['eSignAppId'], $config['eSignAppSecret'], $paramStr, $requestType, $apiaddr);
  397. EsignLogHelper::printMsg($signAndBuildSignAndJsonHeader);
  398. $response = EsignHttpHelper::doCommHttp($config['eSignHost'], $apiaddr, $requestType, $signAndBuildSignAndJsonHeader, $paramStr);
  399. EsignLogHelper::printMsg($response->getStatus());
  400. EsignLogHelper::printMsg($response->getBody());
  401. $signFlowId = false;
  402. if($response->getStatus() == 200){
  403. $result = json_decode($response->getBody());
  404. if($result->code==0){
  405. $signFlowId = $result->data->signFlowId;
  406. EsignLogHelper::printMsg("基于文件发起签署接口调用成功,flowId: ".$signFlowId);
  407. }else{
  408. EsignLogHelper::printMsg("基于文件发起签署接口调用失败,错误信息: ".$result->message);
  409. }
  410. }else{
  411. EsignLogHelper::printMsg("基于文件发起签署接口调用失败,HTTP错误码".$response->getStatus());
  412. }
  413. EsignLogHelper::printMsg("**********基于文件发起签署调用结束**********");
  414. return $signFlowId;
  415. }
  416. function downloadFile($flowId){
  417. $config=$this->config;
  418. EsignLogHelper::printMsg("**********下载已签署文件及附属材料开始**********");
  419. $apiaddr="/v3/sign-flow/%s/file-download-url";
  420. $apiaddr = sprintf($apiaddr,$flowId);
  421. $requestType = HttpEmun::GET;
  422. $paramStr = null;
  423. $signAndBuildSignAndJsonHeader = EsignHttpHelper::signAndBuildSignAndJsonHeader($config['eSignAppId'], $config['eSignAppSecret'], $paramStr, $requestType, $apiaddr);
  424. EsignLogHelper::printMsg($signAndBuildSignAndJsonHeader);
  425. $response = EsignHttpHelper::doCommHttp($config['eSignHost'], $apiaddr, $requestType, $signAndBuildSignAndJsonHeader, $paramStr);
  426. EsignLogHelper::printMsg($response->getStatus());
  427. EsignLogHelper::printMsg($response->getBody());
  428. if($response->getStatus() == 200){
  429. EsignLogHelper::printMsg("下载已签署文件及附属材料调用成功: ".$response->getBody());
  430. return json_decode($response->getBody(),true);
  431. }else{
  432. EsignLogHelper::printMsg("下载已签署文件及附属材料调用失败,HTTP错误码".$response->getStatus());
  433. }
  434. EsignLogHelper::printMsg("**********下载已签署文件及附属材料调用结束**********");
  435. return false;
  436. }
  437. }