AopClient.php 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313
  1. <?php
  2. /**
  3. * 重庆赤晓店信息科技有限公司
  4. * https://www.chixiaodian.com
  5. * Copyright (c) 2023 赤店商城 All rights reserved.
  6. */
  7. require_once 'AopEncrypt.php';
  8. require_once 'EncryptParseItem.php';
  9. require_once 'EncryptResponseData.php';
  10. require_once 'SignData.php';
  11. class AopClient
  12. {
  13. //应用ID
  14. public $appId;
  15. //私钥文件路径
  16. public $rsaPrivateKeyFilePath;
  17. //私钥值
  18. public $rsaPrivateKey;
  19. //网关
  20. public $gatewayUrl = "https://openapi.alipay.com/gateway.do";
  21. //返回数据格式
  22. public $format = "json";
  23. //api版本
  24. public $apiVersion = "1.0";
  25. // 表单提交字符集编码
  26. public $postCharset = "UTF-8";
  27. //使用文件读取文件格式,请只传递该值
  28. public $alipayPublicKey = null;
  29. //使用读取字符串格式,请只传递该值
  30. public $alipayrsaPublicKey;
  31. public $debugInfo = false;
  32. private $fileCharset = "UTF-8";
  33. private $RESPONSE_SUFFIX = "_response";
  34. private $ERROR_RESPONSE = "error_response";
  35. private $SIGN_NODE_NAME = "sign";
  36. //加密XML节点名称
  37. private $ENCRYPT_XML_NODE_NAME = "response_encrypted";
  38. private $needEncrypt = false;
  39. //签名类型
  40. public $signType = "RSA";
  41. //加密密钥和类型
  42. public $encryptKey;
  43. public $encryptType = "AES";
  44. private $targetServiceUrl = "";
  45. protected $alipaySdkVersion = "alipay-sdk-PHP-4.11.14.ALL";
  46. public function generateSign($params, $signType = "RSA")
  47. {
  48. $params = array_filter($params);
  49. $params['sign_type'] = $signType;
  50. return $this->sign($this->getSignContent($params), $signType);
  51. }
  52. public function rsaSign($params, $signType = "RSA")
  53. {
  54. return $this->sign($this->getSignContent($params), $signType);
  55. }
  56. public function getSignContent($params)
  57. {
  58. ksort($params);
  59. unset($params['sign']);
  60. $stringToBeSigned = "";
  61. $i = 0;
  62. foreach ($params as $k => $v) {
  63. if ("@" != substr($v, 0, 1)) {
  64. // 转换成目标字符集
  65. $v = $this->characet($v, $this->postCharset);
  66. if ($i == 0) {
  67. $stringToBeSigned .= "$k" . "=" . "$v";
  68. } else {
  69. $stringToBeSigned .= "&" . "$k" . "=" . "$v";
  70. }
  71. $i++;
  72. }
  73. }
  74. unset ($k, $v);
  75. Yii::error($stringToBeSigned);
  76. return $stringToBeSigned;
  77. }
  78. //此方法对value做urlencode
  79. public function getSignContentUrlencode($params)
  80. {
  81. ksort($params);
  82. $stringToBeSigned = "";
  83. $i = 0;
  84. foreach ($params as $k => $v) {
  85. if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
  86. // 转换成目标字符集
  87. $v = $this->characet($v, $this->postCharset);
  88. if ($i == 0) {
  89. $stringToBeSigned .= "$k" . "=" . urlencode($v);
  90. } else {
  91. $stringToBeSigned .= "&" . "$k" . "=" . urlencode($v);
  92. }
  93. $i++;
  94. }
  95. }
  96. unset ($k, $v);
  97. return $stringToBeSigned;
  98. }
  99. protected function sign($data, $signType = "RSA")
  100. {
  101. if ($this->checkEmpty($this->rsaPrivateKeyFilePath)) {
  102. $priKey = $this->rsaPrivateKey;
  103. $res = "-----BEGIN RSA PRIVATE KEY-----\n" .
  104. wordwrap($priKey, 64, "\n", true) .
  105. "\n-----END RSA PRIVATE KEY-----";
  106. } else {
  107. $priKey = file_get_contents($this->rsaPrivateKeyFilePath);
  108. $res = openssl_get_privatekey($priKey);
  109. }
  110. ($res) or die('您使用的私钥格式错误,请检查RSA私钥配置');
  111. if ("RSA2" == $signType) {
  112. openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);
  113. } else {
  114. openssl_sign($data, $sign, $res);
  115. }
  116. if (!$this->checkEmpty($this->rsaPrivateKeyFilePath)) {
  117. openssl_free_key($res);
  118. }
  119. $sign = base64_encode($sign);
  120. return $sign;
  121. }
  122. /**
  123. * RSA单独签名方法,未做字符串处理,字符串处理见getSignContent()
  124. * @param $data 待签名字符串
  125. * @param $privatekey 商户私钥,根据keyfromfile来判断是读取字符串还是读取文件,false:填写私钥字符串去回车和空格 true:填写私钥文件路径
  126. * @param $signType 签名方式,RSA:SHA1 RSA2:SHA256
  127. * @param $keyfromfile 私钥获取方式,读取字符串还是读文件
  128. * @return string
  129. */
  130. public function alonersaSign($data, $privatekey, $signType = "RSA", $keyfromfile = false)
  131. {
  132. if (!$keyfromfile) {
  133. $priKey = $privatekey;
  134. $res = "-----BEGIN RSA PRIVATE KEY-----\n" .
  135. wordwrap($priKey, 64, "\n", true) .
  136. "\n-----END RSA PRIVATE KEY-----";
  137. } else {
  138. $priKey = file_get_contents($privatekey);
  139. $res = openssl_get_privatekey($priKey);
  140. }
  141. ($res) or die('您使用的私钥格式错误,请检查RSA私钥配置');
  142. if ("RSA2" == $signType) {
  143. openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);
  144. } else {
  145. openssl_sign($data, $sign, $res);
  146. }
  147. if ($keyfromfile) {
  148. openssl_free_key($res);
  149. }
  150. $sign = base64_encode($sign);
  151. return $sign;
  152. }
  153. protected function curl($url, $postFields = null)
  154. {
  155. $ch = curl_init();
  156. curl_setopt($ch, CURLOPT_URL, $url);
  157. curl_setopt($ch, CURLOPT_FAILONERROR, false);
  158. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  159. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  160. $postBodyString = "";
  161. $encodeArray = Array();
  162. $postMultipart = false;
  163. Yii::error($postFields);
  164. if (is_array($postFields) && 0 < count($postFields)) {
  165. foreach ($postFields as $k => $v) {
  166. if ("@" != substr($v, 0, 1)) //判断是不是文件上传
  167. {
  168. $postBodyString .= "$k=" . urlencode($this->characet($v, $this->postCharset)) . "&";
  169. $encodeArray[$k] = $this->characet($v, $this->postCharset);
  170. } else //文件上传用multipart/form-data,否则用www-form-urlencoded
  171. {
  172. $postMultipart = true;
  173. $encodeArray[$k] = new \CURLFile(substr($v, 1));
  174. }
  175. }
  176. Yii::error($encodeArray);
  177. unset ($k, $v);
  178. curl_setopt($ch, CURLOPT_POST, true);
  179. if ($postMultipart) {
  180. curl_setopt($ch, CURLOPT_POSTFIELDS, $encodeArray);
  181. } else {
  182. curl_setopt($ch, CURLOPT_POSTFIELDS, substr($postBodyString, 0, -1));
  183. }
  184. }
  185. if (!$postMultipart) {
  186. $headers = array('content-type: application/x-www-form-urlencoded;charset=' . $this->postCharset);
  187. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  188. }
  189. $reponse = curl_exec($ch);
  190. if (curl_errno($ch)) {
  191. throw new Exception(curl_error($ch), 0);
  192. } else {
  193. $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  194. if (200 !== $httpStatusCode) {
  195. throw new Exception($reponse, $httpStatusCode);
  196. }
  197. }
  198. curl_close($ch);
  199. return $reponse;
  200. }
  201. protected function getMillisecond()
  202. {
  203. list($s1, $s2) = explode(' ', microtime());
  204. return (float)sprintf('%.0f', (floatval($s1) + floatval($s2)) * 1000);
  205. }
  206. protected function logCommunicationError($apiName, $requestUrl, $errorCode, $responseTxt)
  207. {
  208. $logData = array(
  209. date("Y-m-d H:i:s"),
  210. $apiName,
  211. $this->appId,
  212. PHP_OS,
  213. $this->alipaySdkVersion,
  214. $requestUrl,
  215. $errorCode,
  216. str_replace("\n", "", $responseTxt)
  217. );
  218. echo json_encode($logData);
  219. }
  220. /**
  221. * 生成用于调用收银台SDK的字符串
  222. * @param $request SDK接口的请求参数对象
  223. * @param $appAuthToken 三方应用授权token
  224. * @return string
  225. */
  226. public function sdkExecute($request, $appAuthToken = null)
  227. {
  228. $this->setupCharsets($request);
  229. $params['app_id'] = $this->appId;
  230. $params['method'] = $request->getApiMethodName();
  231. $params['format'] = $this->format;
  232. $params['sign_type'] = $this->signType;
  233. $params['timestamp'] = date("Y-m-d H:i:s");
  234. $params['alipay_sdk'] = $this->alipaySdkVersion;
  235. $params['charset'] = $this->postCharset;
  236. $version = $request->getApiVersion();
  237. $params['version'] = $this->checkEmpty($version) ? $this->apiVersion : $version;
  238. if ($notify_url = $request->getNotifyUrl()) {
  239. $params['notify_url'] = $notify_url;
  240. }
  241. $params['app_auth_token'] = $appAuthToken;
  242. $dict = $request->getApiParas();
  243. $params['biz_content'] = $dict['biz_content'];
  244. ksort($params);
  245. $params['sign'] = $this->generateSign($params, $this->signType);
  246. foreach ($params as &$value) {
  247. $value = $this->characet($value, $params['charset']);
  248. }
  249. return http_build_query($params);
  250. }
  251. /**
  252. * 页面提交执行方法
  253. * @param $request 跳转类接口的request
  254. * @param string $httpmethod 提交方式,两个值可选:post、get;
  255. * @param null $appAuthToken 三方应用授权token
  256. * @return 构建好的、签名后的最终跳转URL(GET)或String形式的form(POST)
  257. * @throws Exception
  258. */
  259. public function pageExecute($request, $httpmethod = "POST", $appAuthToken = null)
  260. {
  261. $this->setupCharsets($request);
  262. if (strcasecmp($this->fileCharset, $this->postCharset)) {
  263. // writeLog("本地文件字符集编码与表单提交编码不一致,请务必设置成一样,属性名分别为postCharset!");
  264. throw new Exception("文件编码:[" . $this->fileCharset . "] 与表单提交编码:[" . $this->postCharset . "]两者不一致!");
  265. }
  266. $iv = null;
  267. if (!$this->checkEmpty($request->getApiVersion())) {
  268. $iv = $request->getApiVersion();
  269. } else {
  270. $iv = $this->apiVersion;
  271. }
  272. //组装系统参数
  273. $sysParams["app_id"] = $this->appId;
  274. $sysParams["version"] = $iv;
  275. $sysParams["format"] = $this->format;
  276. $sysParams["sign_type"] = $this->signType;
  277. $sysParams["method"] = $request->getApiMethodName();
  278. $sysParams["timestamp"] = date("Y-m-d H:i:s");
  279. $sysParams["alipay_sdk"] = $this->alipaySdkVersion;
  280. if (!$this->checkEmpty($request->getTerminalType())) {
  281. $sysParams["terminal_type"] = $request->getTerminalType();
  282. }
  283. if (!$this->checkEmpty($request->getTerminalInfo())) {
  284. $sysParams["terminal_info"] = $request->getTerminalInfo();
  285. }
  286. if (!$this->checkEmpty($request->getProdCode())) {
  287. $sysParams["prod_code"] = $request->getProdCode();
  288. }
  289. if (!$this->checkEmpty($request->getNotifyUrl())) {
  290. $sysParams["notify_url"] = $request->getNotifyUrl();
  291. }
  292. if (!$this->checkEmpty($request->getReturnUrl())) {
  293. $sysParams["return_url"] = $request->getReturnUrl();
  294. }
  295. $sysParams["charset"] = $this->postCharset;
  296. if (!$this->checkEmpty($appAuthToken)) {
  297. $sysParams["app_auth_token"] = $appAuthToken;
  298. }
  299. //获取业务参数
  300. $apiParams = $request->getApiParas();
  301. if (method_exists($request, "getNeedEncrypt") && $request->getNeedEncrypt()) {
  302. $sysParams["encrypt_type"] = $this->encryptType;
  303. if ($this->checkEmpty($apiParams['biz_content'])) {
  304. throw new Exception(" api request Fail! The reason : encrypt request is not supperted!");
  305. }
  306. if ($this->checkEmpty($this->encryptKey) || $this->checkEmpty($this->encryptType)) {
  307. throw new Exception(" encryptType and encryptKey must not null! ");
  308. }
  309. if ("AES" != $this->encryptType) {
  310. throw new Exception("加密类型只支持AES");
  311. }
  312. // 执行加密
  313. $enCryptContent = encrypt($apiParams['biz_content'], $this->encryptKey);
  314. $apiParams['biz_content'] = $enCryptContent;
  315. }
  316. //print_r($apiParams);
  317. $totalParams = array_merge($apiParams, $sysParams);
  318. //待签名字符串
  319. $preSignStr = $this->getSignContent($totalParams);
  320. //签名
  321. $totalParams["sign"] = $this->generateSign($totalParams, $this->signType);
  322. if ("GET" == strtoupper($httpmethod)) {
  323. //value做urlencode
  324. $preString = $this->getSignContentUrlencode($totalParams);
  325. //拼接GET请求串
  326. $requestUrl = $this->gatewayUrl . "?" . $preString;
  327. return $requestUrl;
  328. } else {
  329. //拼接表单字符串
  330. return $this->buildRequestForm($totalParams);
  331. }
  332. }
  333. /**
  334. * 建立请求,以表单HTML形式构造(默认)
  335. * @param $para_temp 请求参数数组
  336. * @return 提交表单HTML文本
  337. */
  338. protected function buildRequestForm($para_temp)
  339. {
  340. $sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='" . $this->gatewayUrl . "?charset=" . trim($this->postCharset) . "' method='POST'>";
  341. while (list ($key, $val) = $this->fun_adm_each($para_temp)) {
  342. if (false === $this->checkEmpty($val)) {
  343. //$val = $this->characet($val, $this->postCharset);
  344. $val = str_replace("'", "&apos;", $val);
  345. //$val = str_replace("\"","&quot;",$val);
  346. $sHtml .= "<input type='hidden' name='" . $key . "' value='" . $val . "'/>";
  347. }
  348. }
  349. //submit按钮控件请不要含有name属性
  350. $sHtml = $sHtml . "<input type='submit' value='ok' style='display:none;''></form>";
  351. $sHtml = $sHtml . "<script>document.forms['alipaysubmit'].submit();</script>";
  352. return $sHtml;
  353. }
  354. protected function fun_adm_each(&$array)
  355. {
  356. $res = array();
  357. $key = key($array);
  358. if ($key !== null) {
  359. next($array);
  360. $res[1] = $res['value'] = $array[$key];
  361. $res[0] = $res['key'] = $key;
  362. } else {
  363. $res = false;
  364. }
  365. return $res;
  366. }
  367. public function execute($request, $authToken = null, $appInfoAuthtoken = null, $targetAppId = null)
  368. {
  369. $this->setupCharsets($request);
  370. //如果两者编码不一致,会出现签名验签或者乱码
  371. if (strcasecmp($this->fileCharset, $this->postCharset)) {
  372. // writeLog("本地文件字符集编码与表单提交编码不一致,请务必设置成一样,属性名分别为postCharset!");
  373. throw new Exception("文件编码:[" . $this->fileCharset . "] 与表单提交编码:[" . $this->postCharset . "]两者不一致!");
  374. }
  375. $iv = null;
  376. if (!$this->checkEmpty($request->getApiVersion())) {
  377. $iv = $request->getApiVersion();
  378. } else {
  379. $iv = $this->apiVersion;
  380. }
  381. //组装系统参数
  382. $sysParams["app_id"] = $this->appId;
  383. $sysParams["version"] = $iv;
  384. $sysParams["format"] = $this->format;
  385. $sysParams["sign_type"] = $this->signType;
  386. $sysParams["method"] = $request->getApiMethodName();
  387. $sysParams["timestamp"] = date("Y-m-d H:i:s");
  388. if (!$this->checkEmpty($authToken)) {
  389. $sysParams["auth_token"] = $authToken;
  390. }
  391. $sysParams["alipay_sdk"] = $this->alipaySdkVersion;
  392. if (!$this->checkEmpty($request->getTerminalType())) {
  393. $sysParams["terminal_type"] = $request->getTerminalType();
  394. }
  395. if (!$this->checkEmpty($request->getTerminalInfo())) {
  396. $sysParams["terminal_info"] = $request->getTerminalInfo();
  397. }
  398. if (!$this->checkEmpty($request->getProdCode())) {
  399. $sysParams["prod_code"] = $request->getProdCode();
  400. }
  401. if (!$this->checkEmpty($request->getNotifyUrl())) {
  402. $sysParams["notify_url"] = $request->getNotifyUrl();
  403. }
  404. $sysParams["charset"] = $this->postCharset;
  405. if (!$this->checkEmpty($appInfoAuthtoken)) {
  406. $sysParams["app_auth_token"] = $appInfoAuthtoken;
  407. }
  408. if (!$this->checkEmpty($targetAppId)) {
  409. $sysParams["target_app_id"] = $targetAppId;
  410. }
  411. if (!$this->checkEmpty($this->targetServiceUrl)) {
  412. $sysParams["ws_service_url"] = $this->targetServiceUrl;
  413. }
  414. //获取业务参数
  415. $apiParams = $request->getApiParas();
  416. if (method_exists($request, "getNeedEncrypt") && $request->getNeedEncrypt()) {
  417. $sysParams["encrypt_type"] = $this->encryptType;
  418. if ($this->checkEmpty($apiParams['biz_content'])) {
  419. throw new Exception(" api request Fail! The reason : encrypt request is not supperted!");
  420. }
  421. if ($this->checkEmpty($this->encryptKey) || $this->checkEmpty($this->encryptType)) {
  422. throw new Exception(" encryptType and encryptKey must not null! ");
  423. }
  424. if ("AES" != $this->encryptType) {
  425. throw new Exception("加密类型只支持AES");
  426. }
  427. // 执行加密
  428. $enCryptContent = encrypt($apiParams['biz_content'], $this->encryptKey);
  429. $apiParams['biz_content'] = $enCryptContent;
  430. }
  431. //签名
  432. $sysParams["sign"] = $this->generateSign(array_merge($apiParams, $sysParams), $this->signType);
  433. //系统参数放入GET请求串
  434. $requestUrl = $this->gatewayUrl . "?";
  435. foreach ($sysParams as $sysParamKey => $sysParamValue) {
  436. if ($sysParamValue != null) {
  437. $requestUrl .= "$sysParamKey=" . urlencode($this->characet($sysParamValue, $this->postCharset)) . "&";
  438. }
  439. }
  440. $requestUrl = substr($requestUrl, 0, -1);
  441. //发起HTTP请求
  442. try {
  443. $resp = $this->curl($requestUrl, $apiParams);
  444. } catch (Exception $e) {
  445. $this->logCommunicationError($sysParams["method"], $requestUrl, "HTTP_ERROR_" . $e->getCode(), $e->getMessage());
  446. return false;
  447. }
  448. //解析AOP返回结果
  449. $respWellFormed = false;
  450. // 将返回结果转换本地文件编码
  451. $r = iconv($this->postCharset, $this->fileCharset . "//IGNORE", $resp);
  452. $signData = null;
  453. if ("json" == strtolower($this->format)) {
  454. $respObject = json_decode($r);
  455. if (null !== $respObject) {
  456. $respWellFormed = true;
  457. $signData = $this->parserJSONSignData($request, $resp, $respObject);
  458. }
  459. } else if ("xml" == $this->format) {
  460. $disableLibxmlEntityLoader = libxml_disable_entity_loader(true);
  461. $respObject = @ simplexml_load_string($resp);
  462. if (false !== $respObject) {
  463. $respWellFormed = true;
  464. $signData = $this->parserXMLSignData($request, $resp);
  465. }
  466. libxml_disable_entity_loader($disableLibxmlEntityLoader);
  467. }
  468. //返回的HTTP文本不是标准JSON或者XML,记下错误日志
  469. if (false === $respWellFormed) {
  470. $this->logCommunicationError($sysParams["method"], $requestUrl, "HTTP_RESPONSE_NOT_WELL_FORMED", $resp);
  471. return false;
  472. }
  473. // 验签
  474. $this->checkResponseSign($request, $signData, $resp, $respObject);
  475. // 解密
  476. if (method_exists($request, "getNeedEncrypt") && $request->getNeedEncrypt()) {
  477. if ("json" == $this->format) {
  478. $resp = $this->encryptJSONSignSource($request, $resp);
  479. // 将返回结果转换本地文件编码
  480. $r = iconv($this->postCharset, $this->fileCharset . "//IGNORE", $resp);
  481. $respObject = json_decode($r);
  482. } else {
  483. $resp = $this->encryptXMLSignSource($request, $resp);
  484. $r = iconv($this->postCharset, $this->fileCharset . "//IGNORE", $resp);
  485. $disableLibxmlEntityLoader = libxml_disable_entity_loader(true);
  486. $respObject = @ simplexml_load_string($r);
  487. libxml_disable_entity_loader($disableLibxmlEntityLoader);
  488. }
  489. }
  490. return $respObject;
  491. }
  492. /**
  493. * 转换字符集编码
  494. * @param $data
  495. * @param $targetCharset
  496. * @return string
  497. */
  498. function characet($data, $targetCharset)
  499. {
  500. if (!empty($data)) {
  501. $fileType = $this->fileCharset;
  502. if (strcasecmp($fileType, $targetCharset) != 0) {
  503. $data = mb_convert_encoding($data, $targetCharset, $fileType);
  504. // $data = iconv($fileType, $targetCharset.'//IGNORE', $data);
  505. }
  506. }
  507. return $data;
  508. }
  509. public function exec($paramsArray)
  510. {
  511. if (!isset ($paramsArray["method"])) {
  512. trigger_error("No api name passed");
  513. }
  514. $inflector = new LtInflector;
  515. $inflector->conf["separator"] = ".";
  516. $requestClassName = ucfirst($inflector->camelize(substr($paramsArray["method"], 7))) . "Request";
  517. if (!class_exists($requestClassName)) {
  518. trigger_error("No such api: " . $paramsArray["method"]);
  519. }
  520. $session = isset ($paramsArray["session"]) ? $paramsArray["session"] : null;
  521. $req = new $requestClassName;
  522. foreach ($paramsArray as $paraKey => $paraValue) {
  523. $inflector->conf["separator"] = "_";
  524. $setterMethodName = $inflector->camelize($paraKey);
  525. $inflector->conf["separator"] = ".";
  526. $setterMethodName = "set" . $inflector->camelize($setterMethodName);
  527. if (method_exists($req, $setterMethodName)) {
  528. $req->$setterMethodName ($paraValue);
  529. }
  530. }
  531. return $this->execute($req, $session);
  532. }
  533. /**
  534. * 校验$value是否非空
  535. * if not set ,return true;
  536. * if is null , return true;
  537. **/
  538. protected function checkEmpty($value)
  539. {
  540. if (!isset($value))
  541. return true;
  542. if ($value === null)
  543. return true;
  544. if (trim($value) === "")
  545. return true;
  546. return false;
  547. }
  548. /** rsaCheckV1 & rsaCheckV2
  549. * 验证签名
  550. * 在使用本方法前,必须初始化AopClient且传入公钥参数。
  551. * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。
  552. **/
  553. public function rsaCheckV1($params, $rsaPublicKeyFilePath, $signType = 'RSA')
  554. {
  555. $sign = $params['sign'];
  556. unset($params['sign']);
  557. unset($params['sign_type']);
  558. return $this->verify($this->getSignContent($params), $sign, $rsaPublicKeyFilePath, $signType);
  559. }
  560. public function rsaCheckV2($params, $rsaPublicKeyFilePath, $signType = 'RSA')
  561. {
  562. $sign = $params['sign'];
  563. unset($params['sign']);
  564. return $this->verify($this->getSignContent($params), $sign, $rsaPublicKeyFilePath, $signType);
  565. }
  566. function verify($data, $sign, $rsaPublicKeyFilePath, $signType = 'RSA')
  567. {
  568. if ($this->checkEmpty($this->alipayPublicKey)) {
  569. $pubKey = $this->alipayrsaPublicKey;
  570. $res = "-----BEGIN PUBLIC KEY-----\n" .
  571. wordwrap($pubKey, 64, "\n", true) .
  572. "\n-----END PUBLIC KEY-----";
  573. } else {
  574. //读取公钥文件
  575. $pubKey = file_get_contents($rsaPublicKeyFilePath);
  576. //转换为openssl格式密钥
  577. $res = openssl_get_publickey($pubKey);
  578. }
  579. ($res) or die('支付宝RSA公钥错误。请检查公钥文件格式是否正确');
  580. //调用openssl内置方法验签,返回bool值
  581. $result = FALSE;
  582. if ("RSA2" == $signType) {
  583. $result = (openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256) === 1);
  584. } else {
  585. $result = (openssl_verify($data, base64_decode($sign), $res) === 1);
  586. }
  587. if (!$this->checkEmpty($this->alipayPublicKey)) {
  588. //释放资源
  589. openssl_free_key($res);
  590. }
  591. return $result;
  592. }
  593. /**
  594. * 在使用本方法前,必须初始化AopClient且传入公私钥参数。
  595. * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。
  596. **/
  597. public function checkSignAndDecrypt($params, $rsaPublicKeyPem, $rsaPrivateKeyPem, $isCheckSign, $isDecrypt, $signType = 'RSA')
  598. {
  599. $charset = $params['charset'];
  600. $bizContent = $params['biz_content'];
  601. if ($isCheckSign) {
  602. if (!$this->rsaCheckV2($params, $rsaPublicKeyPem, $signType)) {
  603. echo "<br/>checkSign failure<br/>";
  604. exit;
  605. }
  606. }
  607. if ($isDecrypt) {
  608. return $this->rsaDecrypt($bizContent, $rsaPrivateKeyPem, $charset);
  609. }
  610. return $bizContent;
  611. }
  612. /**
  613. * 在使用本方法前,必须初始化AopClient且传入公私钥参数。
  614. * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。
  615. **/
  616. public function encryptAndSign($bizContent, $rsaPublicKeyPem, $rsaPrivateKeyPem, $charset, $isEncrypt, $isSign, $signType = 'RSA')
  617. {
  618. // 加密,并签名
  619. if ($isEncrypt && $isSign) {
  620. $encrypted = $this->rsaEncrypt($bizContent, $rsaPublicKeyPem, $charset);
  621. $sign = $this->sign($encrypted, $signType);
  622. $response = "<?xml version=\"1.0\" encoding=\"$charset\"?><alipay><response>$encrypted</response><encryption_type>RSA</encryption_type><sign>$sign</sign><sign_type>$signType</sign_type></alipay>";
  623. return $response;
  624. }
  625. // 加密,不签名
  626. if ($isEncrypt && (!$isSign)) {
  627. $encrypted = $this->rsaEncrypt($bizContent, $rsaPublicKeyPem, $charset);
  628. $response = "<?xml version=\"1.0\" encoding=\"$charset\"?><alipay><response>$encrypted</response><encryption_type>$signType</encryption_type></alipay>";
  629. return $response;
  630. }
  631. // 不加密,但签名
  632. if ((!$isEncrypt) && $isSign) {
  633. $sign = $this->sign($bizContent, $signType);
  634. $response = "<?xml version=\"1.0\" encoding=\"$charset\"?><alipay><response>$bizContent</response><sign>$sign</sign><sign_type>$signType</sign_type></alipay>";
  635. return $response;
  636. }
  637. // 不加密,不签名
  638. $response = "<?xml version=\"1.0\" encoding=\"$charset\"?>$bizContent";
  639. return $response;
  640. }
  641. /**
  642. * 在使用本方法前,必须初始化AopClient且传入公私钥参数。
  643. * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。
  644. **/
  645. public function rsaEncrypt($data, $rsaPublicKeyFilePath, $charset)
  646. {
  647. if ($this->checkEmpty($this->alipayPublicKey)) {
  648. //读取字符串
  649. $pubKey = $this->alipayrsaPublicKey;
  650. $res = "-----BEGIN PUBLIC KEY-----\n" .
  651. wordwrap($pubKey, 64, "\n", true) .
  652. "\n-----END PUBLIC KEY-----";
  653. } else {
  654. //读取公钥文件
  655. $pubKey = file_get_contents($rsaPublicKeyFilePath);
  656. //转换为openssl格式密钥
  657. $res = openssl_get_publickey($pubKey);
  658. }
  659. ($res) or die('支付宝RSA公钥错误。请检查公钥文件格式是否正确');
  660. $blocks = $this->splitCN($data, 0, 30, $charset);
  661. $chrtext = null;
  662. $encodes = array();
  663. foreach ($blocks as $n => $block) {
  664. if (!openssl_public_encrypt($block, $chrtext , $res)) {
  665. echo "<br/>" . openssl_error_string() . "<br/>";
  666. }
  667. $encodes[] = $chrtext ;
  668. }
  669. $chrtext = implode(",", $encodes);
  670. return base64_encode($chrtext);
  671. }
  672. /**
  673. * 在使用本方法前,必须初始化AopClient且传入公私钥参数。
  674. * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。
  675. **/
  676. public function rsaDecrypt($data, $rsaPrivateKeyPem, $charset)
  677. {
  678. if ($this->checkEmpty($this->rsaPrivateKeyFilePath)) {
  679. //读字符串
  680. $priKey = $this->rsaPrivateKey;
  681. $res = "-----BEGIN RSA PRIVATE KEY-----\n" .
  682. wordwrap($priKey, 64, "\n", true) .
  683. "\n-----END RSA PRIVATE KEY-----";
  684. } else {
  685. $priKey = file_get_contents($this->rsaPrivateKeyFilePath);
  686. $res = openssl_get_privatekey($priKey);
  687. }
  688. ($res) or die('您使用的私钥格式错误,请检查RSA私钥配置');
  689. //转换为openssl格式密钥
  690. $decodes = explode(',', $data);
  691. $strnull = "";
  692. $dcyCont = "";
  693. foreach ($decodes as $n => $decode) {
  694. if (!openssl_private_decrypt($decode, $dcyCont, $res)) {
  695. echo "<br/>" . openssl_error_string() . "<br/>";
  696. }
  697. $strnull .= $dcyCont;
  698. }
  699. return $strnull;
  700. }
  701. function splitCN($cont, $n = 0, $subnum, $charset)
  702. {
  703. //$len = strlen($cont) / 3;
  704. $arrr = array();
  705. for ($i = $n; $i < strlen($cont); $i += $subnum) {
  706. $res = $this->subCNchar($cont, $i, $subnum, $charset);
  707. if (!empty ($res)) {
  708. $arrr[] = $res;
  709. }
  710. }
  711. return $arrr;
  712. }
  713. function subCNchar($str, $start = 0, $length, $charset = "gbk")
  714. {
  715. if (strlen($str) <= $length) {
  716. return $str;
  717. }
  718. $re['utf-8'] = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}/";
  719. $re['gb2312'] = "/[\x01-\x7f]|[\xb0-\xf7][\xa0-\xfe]/";
  720. $re['gbk'] = "/[\x01-\x7f]|[\x81-\xfe][\x40-\xfe]/";
  721. $re['big5'] = "/[\x01-\x7f]|[\x81-\xfe]([\x40-\x7e]|\xa1-\xfe])/";
  722. preg_match_all($re[$charset], $str, $match);
  723. $slice = join("", array_slice($match[0], $start, $length));
  724. return $slice;
  725. }
  726. function parserResponseSubCode($request, $responseContent, $respObject, $format)
  727. {
  728. if ("json" == $format) {
  729. $apiName = $request->getApiMethodName();
  730. $rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX;
  731. $errorNodeName = $this->ERROR_RESPONSE;
  732. $rootIndex = strpos($responseContent, $rootNodeName);
  733. $errorIndex = strpos($responseContent, $errorNodeName);
  734. if ($rootIndex > 0) {
  735. // 内部节点对象
  736. $rInnerObject = $respObject->$rootNodeName;
  737. } elseif ($errorIndex > 0) {
  738. $rInnerObject = $respObject->$errorNodeName;
  739. } else {
  740. return null;
  741. }
  742. // 存在属性则返回对应值
  743. if (isset($rInnerObject->sub_code)) {
  744. return $rInnerObject->sub_code;
  745. } else {
  746. return null;
  747. }
  748. } elseif ("xml" == $format) {
  749. // xml格式sub_code在同一层级
  750. return $respObject->sub_code;
  751. }
  752. }
  753. function parserJSONSignData($request, $responseContent, $responseJSON)
  754. {
  755. $signData = new SignData();
  756. $signData->sign = $this->parserJSONSign($responseJSON);
  757. $signData->signSourceData = $this->parserJSONSignSource($request, $responseContent);
  758. return $signData;
  759. }
  760. function parserJSONSignSource($request, $responseContent)
  761. {
  762. $apiName = $request->getApiMethodName();
  763. $rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX;
  764. $rootIndex = strpos($responseContent, $rootNodeName);
  765. $errorIndex = strpos($responseContent, $this->ERROR_RESPONSE);
  766. if ($rootIndex > 0) {
  767. return $this->parserJSONSource($responseContent, $rootNodeName, $rootIndex);
  768. } else if ($errorIndex > 0) {
  769. return $this->parserJSONSource($responseContent, $this->ERROR_RESPONSE, $errorIndex);
  770. } else {
  771. return null;
  772. }
  773. }
  774. function parserJSONSource($responseContent, $nodeName, $nodeIndex)
  775. {
  776. $signDataStartIndex = $nodeIndex + strlen($nodeName) + 2;
  777. $signIndex = strrpos($responseContent, "\"" . $this->SIGN_NODE_NAME . "\"");
  778. // 签名前-逗号
  779. $signDataEndIndex = $signIndex - 1;
  780. $indexLen = $signDataEndIndex - $signDataStartIndex;
  781. if ($indexLen < 0) {
  782. return null;
  783. }
  784. return substr($responseContent, $signDataStartIndex, $indexLen);
  785. }
  786. function parserJSONSign($responseJSon)
  787. {
  788. return $responseJSon->sign;
  789. }
  790. function parserXMLSignData($request, $responseContent)
  791. {
  792. $signData = new SignData();
  793. $signData->sign = $this->parserXMLSign($responseContent);
  794. $signData->signSourceData = $this->parserXMLSignSource($request, $responseContent);
  795. return $signData;
  796. }
  797. function parserXMLSignSource($request, $responseContent)
  798. {
  799. $apiName = $request->getApiMethodName();
  800. $rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX;
  801. $rootIndex = strpos($responseContent, $rootNodeName);
  802. $errorIndex = strpos($responseContent, $this->ERROR_RESPONSE);
  803. // $this->echoDebug("<br/>rootNodeName:" . $rootNodeName);
  804. // $this->echoDebug("<br/> responseContent:<xmp>" . $responseContent . "</xmp>");
  805. if ($rootIndex > 0) {
  806. return $this->parserXMLSource($responseContent, $rootNodeName, $rootIndex);
  807. } else if ($errorIndex > 0) {
  808. return $this->parserXMLSource($responseContent, $this->ERROR_RESPONSE, $errorIndex);
  809. } else {
  810. return null;
  811. }
  812. }
  813. function parserXMLSource($responseContent, $nodeName, $nodeIndex)
  814. {
  815. $signDataStartIndex = $nodeIndex + strlen($nodeName) + 1;
  816. $signIndex = strrpos($responseContent, "<" . $this->SIGN_NODE_NAME . ">");
  817. // 签名前-逗号
  818. $signDataEndIndex = $signIndex - 1;
  819. $indexLen = $signDataEndIndex - $signDataStartIndex + 1;
  820. if ($indexLen < 0) {
  821. return null;
  822. }
  823. return substr($responseContent, $signDataStartIndex, $indexLen);
  824. }
  825. function parserXMLSign($responseContent)
  826. {
  827. $signNodeName = "<" . $this->SIGN_NODE_NAME . ">";
  828. $signEndNodeName = "</" . $this->SIGN_NODE_NAME . ">";
  829. $indexOfSignNode = strpos($responseContent, $signNodeName);
  830. $indexOfSignEndNode = strpos($responseContent, $signEndNodeName);
  831. if ($indexOfSignNode < 0 || $indexOfSignEndNode < 0) {
  832. return null;
  833. }
  834. $nodeIndex = ($indexOfSignNode + strlen($signNodeName));
  835. $indexLen = $indexOfSignEndNode - $nodeIndex;
  836. if ($indexLen < 0) {
  837. return null;
  838. }
  839. // 签名
  840. return substr($responseContent, $nodeIndex, $indexLen);
  841. }
  842. /**
  843. * 验签
  844. * @param $request
  845. * @param $signData
  846. * @param $resp
  847. * @param $respObject
  848. * @throws Exception
  849. */
  850. public function checkResponseSign($request, $signData, $resp, $respObject)
  851. {
  852. if (!$this->checkEmpty($this->alipayPublicKey) || !$this->checkEmpty($this->alipayrsaPublicKey)) {
  853. if ($signData == null || $this->checkEmpty($signData->sign) || $this->checkEmpty($signData->signSourceData)) {
  854. throw new Exception(" check sign Fail! The reason : signData is Empty");
  855. }
  856. // 获取结果sub_code
  857. $responseSubCode = $this->parserResponseSubCode($request, $resp, $respObject, $this->format);
  858. if (!$this->checkEmpty($responseSubCode) || ($this->checkEmpty($responseSubCode) && !$this->checkEmpty($signData->sign))) {
  859. $checkResult = $this->verify($signData->signSourceData, $signData->sign, $this->alipayPublicKey, $this->signType);
  860. if (!$checkResult) {
  861. if (strpos($signData->signSourceData, "\\/") > 0) {
  862. $signData->signSourceData = str_replace("\\/", "/", $signData->signSourceData);
  863. $checkResult = $this->verify($signData->signSourceData, $signData->sign, $this->alipayPublicKey, $this->signType);
  864. if (!$checkResult) {
  865. throw new Exception("check sign Fail! [sign=" . $signData->sign . ", signSourceData=" . $signData->signSourceData . "]");
  866. }
  867. } else {
  868. throw new Exception("check sign Fail! [sign=" . $signData->sign . ", signSourceData=" . $signData->signSourceData . "]");
  869. }
  870. }
  871. }
  872. }
  873. }
  874. private function setupCharsets($request)
  875. {
  876. if ($this->checkEmpty($this->postCharset)) {
  877. $this->postCharset = 'UTF-8';
  878. }
  879. $str = preg_match('/[\x80-\xff]/', $this->appId) ? $this->appId : print_r($request, true);
  880. $this->fileCharset = mb_detect_encoding($str, "UTF-8, GBK") == 'UTF-8' ? 'UTF-8' : 'GBK';
  881. }
  882. // 获取加密内容
  883. private function encryptJSONSignSource($request, $responseContent)
  884. {
  885. $parsetItem = $this->parserEncryptJSONSignSource($request, $responseContent);
  886. $bodyIndexContent = substr($responseContent, 0, $parsetItem->startIndex);
  887. $bodyEndContent = substr($responseContent, $parsetItem->endIndex, strlen($responseContent) + 1 - $parsetItem->endIndex);
  888. $bizContent = decrypt($parsetItem->encryptContent, $this->encryptKey);
  889. return $bodyIndexContent . $bizContent . $bodyEndContent;
  890. }
  891. private function parserEncryptJSONSignSource($request, $responseContent)
  892. {
  893. $apiName = $request->getApiMethodName();
  894. $rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX;
  895. $rootIndex = strpos($responseContent, $rootNodeName);
  896. $errorIndex = strpos($responseContent, $this->ERROR_RESPONSE);
  897. if ($rootIndex > 0) {
  898. return $this->parserEncryptJSONItem($responseContent, $rootNodeName, $rootIndex);
  899. } else if ($errorIndex > 0) {
  900. return $this->parserEncryptJSONItem($responseContent, $this->ERROR_RESPONSE, $errorIndex);
  901. } else {
  902. return null;
  903. }
  904. }
  905. private function parserEncryptJSONItem($responseContent, $nodeName, $nodeIndex)
  906. {
  907. $signDataStartIndex = $nodeIndex + strlen($nodeName) + 2;
  908. $signIndex = strpos($responseContent, "\"" . $this->SIGN_NODE_NAME . "\"");
  909. // 签名前-逗号
  910. $signDataEndIndex = $signIndex - 1;
  911. if ($signDataEndIndex < 0) {
  912. $signDataEndIndex = strlen($responseContent) - 1;
  913. }
  914. $indexLen = $signDataEndIndex - $signDataStartIndex;
  915. $encContent = substr($responseContent, $signDataStartIndex + 1, $indexLen - 2);
  916. $encryptParseItem = new EncryptParseItem();
  917. $encryptParseItem->encryptContent = $encContent;
  918. $encryptParseItem->startIndex = $signDataStartIndex;
  919. $encryptParseItem->endIndex = $signDataEndIndex;
  920. return $encryptParseItem;
  921. }
  922. // 获取加密内容
  923. private function encryptXMLSignSource($request, $responseContent)
  924. {
  925. $parsetItem = $this->parserEncryptXMLSignSource($request, $responseContent);
  926. $bodyIndexContent = substr($responseContent, 0, $parsetItem->startIndex);
  927. $bodyEndContent = substr($responseContent, $parsetItem->endIndex, strlen($responseContent) + 1 - $parsetItem->endIndex);
  928. $bizContent = decrypt($parsetItem->encryptContent, $this->encryptKey);
  929. return $bodyIndexContent . $bizContent . $bodyEndContent;
  930. }
  931. private function parserEncryptXMLSignSource($request, $responseContent)
  932. {
  933. $apiName = $request->getApiMethodName();
  934. $rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX;
  935. $rootIndex = strpos($responseContent, $rootNodeName);
  936. $errorIndex = strpos($responseContent, $this->ERROR_RESPONSE);
  937. // $this->echoDebug("<br/>rootNodeName:" . $rootNodeName);
  938. // $this->echoDebug("<br/> responseContent:<xmp>" . $responseContent . "</xmp>");
  939. if ($rootIndex > 0) {
  940. return $this->parserEncryptXMLItem($responseContent, $rootNodeName, $rootIndex);
  941. } else if ($errorIndex > 0) {
  942. return $this->parserEncryptXMLItem($responseContent, $this->ERROR_RESPONSE, $errorIndex);
  943. } else {
  944. return null;
  945. }
  946. }
  947. private function parserEncryptXMLItem($responseContent, $nodeName, $nodeIndex)
  948. {
  949. $signDataStartIndex = $nodeIndex + strlen($nodeName) + 1;
  950. $xmlStartNode = "<" . $this->ENCRYPT_XML_NODE_NAME . ">";
  951. $xmlEndNode = "</" . $this->ENCRYPT_XML_NODE_NAME . ">";
  952. $indexOfXmlNode = strpos($responseContent, $xmlEndNode);
  953. if ($indexOfXmlNode < 0) {
  954. $item = new EncryptParseItem();
  955. $item->encryptContent = null;
  956. $item->startIndex = 0;
  957. $item->endIndex = 0;
  958. return $item;
  959. }
  960. $startIndex = $signDataStartIndex + strlen($xmlStartNode);
  961. $bizContentLen = $indexOfXmlNode - $startIndex;
  962. $bizContent = substr($responseContent, $startIndex, $bizContentLen);
  963. $encryptParseItem = new EncryptParseItem();
  964. $encryptParseItem->encryptContent = $bizContent;
  965. $encryptParseItem->startIndex = $signDataStartIndex;
  966. $encryptParseItem->endIndex = $indexOfXmlNode + strlen($xmlEndNode);
  967. return $encryptParseItem;
  968. }
  969. function echoDebug($content)
  970. {
  971. if ($this->debugInfo) {
  972. echo "<br/>" . $content;
  973. }
  974. }
  975. }