<?php
namespace app\api\controller;
use Think\Exception;
use think\facade\Log;
class WeChatPay
{
protected $_config;
const KEY_LENGTH_BYTE = 32;
const AUTH_TAG_LENGTH_BYTE = 16;
public function __construct()
{
// 初始化一个支付对象
$this->_config = config('app.wechatPay');
}
/**
* Undocumented function
*
* @param [type] $total
* @param [type] $desc
* @param [type] $out_trade_no
* @return void
*/
public function pay($total, $orderNo, $desc, $ip)
{
$payConfig = $this->_config;
// 创建请求参数
$param = [
'appid' => $payConfig['appid'],
'mchid' => $payConfig['mchid'],
'description' => "盲盒-".$desc,
'out_trade_no' => $orderNo,
'notify_url' => $payConfig['notify_url'],
'amount' => [
'total' => $total * 100,
'currency' => 'CNY'
],
'scene_info' => [
'payer_client_ip' => $ip,
'h5_info' => [
'type' => 'Wap',
],
]
];
$headers = $this->sign('POST',$payConfig['pay_url'],json_encode($param));
return $this->sendHttp($payConfig['pay_url'], $param, 'POST', $headers);
}
/**
* 签名
* @param string $http_method 请求方式GET|POST
* @param string $url url
* @param string $body 报文主体
* @return array
*/
public function sign($http_method = 'POST',$url = '',$body = ''){
$mch_private_key = $this->getMchKey(); //私钥
$timestamp = time(); //时间戳
$nonce = $this->getRandomStr(32); //随机串
$url_parts = parse_url($url); //
$canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
//构造签名串
$message = $http_method."\n".
$canonical_url."\n".
$timestamp."\n".
$nonce."\n".
$body."\n";//报文主体
//计算签名值
openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);
//设置HTTP头
$payConf = $this->_config;
$token = sprintf('WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
$payConf['mchid'], $nonce, $timestamp, $payConf['serial_no'], $sign);
$headers = [
'Accept: application/json',
'User-Agent: */*',
'Content-Type: application/json; charset=utf-8',
'Authorization: '.$token,
];
return $headers;
}
/**
* curl post or get
*
* @param string $url
* @param [type] $params
* @param string $method
* @param array $header
* @param boolean $multi
* @return void
*/
public function sendHttp($url, $params, $method = 'GET', $header = array(), $multi = false)
{
$uuid = uniqid();
Log::info('['. $uuid .']' .__FILE__ . __DIR__. '请求开始');
Log::info('['. $uuid .']Request url:' . $method . '=>' . $url);
Log::info('['. $uuid .']Request data:' . json_encode($params));
Log::info('['. $uuid .']Request header:' . json_encode($header));
$opts = array(
CURLOPT_TIMEOUT => 30,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_HTTPHEADER => $header
);
// 根据请求类型设置特定参数
switch(strtoupper($method)){
case 'GET':
$opts[CURLOPT_URL] = $url . '?' . http_build_query($params);
break;
case 'POST':
//判断是否传输文件
$params = $multi ? $params : json_encode($params);
$opts[CURLOPT_URL] = $url;
$opts[CURLOPT_POST] = 1;
$opts[CURLOPT_POSTFIELDS] = $params;
$opts[CURLOPT_ENCODING] = '';
break;
default:
throw new Exception('不支持的请求方式!');
}
// 初始化并执行curl请求
try {
$ch = curl_init();
$ch = curl_init();
curl_setopt_array($ch, $opts);
$data = curl_exec($ch);
curl_close($ch);
} catch (Exception $e) {
Log::error('['. $uuid .']Response Error:' . $e->getMessage());
throw new Exception('请求发生错误:' . $e->getMessage());
}
$res = mb_convert_encoding($data, 'UTF-8', 'UTF-8,GBK,GB2312,BIG5');
Log::info('['. $uuid .']Response Info:' . json_encode($res));
return $res;
}
/**
* 读取私钥
*
* @return void
*/
public function getMchKey(){
//path->私钥文件存放路径
$path = __DIR__ . "/cert/apiclient_key.pem";
return openssl_get_privatekey(file_get_contents($path));
}
/**
* 获得随机字符串
* @param $len integer 需要的长度
* @param $special bool 是否需要特殊符号
* @return string 返回随机字符串
*/
public function getRandomStr($len, $special=false){
$chars = array(
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G",
"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
"S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2",
"3", "4", "5", "6", "7", "8", "9"
);
if($special){
$chars = array_merge($chars, array(
"!", "@", "#", "$", "?", "|", "{", "/", ":", ";",
"%", "^", "&", "*", "(", ")", "-", "_", "[", "]",
"}", "<", ">", "~", "+", "=", ",", "."
));
}
$charsLen = count($chars) - 1;
shuffle($chars); //打乱数组顺序
$str = '';
for($i=0; $i<$len; $i++){
$str .= $chars[mt_rand(0, $charsLen)]; //随机取出一位
}
return $str;
}
public function decryptToString($associatedData, $nonceStr, $ciphertext)
{
$ciphertext = \base64_decode($ciphertext);
if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
return false;
}
// ext-sodium (default installed on >= PHP 7.2)
if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') &&
\sodium_crypto_aead_aes256gcm_is_available()) {
return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
}
// ext-libsodium (need install libsodium-php 1.x via pecl)
if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') &&
\Sodium\crypto_aead_aes256gcm_is_available()) {
return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
}
// openssl (PHP >= 7.1 support AEAD)
if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
$ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
$authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);
return \openssl_decrypt($ctext, 'aes-256-gcm', $this->aesKey, \OPENSSL_RAW_DATA, $nonceStr,
$authTag, $associatedData);
}
throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
}
}
最后修改:2022 年 03 月 27 日 09 : 00 PM
© 允许规范转载