SandPay.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. <?php
  2. namespace App\Libs\Pay\Merchants;
  3. use Exception;
  4. use GuzzleHttp\Client;
  5. use Log;
  6. class SandPay
  7. {
  8. const API_HOST = 'https://cashier.sandpay.com.cn/gateway/api/';
  9. const API_TEST_HOST = 'http://61.129.71.103:8003/gateway/';
  10. const NOTIFY_HOST = 'http://47.97.120.133:8094/';
  11. const NOTIFY_TEST_HOST = 'http://managepre.aizhuishu.com/';
  12. private $notify_url;
  13. private $front_url;
  14. private $pay_url;
  15. private $query_order_url;
  16. private $private_key;
  17. private $public_key;
  18. private $client;
  19. private $mer_id;
  20. private $app_id;
  21. public function __construct(array $config)
  22. {
  23. $this->pay_url = (self::API_HOST) . 'order/pay';
  24. $this->query_order_url = (self::API_HOST) . 'order/query';
  25. $this->mer_id = $config['mer_id'];
  26. $this->app_id = $config['app_id'];
  27. $this->private_key = $this->loadPk12Cert(storage_path($config['private_key_path']), $config['cert_pwd']);
  28. $this->public_key = $this->loadX509Cert(storage_path($config['public_key_path']));
  29. $this->notify_url = env('SANDPAY_CALL_BACK_URL');
  30. //$this->notify_url = (env('APP_ENV') == 'online' ? self::NOTIFY_HOST : self::NOTIFY_TEST_HOST) . 'pay/sandpay_back';
  31. $this->front_url = (env('APP_ENV') == 'online' ? self::NOTIFY_HOST : self::NOTIFY_TEST_HOST) . 'pay/sandpay_back';
  32. $this->client = new Client();
  33. }
  34. public function notify(array $params)
  35. {
  36. $sign = $params['sign']; //签名
  37. $signType = $params['signType']; //签名方式
  38. $data = stripslashes($params['data']); //支付数据
  39. $result = json_decode($data, true); //data数据
  40. if ($this->verify($data, $sign, $this->public_key)) {
  41. //签名验证成功
  42. return $result;
  43. } else {
  44. //签名验证失败
  45. myLog('sand_pay')->error('sign error: ' . json_encode($params));
  46. }
  47. }
  48. public function send(array $data)
  49. {
  50. $params = [
  51. 'head' => [
  52. 'version' => '1.0',
  53. 'method' => 'sandpay.trade.pay',
  54. 'productId' => '00002020',
  55. 'accessType' => '1',
  56. 'mid' => $this->mer_id,
  57. 'channelType' => '07',
  58. 'reqTime' => date('YmdHis', time())
  59. ],
  60. 'body' => [
  61. 'orderCode' => $data['trade_no'],
  62. 'totalAmount' => $this->amountFormat($data['price']/100),
  63. 'subject' => $data['body'],
  64. 'body' => $data['body'],
  65. 'payMode' => 'sand_wx',
  66. 'payExtra' => json_encode([
  67. 'subAppid' => $this->app_id,
  68. 'userId' => $data['openid'],
  69. ]),
  70. 'clientIp' => _getIp(),
  71. 'notifyUrl' => $this->notify_url,
  72. 'frontUrl' => $this->front_url,
  73. 'extend' => ''
  74. ]
  75. ];
  76. $result = '';
  77. try{
  78. $result = $this->sendPost($this->pay_url, $params);
  79. }catch (\Exception $e){
  80. Log::error('SandPay $request param is :');
  81. Log::error($params);
  82. Log::error('SandPay error return '.' pay_url: response '.$result);
  83. }
  84. $data = json_decode($result['data'], true);
  85. if ($data['head']['respCode'] == "000000") {
  86. $credential = json_decode($data['body']['credential'],true);
  87. return json_decode($credential['params'],true);
  88. } else {
  89. myLog('sand_pay')->error('唤起支付失败: ' );
  90. myLog('sand_pay')->error($result );
  91. myLog('sand_pay')->error($params );
  92. return [];
  93. }
  94. }
  95. public function refund(array $data){
  96. $params = [
  97. 'head'=>[
  98. 'version'=>'1.0',
  99. 'method'=>'sandpay.trade.refund',
  100. 'productId'=>'00002020',
  101. 'accessType'=>'1',
  102. 'mid'=>$this->mer_id,
  103. 'channelType' => '07',
  104. 'reqTime' => date('YmdHis')
  105. ],
  106. 'body' => [
  107. 'orderCode' => $data['refund_no'],
  108. 'refundAmount' => $this->amountFormat($data['price']/100),
  109. 'oriOrderCode'=>$data['trade_no'],
  110. 'notifyUrl' => $data['callback'],
  111. 'refundReason' => '退货',
  112. 'extend' => isset($data['attach'])?$data['attach']:''
  113. ]
  114. ];
  115. $url = 'https://cashier.sandpay.com.cn/gateway/api/order/refund';
  116. $result = '';
  117. try{
  118. $result = $this->sendPost($url, $params);
  119. }catch (\Exception $e){
  120. Log::error('SandPay $request param is :');
  121. Log::error($params);
  122. Log::error('SandPay error return '.' pay_url: response '.$result);
  123. }
  124. return $result;
  125. }
  126. public function query(array $data){
  127. $params = [
  128. 'head'=>[
  129. 'version'=>'1.0',
  130. 'method'=>'sandpay.trade.query',
  131. 'productId'=>'00002020',
  132. 'accessType'=>'1',
  133. 'mid'=>$this->mer_id,
  134. 'channelType' => '07',
  135. 'reqTime' => date('YmdHis')
  136. ],
  137. 'body' => [
  138. 'orderCode' => $data['trade_no'],
  139. 'extend' => ''
  140. ]
  141. ];
  142. $result = '';
  143. $url = 'https://cashier.sandpay.com.cn/gateway/api/order/query';
  144. try{
  145. $result = $this->sendPost($url, $params);
  146. }catch (\Exception $e){
  147. }
  148. return $result;
  149. }
  150. /**
  151. * 格式金额
  152. */
  153. private function amountFormat(float $amount)
  154. {
  155. $amount = (string) (int) ($amount * 100);
  156. $length = strlen($amount);
  157. for ($i = 0; $i < 12 - $length; $i++) {
  158. $amount = '0' . $amount;
  159. }
  160. return $amount;
  161. }
  162. /**
  163. * 请求接口
  164. * @param array $data
  165. * @return array
  166. */
  167. private function sendPost(string $url, array $data)
  168. {
  169. // step1: 私钥签名
  170. $sign = $this->sign($data, $this->private_key);
  171. // step2: 拼接post数据
  172. $post = array(
  173. 'charset' => 'utf-8',
  174. 'signType' => '01',
  175. 'data' => json_encode($data),
  176. 'sign' => $sign
  177. );
  178. // step3: post请求
  179. $result = $this->http_post_json($url, $post);
  180. $arr = $this->parse_result($result);
  181. try {
  182. //step4: 公钥验签
  183. $this->verify($arr['data'], $arr['sign'], $this->public_key);
  184. return $arr;
  185. } catch (Exception $e) {
  186. echo $e->getMessage();
  187. return [];
  188. }
  189. }
  190. private function parse_result($result)
  191. {
  192. $arr = array();
  193. $response = urldecode($result);
  194. $arrStr = explode('&', $response);
  195. foreach ($arrStr as $str) {
  196. $p = strpos($str, "=");
  197. $key = substr($str, 0, $p);
  198. $value = substr($str, $p + 1);
  199. $arr[$key] = $value;
  200. }
  201. return $arr;
  202. }
  203. /**
  204. * 发送请求
  205. * @param $url
  206. * @param $param
  207. * @return array|null
  208. * @throws Exception
  209. */
  210. private function http_post_json(string $url, array $param)
  211. {
  212. try {
  213. $response = $this->client->post($url, ['form_params' => $param, 'Content-Type' => 'application/x-www-form-urlencoded']);
  214. $content = $response->getBody()->getContents();
  215. return $content;
  216. /*$content = urldecode($content);
  217. parse_str($content, $result);
  218. return $result;*/
  219. } catch (Exception $e) {
  220. throw $e;
  221. }
  222. }
  223. /**
  224. * 获取公钥
  225. * @param $path
  226. * @return mixed
  227. * @throws Exception
  228. */
  229. private function loadX509Cert($path)
  230. {
  231. try {
  232. $file = file_get_contents($path);
  233. if (!$file) {
  234. throw new Exception('loadx509Cert::file_get_contents ERROR');
  235. }
  236. $cert = chunk_split(base64_encode($file), 64, "\n");
  237. $cert = "-----BEGIN CERTIFICATE-----\n" . $cert . "-----END CERTIFICATE-----\n";
  238. $res = openssl_pkey_get_public($cert);
  239. $detail = openssl_pkey_get_details($res);
  240. openssl_free_key($res);
  241. if (!$detail) {
  242. throw new Exception('loadX509Cert::openssl_pkey_get_details ERROR');
  243. }
  244. return $detail['key'];
  245. } catch (Exception $e) {
  246. throw $e;
  247. }
  248. }
  249. /**
  250. * 获取私钥
  251. * @param $path
  252. * @param $pwd
  253. * @return mixed
  254. * @throws Exception
  255. */
  256. private function loadPk12Cert($path, $pwd)
  257. {
  258. try {
  259. $file = file_get_contents($path);
  260. if (!$file) {
  261. throw new Exception('loadPk12Cert::file
  262. _get_contents');
  263. }
  264. if (!openssl_pkcs12_read($file, $cert, $pwd)) {
  265. throw new Exception('loadPk12Cert::openssl_pkcs12_read ERROR');
  266. }
  267. return $cert['pkey'];
  268. } catch (Exception $e) {
  269. throw $e;
  270. }
  271. }
  272. /**
  273. * 私钥签名
  274. * @param $plainText
  275. * @param $path
  276. * @return string
  277. * @throws Exception
  278. */
  279. function sign($plainText, $path)
  280. {
  281. $plainText = json_encode($plainText);
  282. try {
  283. $resource = openssl_pkey_get_private($path);
  284. $result = openssl_sign($plainText, $sign, $resource);
  285. openssl_free_key($resource);
  286. if (!$result) {
  287. throw new Exception('签名出错' . $plainText);
  288. }
  289. return base64_encode($sign);
  290. } catch (Exception $e) {
  291. throw $e;
  292. }
  293. }
  294. /**
  295. * 公钥验签
  296. * @param $plainText
  297. * @param $sign
  298. * @param $path
  299. * @return int
  300. * @throws Exception
  301. */
  302. function verify($plainText, $sign, $path)
  303. {
  304. $resource = openssl_pkey_get_public($path);
  305. $result = openssl_verify($plainText, base64_decode($sign), $resource);
  306. openssl_free_key($resource);
  307. if (!$result) {
  308. throw new Exception('签名验证未通过,plainText:' . $plainText . '。sign:' . $sign, '02002');
  309. }
  310. return $result;
  311. }
  312. /**
  313. * 公钥加密AESKey
  314. * @param $plainText
  315. * @param $puk
  316. * @return string
  317. * @throws Exception
  318. */
  319. function RSAEncryptByPub($plainText, $puk)
  320. {
  321. if (!openssl_public_encrypt($plainText, $cipherText, $puk, OPENSSL_PKCS1_PADDING)) {
  322. throw new Exception('AESKey 加密错误');
  323. }
  324. return base64_encode($cipherText);
  325. }
  326. /**
  327. * 私钥解密AESKey
  328. * @param $cipherText
  329. * @param $prk
  330. * @return string
  331. * @throws Exception
  332. */
  333. function RSADecryptByPri($cipherText, $prk)
  334. {
  335. if (!openssl_private_decrypt(base64_decode($cipherText), $plainText, $prk, OPENSSL_PKCS1_PADDING)) {
  336. throw new Exception('AESKey 解密错误');
  337. }
  338. return (string) $plainText;
  339. }
  340. /**
  341. * AES加密
  342. * @param $plainText
  343. * @param $key
  344. * @return string
  345. * @throws Exception
  346. */
  347. function AESEncrypt($plainText, $key)
  348. {
  349. $plainText = json_encode($plainText);
  350. $result = openssl_encrypt($plainText, 'AES-128-ECB', $key, 1);
  351. if (!$result) {
  352. throw new Exception('报文加密错误');
  353. }
  354. return base64_encode($result);
  355. }
  356. /**
  357. * AES解密
  358. * @param $cipherText
  359. * @param $key
  360. * @return string
  361. * @throws Exception
  362. */
  363. function AESDecrypt($cipherText, $key)
  364. {
  365. $result = openssl_decrypt(base64_decode($cipherText), 'AES-128-ECB', $key, 1);
  366. if (!$result) {
  367. throw new Exception('报文解密错误', 2003);
  368. }
  369. return $result;
  370. }
  371. /**
  372. * 生成AESKey
  373. * @param $size
  374. * @return string
  375. */
  376. function aes_generate($size)
  377. {
  378. $str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  379. $arr = array();
  380. for ($i = 0; $i < $size; $i++) {
  381. $arr[] = $str[mt_rand(0, 61)];
  382. }
  383. return implode('', $arr);
  384. }
  385. }