123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 |
- <?php
- namespace App\Libs\Pay;
- use Exception;
- use GuzzleHttp\Client;
- class SandPay
- {
- const API_HOST = 'https://caspay.sandpay.com.cn/agent-main/openapi/';
- const API_TEST_HOST = 'http://61.129.71.103:7970/agent-main/openapi/';
- const NOTIFY_HOST = 'http://116.62.175.234:8094/';
- const NOTIFY_TEST_HOST = 'http://managepre.aizhuishu.com/';
- const PUB_PRODUCT_ID = '00000003'; //代付对公:00000003
- const PRI_PRODUCT_ID = '00000004'; //代付对私:00000004
- const PUB_KEY_PATH = 'cert/sandpay-finance/sand.cer'; //公钥文件
- const PRI_KEY_PATH = 'cert/sandpay-finance/vPvWnHjBeQhHLebn.pfx'; //私钥文件
- const CERT_PWD = 'vPvWnHjBeQhHLebn'; //私钥证书密码
- const MER_ID = '6888804025954';
- private $notify_url;
- private $agentpay_url;
- private $query_balance_url;
- private $query_order_url;
- private $private_key;
- private $public_key;
- private $client;
- private $product_id;
- public function __construct(bool $is_private = true)
- {
- $this->agentpay_url = (self::API_HOST) . 'agentpay';
- $this->query_balance_url = (self::API_HOST) . 'queryBalance';
- $this->query_order_url = (self::API_HOST) . 'queryOrder';
- $this->private_key = $this->loadPk12Cert(storage_path(self::PRI_KEY_PATH), self::CERT_PWD);
- $this->public_key = $this->loadX509Cert(storage_path(self::PUB_KEY_PATH));
- $this->notify_url = (env('APP_ENV') == 'online' ? self::NOTIFY_HOST : self::NOTIFY_TEST_HOST) . 'api/tradepayapi/sandPayNotify';
- $this->product_id = $is_private ? self::PRI_PRODUCT_ID : self::PUB_PRODUCT_ID;
- $this->client = new Client();
- }
- /**
- * 格式金额
- */
- private function amountFormat(float $amount)
- {
- $amount = strval($amount * 100);
- $length = strlen($amount);
- for ($i = 0; $i < 12 - $length; $i++) {
- $amount = '0' . $amount;
- }
- return $amount;
- }
- /**
- * 反格式化金额
- */
- private function formatAmount(string $amount)
- {
- return (float) (((int) ltrim($amount, '\0')) / 100);
- }
- /**
- * 实时代付
- * @param array $data
- * @return array
- */
- public function agentPay(array $data)
- {
- $params = [
- 'transCode' => 'RTPM', // 实时代付
- 'url' => $this->agentpay_url,
- 'params' => [
- 'version' => '01',
- 'productId' => $this->product_id,
- 'tranTime' => date('YmdHis', strtotime($data['pay_time'])),
- 'orderCode' => $data['order_no'],
- 'tranAmt' => $this->amountFormat($data['amount']),
- 'currencyCode' => '156',
- 'accAttr' => $this->product_id == self::PRI_PRODUCT_ID ? '0' : '1',
- 'accType' => $this->product_id == self::PRI_PRODUCT_ID ? '4' : '3',
- 'accNo' => $data['bank_account'],
- 'accName' => $data['account_name'],
- 'bankName' => $data['account_bank'],
- 'remark' => $data['remark'],
- 'payMode' => '1',
- 'channelType' => '07',
- 'noticeUrl' => $this->notify_url,
- ],
- ];
- if ($this->product_id == self::PUB_PRODUCT_ID) {
- $params['params']['bankType'] = $data['bank_number'];
- }
- $result = $this->send($params);
- if ($result->resultFlag == 0) {
- return [
- 'result' => true,
- 'content' => $result,
- ];
- } else {
- return [
- 'result' => false,
- 'content' => $result,
- ];
- }
- }
- /**
- * 查询订单
- * @param array $data
- * @return array
- */
- public function queryOrder(array $data, bool $is_private)
- {
- $this->product_id = $is_private ? self::PRI_PRODUCT_ID : self::PUB_PRODUCT_ID;
- $params = [
- 'transCode' => 'ODQU', // 订单查询
- 'url' => $this->query_order_url,
- 'params' => [
- 'orderCode' => $data['order_no'],
- 'version' => '01',
- 'productId' => $this->product_id,
- 'tranTime' => date('YmdHis', strtotime($data['pay_time'])),
- ],
- ];
- $result = $this->send($params);
- if (isset($result->resultFlag) && $result->resultFlag == 0) {
- return [
- 'result' => 'success',
- 'content' => $result,
- ];
- } else if (isset($result->resultFlag) && $result->resultFlag == 2) {
- return [
- 'result' => 'waitting',
- 'content' => $result,
- ];
- } else {
- return [
- 'result' => 'failure',
- 'content' => $result,
- ];
- }
- }
- /**
- * 查询余额
- * @param array $data
- * @return float
- */
- public function queryBalance(array $data)
- {
- $params = [
- 'transCode' => 'MBQU', // 商户余额查询
- 'url' => $this->query_balance_url,
- 'params' => array(
- 'orderCode' => $data['order_no'],
- 'version' => '01',
- 'productId' => $this->product_id,
- 'tranTime' => date('YmdHis', strtotime($data['pay_time'])),
- )
- ];
- $result = $this->send($params);
- if (isset($result->balance)) {
- return ((int) ltrim($result->balance, '+')) / 100;
- } else {
- return 0;
- }
- }
- /**
- * 发送请求
- * @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();
- parse_str($content, $result);
- return $result;
- } catch (Exception $e) {
- throw $e;
- }
- }
- /**
- * 请求接口
- * @param array $data
- * @return object
- */
- private function send(array $data)
- {
- // step1: 生成AESKey并使用公钥加密
- $AESKey = $this->aes_generate(16);
- $encryptKey = $this->RSAEncryptByPub($AESKey, $this->public_key);
- $params = $data['params'];
- // step2: 使用AESKey加密报文
- $encryptData = $this->AESEncrypt($params, $AESKey);
- // step3: 使用私钥签名报文
- $sign = $this->sign($params, $this->private_key);
- // step4: 拼接post数据
- $post = [
- 'transCode' => $data['transCode'],
- 'accessType' => '0',
- 'merId' => self::MER_ID,
- 'encryptKey' => $encryptKey,
- 'encryptData' => $encryptData,
- 'sign' => $sign
- ];
- try {
- // step5: post请求
- $encrypt_data = $this->http_post_json($data['url'], $post);
- // step6: 使用私钥解密AESKey
- $decryptAESKey = $this->RSADecryptByPri($encrypt_data['encryptKey'], $this->private_key);
- // step7: 使用解密后的AESKey解密报文
- $decryptPlainText = $this->AESDecrypt($encrypt_data['encryptData'], $decryptAESKey);
- // step8: 使用公钥验签报文
- $this->verify($decryptPlainText, $encrypt_data['sign'], $this->public_key);
- $result = json_decode($decryptPlainText);
- if ($result->respCode != '0000') {
- myLog('sand_pay')->error($params['orderCode'] . ': ' . $decryptPlainText);
- } else {
- myLog('sand_pay')->info($decryptPlainText);
- }
- return $result;
- } catch (Exception $e) {
- myLog('sand_pay')->error($params['orderCode'] . ': ' . $e->getMessage());
- }
- return [];
- }
- /**
- * 获取公钥
- * @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);
- }
- }
|