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); } }