修改平台证书机制

#
This commit is contained in:
邹景立 2023-06-19 13:49:47 +08:00
parent 683eb10ebd
commit b9c4823486
2 changed files with 98 additions and 41 deletions

View File

@ -27,6 +27,13 @@ use WePayV3\Contracts\DecryptAes;
*/ */
class Cert extends BasicWePay class Cert extends BasicWePay
{ {
/**
* 自动配置平台证书
* @var bool
*/
protected $autoCert = false;
/** /**
* 商户平台下载证书 * 商户平台下载证书
* @return void * @return void
@ -37,13 +44,18 @@ class Cert extends BasicWePay
try { try {
$aes = new DecryptAes($this->config['mch_v3_key']); $aes = new DecryptAes($this->config['mch_v3_key']);
$result = $this->doRequest('GET', '/v3/certificates'); $result = $this->doRequest('GET', '/v3/certificates');
$certs = [];
foreach ($result['data'] as $vo) { foreach ($result['data'] as $vo) {
$this->tmpFile($vo['serial_no'], $aes->decryptToString( $certs[$vo['serial_no']] = [
$vo['encrypt_certificate']['associated_data'], 'expire' => strtotime($vo['expire_time']),
$vo['encrypt_certificate']['nonce'], 'content' => $aes->decryptToString(
$vo['encrypt_certificate']['ciphertext'] $vo['encrypt_certificate']['associated_data'],
)); $vo['encrypt_certificate']['nonce'],
$vo['encrypt_certificate']['ciphertext']
)
];
} }
$this->tmpFile("{$this->config['mch_id']}_certs", $certs);
} catch (\Exception $exception) { } catch (\Exception $exception) {
throw new InvalidResponseException($exception->getMessage(), $exception->getCode()); throw new InvalidResponseException($exception->getMessage(), $exception->getCode());
} }

View File

@ -41,17 +41,25 @@ abstract class BasicWePay
*/ */
static $cache = []; static $cache = [];
/**
* 自动配置平台证书
* @var bool
*/
protected $autoCert = true;
/** /**
* 配置参数 * 配置参数
* @var array * @var array
*/ */
protected $config = [ protected $config = [
'appid' => '', // 微信绑定APPID需配置 'appid' => '', // 微信绑定APPID需配置
'mch_id' => '', // 微信商户编号,需要配置 'mch_id' => '', // 微信商户编号,需要配置
'mch_v3_key' => '', // 微信商户密钥,需要配置 'mch_v3_key' => '', // 微信商户密钥,需要配置
'cert_serial' => '', // 商户证书序号,无需配置 'cert_serial' => '', // 商户证书序号,无需配置
'cert_public' => '', // 商户公钥内容,需要配置 'cert_public' => '', // 商户公钥内容,需要配置
'cert_private' => '', // 商户密钥内容,需要配置 'cert_private' => '', // 商户密钥内容,需要配置
'mp_cert_serial' => '', // 平台证书序号,无需配置
'mp_cert_content' => '', // 平台证书内容,无需配置
]; ];
/** /**
@ -94,7 +102,6 @@ abstract class BasicWePay
$this->config['mch_v3_key'] = $options['mch_v3_key']; $this->config['mch_v3_key'] = $options['mch_v3_key'];
$this->config['cert_public'] = $options['cert_public']; $this->config['cert_public'] = $options['cert_public'];
$this->config['cert_private'] = $options['cert_private']; $this->config['cert_private'] = $options['cert_private'];
if (empty($options['cert_serial'])) { if (empty($options['cert_serial'])) {
$this->config['cert_serial'] = openssl_x509_parse($this->config['cert_public'], true)['serialNumberHex']; $this->config['cert_serial'] = openssl_x509_parse($this->config['cert_public'], true)['serialNumberHex'];
} else { } else {
@ -108,6 +115,11 @@ abstract class BasicWePay
Tools::$cache_path = $options['cache_path']; Tools::$cache_path = $options['cache_path'];
} }
// 自动配置平台证书
if ($this->autoCert) {
$this->_autoCert();
}
// 服务商参数支持 // 服务商参数支持
// if (!empty($options['sp_appid'])) { // if (!empty($options['sp_appid'])) {
// $this->config['sp_appid'] = $options['sp_appid']; // $this->config['sp_appid'] = $options['sp_appid'];
@ -149,6 +161,7 @@ abstract class BasicWePay
{ {
list($time, $nonce) = [time(), uniqid() . rand(1000, 9999)]; list($time, $nonce) = [time(), uniqid() . rand(1000, 9999)];
$signstr = join("\n", [$method, $pathinfo, $time, $nonce, $jsondata, '']); $signstr = join("\n", [$method, $pathinfo, $time, $nonce, $jsondata, '']);
echo $pathinfo . PHP_EOL;
// 生成数据签名TOKEN // 生成数据签名TOKEN
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
@ -161,7 +174,7 @@ abstract class BasicWePay
'Content-Type: application/json', 'Content-Type: application/json',
'User-Agent: https://thinkadmin.top', 'User-Agent: https://thinkadmin.top',
"Authorization: WECHATPAY2-SHA256-RSA2048 {$token}", "Authorization: WECHATPAY2-SHA256-RSA2048 {$token}",
"Wechatpay-Serial: {$this->config['cert_serial']}" "Wechatpay-Serial: {$this->config['mp_cert_serial']}"
], ],
]); ]);
@ -242,29 +255,77 @@ abstract class BasicWePay
* @throws \WeChat\Exceptions\InvalidResponseException * @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException * @throws \WeChat\Exceptions\LocalCacheException
*/ */
protected function signVerify($data, $sign, $serial = '') protected function signVerify($data, $sign, $serial)
{ {
$cert = $this->tmpFile($serial); $cert = $this->_getCert($serial);
if (empty($cert)) {
Cert::instance($this->config)->download();
$cert = $this->tmpFile($serial);
}
return @openssl_verify($data, base64_decode($sign), openssl_x509_read($cert), 'sha256WithRSAEncryption'); return @openssl_verify($data, base64_decode($sign), openssl_x509_read($cert), 'sha256WithRSAEncryption');
} }
/**
* 获取平台证书
* @param string $serial
* @return string
* @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException
*/
protected function _getCert($serial = '')
{
$certs = $this->tmpFile("{$this->config['mch_id']}_certs");
if (empty($certs) || empty($certs[$serial]['content'])) {
Cert::instance($this->config)->download();
$certs = $this->tmpFile("{$this->config['mch_id']}_certs");
}
if (empty($certs[$serial]['content']) || $certs[$serial]['expire'] < time()) {
throw new InvalidResponseException("读取平台证书失败!");
} else {
return $certs[$serial]['content'];
}
}
/**
* 自动配置平台证书
* @return void
* @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException
*/
protected function _autoCert()
{
$certs = $this->tmpFile("{$this->config['mch_id']}_certs");
if (is_array($certs)) foreach ($certs as $k => $v) if ($v['expire'] < time()) unset($certs[$k]);
if (empty($certs)) {
Cert::instance($this->config)->download();
$certs = $this->tmpFile("{$this->config['mch_id']}_certs");
}
if (empty($certs) || !is_array($certs)) {
throw new InvalidResponseException("读取平台证书失败!");
}
foreach ($certs as $k => $v) if ($v['expire'] > time() + 10) {
$this->config['mp_cert_serial'] = $k;
$this->config['mp_cert_content'] = $v['content'];
break;
}
if (empty($this->config['mp_cert_serial']) || empty($this->config['mp_cert_content'])) {
throw new InvalidResponseException("自动配置平台证书失败!");
}
}
/** /**
* 写入或读取临时文件 * 写入或读取临时文件
* @param string $name * @param string $name
* @param null|string $content * @param null|array|string $content
* @return string * @param integer $expire
* @return array|string
* @throws \WeChat\Exceptions\LocalCacheException * @throws \WeChat\Exceptions\LocalCacheException
*/ */
protected function tmpFile($name, $content = null) protected function tmpFile($name, $content = null, $expire = 7200)
{ {
if (is_null($content)) { if (is_null($content)) {
return base64_decode(Tools::getCache($name) ?: ''); $text = Tools::getCache($name);
if (empty($text)) return '';
$json = json_decode(Tools::getCache($name) ?: '', true);
return isset($json[0]) ? $json[0] : '';
} else { } else {
return Tools::setCache($name, base64_encode($content), 7200); return Tools::setCache($name, json_encode([$content], JSON_UNESCAPED_UNICODE), $expire);
} }
} }
@ -276,27 +337,11 @@ abstract class BasicWePay
*/ */
protected function rsaEncode($string) protected function rsaEncode($string)
{ {
$publicKey = file_get_contents($this->config['cert_public']); $publicKey = file_get_contents($this->config['mp_cert_content']);
if (openssl_public_encrypt($string, $encrypted, $publicKey, OPENSSL_PKCS1_OAEP_PADDING)) { if (openssl_public_encrypt($string, $encrypted, $publicKey, OPENSSL_PKCS1_OAEP_PADDING)) {
return base64_encode($encrypted); return base64_encode($encrypted);
} else { } else {
throw new InvalidDecryptException('Rsa Encrypt Error.'); throw new InvalidDecryptException('Rsa Encrypt Error.');
} }
} }
/**
* RSA 解密处理
* @param string $string
* @return string
* @throws \WeChat\Exceptions\InvalidDecryptException
*/
protected function rsaDecode($string)
{
$private = file_get_contents($this->config['cert_private']);
if (openssl_private_decrypt(base64_decode($string), $content, $private, OPENSSL_PKCS1_OAEP_PADDING)) {
return $content;
} else {
throw new InvalidDecryptException('Rsa Decrypt Error.');
}
}
} }