|
@@ -0,0 +1,427 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace App\Libs\Pay\Merchants;
|
|
|
+
|
|
|
+use Exception;
|
|
|
+use GuzzleHttp\Client;
|
|
|
+use Log;
|
|
|
+
|
|
|
+class SandPay
|
|
|
+{
|
|
|
+ const API_HOST = 'https://cashier.sandpay.com.cn/gateway/api/';
|
|
|
+ const API_TEST_HOST = 'http://61.129.71.103:8003/gateway/';
|
|
|
+ const NOTIFY_HOST = 'http://47.97.120.133:8094/';
|
|
|
+ const NOTIFY_TEST_HOST = 'http://managepre.aizhuishu.com/';
|
|
|
+
|
|
|
+ private $notify_url;
|
|
|
+ private $front_url;
|
|
|
+ private $pay_url;
|
|
|
+ private $query_order_url;
|
|
|
+ private $private_key;
|
|
|
+ private $public_key;
|
|
|
+ private $client;
|
|
|
+ private $mer_id;
|
|
|
+ private $app_id;
|
|
|
+
|
|
|
+ public function __construct(array $config)
|
|
|
+ {
|
|
|
+ $this->pay_url = (self::API_HOST) . 'order/pay';
|
|
|
+ $this->query_order_url = (self::API_HOST) . 'order/query';
|
|
|
+ $this->mer_id = $config['mer_id'];
|
|
|
+ $this->app_id = $config['app_id'];
|
|
|
+ $this->private_key = $this->loadPk12Cert(storage_path($config['private_key_path']), $config['cert_pwd']);
|
|
|
+ $this->public_key = $this->loadX509Cert(storage_path($config['public_key_path']));
|
|
|
+ $this->notify_url = env('SANDPAY_CALL_BACK_URL');
|
|
|
+ //$this->notify_url = (env('APP_ENV') == 'online' ? self::NOTIFY_HOST : self::NOTIFY_TEST_HOST) . 'pay/sandpay_back';
|
|
|
+ $this->front_url = (env('APP_ENV') == 'online' ? self::NOTIFY_HOST : self::NOTIFY_TEST_HOST) . 'pay/sandpay_back';
|
|
|
+ $this->client = new Client();
|
|
|
+ }
|
|
|
+
|
|
|
+ public function notify(array $params)
|
|
|
+ {
|
|
|
+ $sign = $params['sign']; //签名
|
|
|
+ $signType = $params['signType']; //签名方式
|
|
|
+ $data = stripslashes($params['data']); //支付数据
|
|
|
+ $result = json_decode($data, true); //data数据
|
|
|
+
|
|
|
+ if ($this->verify($data, $sign, $this->public_key)) {
|
|
|
+ //签名验证成功
|
|
|
+ return $result;
|
|
|
+ } else {
|
|
|
+ //签名验证失败
|
|
|
+ myLog('sand_pay')->error('sign error: ' . json_encode($params));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public function send(array $data)
|
|
|
+ {
|
|
|
+ $params = [
|
|
|
+ 'head' => [
|
|
|
+ 'version' => '1.0',
|
|
|
+ 'method' => 'sandpay.trade.pay',
|
|
|
+ 'productId' => '00002020',
|
|
|
+ 'accessType' => '1',
|
|
|
+ 'mid' => $this->mer_id,
|
|
|
+ 'channelType' => '07',
|
|
|
+ 'reqTime' => date('YmdHis', time())
|
|
|
+ ],
|
|
|
+ 'body' => [
|
|
|
+ 'orderCode' => $data['trade_no'],
|
|
|
+ 'totalAmount' => $this->amountFormat($data['price']/100),
|
|
|
+ 'subject' => $data['body'],
|
|
|
+ 'body' => $data['body'],
|
|
|
+ 'payMode' => 'sand_wx',
|
|
|
+ 'payExtra' => json_encode([
|
|
|
+ 'subAppid' => $this->app_id,
|
|
|
+ 'userId' => $data['openid'],
|
|
|
+ ]),
|
|
|
+ 'clientIp' => _getIp(),
|
|
|
+ 'notifyUrl' => $this->notify_url,
|
|
|
+ 'frontUrl' => $this->front_url,
|
|
|
+ 'extend' => ''
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+ $result = '';
|
|
|
+ try{
|
|
|
+ $result = $this->sendPost($this->pay_url, $params);
|
|
|
+ }catch (\Exception $e){
|
|
|
+ Log::error('SandPay $request param is :');
|
|
|
+ Log::error($params);
|
|
|
+ Log::error('SandPay error return '.' pay_url: response '.$result);
|
|
|
+ }
|
|
|
+
|
|
|
+ $data = json_decode($result['data'], true);
|
|
|
+ if ($data['head']['respCode'] == "000000") {
|
|
|
+ $credential = json_decode($data['body']['credential'],true);
|
|
|
+ return json_decode($credential['params'],true);
|
|
|
+ } else {
|
|
|
+ myLog('sand_pay')->error('唤起支付失败: ' );
|
|
|
+ myLog('sand_pay')->error($result );
|
|
|
+ myLog('sand_pay')->error($params );
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ public function refund(array $data){
|
|
|
+ $params = [
|
|
|
+ 'head'=>[
|
|
|
+ 'version'=>'1.0',
|
|
|
+ 'method'=>'sandpay.trade.refund',
|
|
|
+ 'productId'=>'00002020',
|
|
|
+ 'accessType'=>'1',
|
|
|
+ 'mid'=>$this->mer_id,
|
|
|
+ 'channelType' => '07',
|
|
|
+ 'reqTime' => date('YmdHis')
|
|
|
+ ],
|
|
|
+ 'body' => [
|
|
|
+ 'orderCode' => $data['refund_no'],
|
|
|
+ 'refundAmount' => $this->amountFormat($data['price']/100),
|
|
|
+ 'oriOrderCode'=>$data['trade_no'],
|
|
|
+ 'notifyUrl' => $data['callback'],
|
|
|
+ 'refundReason' => '退货',
|
|
|
+ 'extend' => isset($data['attach'])?$data['attach']:''
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+ $url = 'https://cashier.sandpay.com.cn/gateway/api/order/refund';
|
|
|
+ $result = '';
|
|
|
+ try{
|
|
|
+ $result = $this->sendPost($url, $params);
|
|
|
+ }catch (\Exception $e){
|
|
|
+ Log::error('SandPay $request param is :');
|
|
|
+ Log::error($params);
|
|
|
+ Log::error('SandPay error return '.' pay_url: response '.$result);
|
|
|
+ }
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function query(array $data){
|
|
|
+ $params = [
|
|
|
+ 'head'=>[
|
|
|
+ 'version'=>'1.0',
|
|
|
+ 'method'=>'sandpay.trade.query',
|
|
|
+ 'productId'=>'00002020',
|
|
|
+ 'accessType'=>'1',
|
|
|
+ 'mid'=>$this->mer_id,
|
|
|
+ 'channelType' => '07',
|
|
|
+ 'reqTime' => date('YmdHis')
|
|
|
+ ],
|
|
|
+ 'body' => [
|
|
|
+ 'orderCode' => $data['trade_no'],
|
|
|
+ 'extend' => ''
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+ $result = '';
|
|
|
+ $url = 'https://cashier.sandpay.com.cn/gateway/api/order/query';
|
|
|
+ try{
|
|
|
+ $result = $this->sendPost($url, $params);
|
|
|
+ }catch (\Exception $e){
|
|
|
+ }
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式金额
|
|
|
+ */
|
|
|
+ private function amountFormat(float $amount)
|
|
|
+ {
|
|
|
+ $amount = (string) (int) ($amount * 100);
|
|
|
+ $length = strlen($amount);
|
|
|
+ for ($i = 0; $i < 12 - $length; $i++) {
|
|
|
+ $amount = '0' . $amount;
|
|
|
+ }
|
|
|
+ return $amount;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 请求接口
|
|
|
+ * @param array $data
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ private function sendPost(string $url, array $data)
|
|
|
+ {
|
|
|
+ // step1: 私钥签名
|
|
|
+ $sign = $this->sign($data, $this->private_key);
|
|
|
+
|
|
|
+ // step2: 拼接post数据
|
|
|
+ $post = array(
|
|
|
+ 'charset' => 'utf-8',
|
|
|
+ 'signType' => '01',
|
|
|
+ 'data' => json_encode($data),
|
|
|
+ 'sign' => $sign
|
|
|
+ );
|
|
|
+
|
|
|
+ // step3: post请求
|
|
|
+ $result = $this->http_post_json($url, $post);
|
|
|
+ $arr = $this->parse_result($result);
|
|
|
+
|
|
|
+ try {
|
|
|
+ //step4: 公钥验签
|
|
|
+ $this->verify($arr['data'], $arr['sign'], $this->public_key);
|
|
|
+ return $arr;
|
|
|
+ } catch (Exception $e) {
|
|
|
+ echo $e->getMessage();
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private function parse_result($result)
|
|
|
+ {
|
|
|
+ $arr = array();
|
|
|
+ $response = urldecode($result);
|
|
|
+ $arrStr = explode('&', $response);
|
|
|
+ foreach ($arrStr as $str) {
|
|
|
+ $p = strpos($str, "=");
|
|
|
+ $key = substr($str, 0, $p);
|
|
|
+ $value = substr($str, $p + 1);
|
|
|
+ $arr[$key] = $value;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $arr;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送请求
|
|
|
+ * @param $url
|
|
|
+ * @param $param
|
|
|
+ * @return array|null
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ private function http_post_json(string $url, array $param)
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $response = $this->client->post($url, ['form_params' => $param, 'Content-Type' => 'application/x-www-form-urlencoded']);
|
|
|
+ $content = $response->getBody()->getContents();
|
|
|
+ return $content;
|
|
|
+ /*$content = urldecode($content);
|
|
|
+ parse_str($content, $result);
|
|
|
+ return $result;*/
|
|
|
+ } catch (Exception $e) {
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取公钥
|
|
|
+ * @param $path
|
|
|
+ * @return mixed
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ private function loadX509Cert($path)
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $file = file_get_contents($path);
|
|
|
+ if (!$file) {
|
|
|
+ throw new Exception('loadx509Cert::file_get_contents ERROR');
|
|
|
+ }
|
|
|
+
|
|
|
+ $cert = chunk_split(base64_encode($file), 64, "\n");
|
|
|
+ $cert = "-----BEGIN CERTIFICATE-----\n" . $cert . "-----END CERTIFICATE-----\n";
|
|
|
+
|
|
|
+ $res = openssl_pkey_get_public($cert);
|
|
|
+ $detail = openssl_pkey_get_details($res);
|
|
|
+ openssl_free_key($res);
|
|
|
+
|
|
|
+ if (!$detail) {
|
|
|
+ throw new Exception('loadX509Cert::openssl_pkey_get_details ERROR');
|
|
|
+ }
|
|
|
+
|
|
|
+ return $detail['key'];
|
|
|
+ } catch (Exception $e) {
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取私钥
|
|
|
+ * @param $path
|
|
|
+ * @param $pwd
|
|
|
+ * @return mixed
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ private function loadPk12Cert($path, $pwd)
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $file = file_get_contents($path);
|
|
|
+ if (!$file) {
|
|
|
+ throw new Exception('loadPk12Cert::file
|
|
|
+ _get_contents');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!openssl_pkcs12_read($file, $cert, $pwd)) {
|
|
|
+ throw new Exception('loadPk12Cert::openssl_pkcs12_read ERROR');
|
|
|
+ }
|
|
|
+ return $cert['pkey'];
|
|
|
+ } catch (Exception $e) {
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 私钥签名
|
|
|
+ * @param $plainText
|
|
|
+ * @param $path
|
|
|
+ * @return string
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ function sign($plainText, $path)
|
|
|
+ {
|
|
|
+ $plainText = json_encode($plainText);
|
|
|
+ try {
|
|
|
+ $resource = openssl_pkey_get_private($path);
|
|
|
+ $result = openssl_sign($plainText, $sign, $resource);
|
|
|
+ openssl_free_key($resource);
|
|
|
+
|
|
|
+ if (!$result) {
|
|
|
+ throw new Exception('签名出错' . $plainText);
|
|
|
+ }
|
|
|
+
|
|
|
+ return base64_encode($sign);
|
|
|
+ } catch (Exception $e) {
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 公钥验签
|
|
|
+ * @param $plainText
|
|
|
+ * @param $sign
|
|
|
+ * @param $path
|
|
|
+ * @return int
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ function verify($plainText, $sign, $path)
|
|
|
+ {
|
|
|
+ $resource = openssl_pkey_get_public($path);
|
|
|
+ $result = openssl_verify($plainText, base64_decode($sign), $resource);
|
|
|
+ openssl_free_key($resource);
|
|
|
+
|
|
|
+ if (!$result) {
|
|
|
+ throw new Exception('签名验证未通过,plainText:' . $plainText . '。sign:' . $sign, '02002');
|
|
|
+ }
|
|
|
+
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 公钥加密AESKey
|
|
|
+ * @param $plainText
|
|
|
+ * @param $puk
|
|
|
+ * @return string
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ function RSAEncryptByPub($plainText, $puk)
|
|
|
+ {
|
|
|
+ if (!openssl_public_encrypt($plainText, $cipherText, $puk, OPENSSL_PKCS1_PADDING)) {
|
|
|
+ throw new Exception('AESKey 加密错误');
|
|
|
+ }
|
|
|
+
|
|
|
+ return base64_encode($cipherText);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 私钥解密AESKey
|
|
|
+ * @param $cipherText
|
|
|
+ * @param $prk
|
|
|
+ * @return string
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ function RSADecryptByPri($cipherText, $prk)
|
|
|
+ {
|
|
|
+ if (!openssl_private_decrypt(base64_decode($cipherText), $plainText, $prk, OPENSSL_PKCS1_PADDING)) {
|
|
|
+ throw new Exception('AESKey 解密错误');
|
|
|
+ }
|
|
|
+
|
|
|
+ return (string) $plainText;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * AES加密
|
|
|
+ * @param $plainText
|
|
|
+ * @param $key
|
|
|
+ * @return string
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ function AESEncrypt($plainText, $key)
|
|
|
+ {
|
|
|
+ $plainText = json_encode($plainText);
|
|
|
+ $result = openssl_encrypt($plainText, 'AES-128-ECB', $key, 1);
|
|
|
+
|
|
|
+ if (!$result) {
|
|
|
+ throw new Exception('报文加密错误');
|
|
|
+ }
|
|
|
+
|
|
|
+ return base64_encode($result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * AES解密
|
|
|
+ * @param $cipherText
|
|
|
+ * @param $key
|
|
|
+ * @return string
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ function AESDecrypt($cipherText, $key)
|
|
|
+ {
|
|
|
+ $result = openssl_decrypt(base64_decode($cipherText), 'AES-128-ECB', $key, 1);
|
|
|
+
|
|
|
+ if (!$result) {
|
|
|
+ throw new Exception('报文解密错误', 2003);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成AESKey
|
|
|
+ * @param $size
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+ function aes_generate($size)
|
|
|
+ {
|
|
|
+ $str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
|
+ $arr = array();
|
|
|
+ for ($i = 0; $i < $size; $i++) {
|
|
|
+ $arr[] = $str[mt_rand(0, 61)];
|
|
|
+ }
|
|
|
+ return implode('', $arr);
|
|
|
+ }
|
|
|
+}
|