Skip to content

Commit fc62209

Browse files
committed
fix: 增加自定义批量证书配置,支持平台证书及支付证书
1 parent d0955ea commit fc62209

File tree

2 files changed

+170
-97
lines changed

2 files changed

+170
-97
lines changed

WePayV3/Contracts/BasicWePay.php

Lines changed: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ abstract class BasicWePay
3939
* 实例对象静态缓存
4040
* @var array
4141
*/
42-
static $cache = [];
42+
protected static $cache = [];
4343

4444
/**
4545
* 自动配置平台证书
@@ -58,8 +58,9 @@ abstract class BasicWePay
5858
'cert_serial' => '', // 商户证书序号,无需配置
5959
'cert_public' => '', // 商户公钥内容,需要配置
6060
'cert_private' => '', // 商户密钥内容,需要配置
61-
'mp_cert_serial' => '', // 平台证书序号,无需配置 ( 新平台,当做微信支付证书用 )
62-
'mp_cert_content' => '', // 平台证书内容,无需配置 ( 新平台,当做微信支付证书用 )
61+
'cert_package' => [], // 平台证书或支付证书配置
62+
'mp_cert_serial' => '', // 平台证书序号,无需配置 ( 指定平台证书或支付公钥 )
63+
'mp_cert_content' => '', // 平台证书内容,无需配置 ( 指定平台证书或支付公钥 )
6364
];
6465

6566
/**
@@ -117,10 +118,19 @@ public function __construct(array $options = [])
117118
Tools::$cache_path = $options['cache_path'];
118119
}
119120

120-
// 自动配置平台证书
121+
// 批量设置自定义证书
122+
if (isset($options['cert_package']) && is_array($options['cert_package'])) {
123+
foreach ($options['cert_package'] as $key => $cert) {
124+
$this->withCertContent($key, $cert);
125+
}
126+
}
127+
128+
// 自动配置平台证书或支付公钥
121129
if (empty($options['mp_cert_serial']) || empty($options['mp_cert_content'])) {
122-
if ($this->autoCert) $this->_autoCert();
123-
} else {
130+
if ($this->autoCert && !$this->withCertPayment()) {
131+
$this->_autoCert();
132+
}
133+
} elseif ($this->withCertContent($options['mp_cert_serial'], $options['mp_cert_content'])) {
124134
$this->config['mp_cert_serial'] = $options['mp_cert_serial'];
125135
$this->config['mp_cert_content'] = $options['mp_cert_content'];
126136
}
@@ -340,10 +350,10 @@ protected function signBuild($data)
340350
*/
341351
protected function signVerify($data, $sign, $serial)
342352
{
343-
if (stripos($this->config['mp_cert_serial'], 'PUB_KEY_ID_') !== false) {
344-
return @openssl_verify($data, base64_decode($sign), $this->config['mp_cert_content'], OPENSSL_ALGO_SHA256);
353+
if (stripos($serial, 'PUB_KEY_ID_') !== false && !empty($this->config['cert_package'][$serial])) {
354+
return openssl_verify($data, base64_decode($sign), $this->config['cert_package'][$serial], OPENSSL_ALGO_SHA256);
345355
} else {
346-
return @openssl_verify($data, base64_decode($sign), openssl_x509_read($this->_getCert($serial)), 'sha256WithRSAEncryption');
356+
return openssl_verify($data, base64_decode($sign), openssl_x509_read($this->_getCert($serial)), 'sha256WithRSAEncryption');
347357
}
348358
}
349359

@@ -361,11 +371,31 @@ protected function _getCert($serial = '')
361371
Cert::instance($this->config)->download();
362372
$certs = $this->tmpFile("{$this->config['mch_id']}_certs");
363373
}
364-
if (empty($certs[$serial]['content']) || $certs[$serial]['expire'] < time()) {
374+
foreach ($certs as $cert) {
375+
if ($certs[$serial]['expire'] > time()) {
376+
$this->config['cert_package'][$cert['serial']] = $cert['content'];
377+
if (empty($this->config['mp_cert_serial'])) {
378+
$this->config['mp_cert_serial'] = $cert['serial'];
379+
$this->config['mp_cert_content'] = $cert['content'];
380+
}
381+
}
382+
}
383+
// 未设置序号时,直接返回默认证书内容
384+
if (empty($serial) && !empty($this->config['mp_cert_content'])) {
385+
return $this->config['mp_cert_content'];
386+
}
387+
388+
// 遍历证书数组,找到匹配的证书
389+
if ($cert = $this->withCertPayment()) {
390+
return $cert;
391+
}
392+
393+
// 检查指定序号的证书是否存在
394+
if (!isset($this->config['cert_package'][$serial])) {
365395
throw new InvalidResponseException("读取平台证书失败!");
366-
} else {
367-
return $certs[$serial]['content'];
368396
}
397+
398+
return $this->config['cert_package'][$serial];
369399
}
370400

371401
/**
@@ -388,11 +418,13 @@ protected function _autoCert()
388418
throw new InvalidResponseException("读取平台证书失败!");
389419
}
390420
foreach ($certs as $k => $v) if ($v['expire'] > time() + 10) {
391-
$this->config['mp_cert_serial'] = $k;
392-
$this->config['mp_cert_content'] = $v['content'];
393-
break;
421+
$this->config['cert_package'][$k] = $v['content'];
422+
if (empty($this->config['mp_cert_serial'])) {
423+
$this->config['mp_cert_serial'] = $k;
424+
$this->config['mp_cert_content'] = $v['content'];
425+
}
394426
}
395-
if (empty($this->config['mp_cert_serial']) || empty($this->config['mp_cert_content'])) {
427+
if (empty($this->config['cert_package'])) {
396428
throw new InvalidResponseException("自动配置平台证书失败!");
397429
}
398430
}
@@ -432,4 +464,41 @@ protected function rsaEncode($string)
432464
throw new InvalidDecryptException('Rsa Encrypt Error.');
433465
}
434466
}
467+
468+
/**
469+
* 设置证书内容
470+
* @param string $key 证书ID或序号
471+
* @param string $cert 证书文本内容
472+
* @return string
473+
* @throws \WeChat\Exceptions\InvalidResponseException
474+
*/
475+
private function withCertContent($key, $cert)
476+
{
477+
if (substr(trim($cert), 0, 5) == '-----') {
478+
$this->config['cert_package'][$key] = $cert;
479+
} elseif (file_exists($cert)) {
480+
$this->config['cert_package'][$key] = file_get_contents($cert);
481+
} else {
482+
throw new InvalidResponseException("证书设置失败!");
483+
}
484+
return $cert;
485+
}
486+
487+
/**
488+
* 获取支付证书
489+
* @return mixed|string
490+
*/
491+
private function withCertPayment()
492+
{
493+
foreach ($this->config['cert_package'] as $key => $cert) {
494+
if (strpos($key, 'PUB_KEY_ID_') === 0) {
495+
if (empty($this->config['mp_cert_serial']) || empty($this->config['mp_cert_content'])) {
496+
$this->config['mp_cert_serial'] = $key;
497+
$this->config['mp_cert_content'] = $cert;
498+
}
499+
return $cert;
500+
}
501+
}
502+
return '';
503+
}
435504
}

_test/pay-v3-config.php

Lines changed: 85 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,86 @@
1-
<?php
2-
3-
// +----------------------------------------------------------------------
4-
// | WeChatDeveloper
5-
// +----------------------------------------------------------------------
6-
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
7-
// +----------------------------------------------------------------------
8-
// | 官方网站: https://thinkadmin.top
9-
// +----------------------------------------------------------------------
10-
// | 开源协议 ( https://mit-license.org )
11-
// | 免责声明 ( https://thinkadmin.top/disclaimer )
12-
// +----------------------------------------------------------------------
13-
// | gitee 代码仓库:https://gitee.com/zoujingli/WeChatDeveloper
14-
// | github 代码仓库:https://github.com/zoujingli/WeChatDeveloper
15-
// +----------------------------------------------------------------------
16-
17-
$certPublic = <<<CERT
18-
-----BEGIN CERTIFICATE-----
19-
文件文本内容
20-
-----END CERTIFICATE-----
21-
CERT;
22-
23-
$certPrivate = <<<CERT
24-
-----BEGIN PRIVATE KEY-----
25-
文件文本内容
26-
-----END PRIVATE KEY-----
27-
CERT;
28-
29-
// 支付证书内容
30-
$certPayment = <<<CERT
31-
-----BEGIN PUBLIC KEY-----
32-
文件文本内容
33-
-----END PUBLIC KEY-----
34-
CERT;
35-
36-
37-
// =====================================================
38-
// 配置缓存处理函数 ( 适配其他环境 )
39-
// -----------------------------------------------------
40-
// 数据缓存 (set|get|del) 操作可以将缓存写到任意位置或Redis
41-
// 文件缓存 (put) 只能写在本地服务器,还需要返回可读的文件路径
42-
// 未配置自定义缓存处理机制时,默认在 cache_path 写入文件缓存
43-
// // =====================================================
44-
// \WeChat\Contracts\Tools::$cache_callable = [
45-
// 'set' => function ($name, $value, $expired = 360) {
46-
// var_dump(func_get_args());
47-
// return $value;
48-
// },
49-
// 'get' => function ($name) {
50-
// var_dump(func_get_args());
51-
// return $value;
52-
// },
53-
// 'del' => function ($name) {
54-
// var_dump(func_get_args());
55-
// return true;
56-
// },
57-
// 'put' => function ($name) {
58-
// var_dump(func_get_args());
59-
// return $filePath;
60-
// },
61-
// ];
62-
63-
return [
64-
// 可选,公众号APPID
65-
'appid' => 'wx3760axxxxxxxxxxx',
66-
// 必填,微信商户编号ID
67-
'mch_id' => '152xxxxxxxx',
68-
// 必填,微信商户V3接口密钥,不影响发起支付但无法验证支付通知
69-
'mch_v3_key' => '98b7f45xxxxxxxxxxxxxxxxxxxxxxxxxx',
70-
// 可选,微信商户证书序列号,可从公钥中提取,请求签名使用
71-
'cert_serial' => '49055xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
72-
// 必填,微信商户证书公钥,支持证书内容或文件路径,仅用于提取序号
73-
'cert_public' => $certPublic,
74-
// 必填,微信商户证书私钥,支持证书内容或文件路径,用于请求数据签名
75-
'cert_private' => $certPrivate,
76-
// 可选,微信平台证书序号或支付证书序号,用于接口请求序号
77-
'mp_cert_serial' => 'PUB_KEY_ID_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
78-
// 可选,微信平台证书内容或支付证书内容
79-
'mp_cert_content' => $certPayment,
80-
// 可选,运行时的文件缓存路径
81-
'cache_path' => ''
1+
<?php
2+
3+
// +----------------------------------------------------------------------
4+
// | WeChatDeveloper
5+
// +----------------------------------------------------------------------
6+
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
7+
// +----------------------------------------------------------------------
8+
// | 官方网站: https://thinkadmin.top
9+
// +----------------------------------------------------------------------
10+
// | 开源协议 ( https://mit-license.org )
11+
// | 免责声明 ( https://thinkadmin.top/disclaimer )
12+
// +----------------------------------------------------------------------
13+
// | gitee 代码仓库:https://gitee.com/zoujingli/WeChatDeveloper
14+
// | github 代码仓库:https://github.com/zoujingli/WeChatDeveloper
15+
// +----------------------------------------------------------------------
16+
17+
$certPublic = <<<CERT
18+
-----BEGIN CERTIFICATE-----
19+
文件文本内容
20+
-----END CERTIFICATE-----
21+
CERT;
22+
23+
$certPrivate = <<<CERT
24+
-----BEGIN PRIVATE KEY-----
25+
文件文本内容
26+
-----END PRIVATE KEY-----
27+
CERT;
28+
29+
// 支付证书内容
30+
$certPayment = <<<CERT
31+
-----BEGIN PUBLIC KEY-----
32+
文件文本内容
33+
-----END PUBLIC KEY-----
34+
CERT;
35+
36+
37+
// =====================================================
38+
// 配置缓存处理函数 ( 适配其他环境 )
39+
// -----------------------------------------------------
40+
// 数据缓存 (set|get|del) 操作可以将缓存写到任意位置或Redis
41+
// 文件缓存 (put) 只能写在本地服务器,还需要返回可读的文件路径
42+
// 未配置自定义缓存处理机制时,默认在 cache_path 写入文件缓存
43+
// // =====================================================
44+
// \WeChat\Contracts\Tools::$cache_callable = [
45+
// 'set' => function ($name, $value, $expired = 360) {
46+
// var_dump(func_get_args());
47+
// return $value;
48+
// },
49+
// 'get' => function ($name) {
50+
// var_dump(func_get_args());
51+
// return $value;
52+
// },
53+
// 'del' => function ($name) {
54+
// var_dump(func_get_args());
55+
// return true;
56+
// },
57+
// 'put' => function ($name) {
58+
// var_dump(func_get_args());
59+
// return $filePath;
60+
// },
61+
// ];
62+
63+
return [
64+
// 可选,公众号APPID
65+
'appid' => 'wx3760xxxxxxxxxxxx',
66+
// 必填,微信商户编号ID
67+
'mch_id' => '15293xxxxxx',
68+
// 必填,微信商户V3接口密钥
69+
'mch_v3_key' => '98b7fxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
70+
// 可选,微信商户证书序列号,可从公钥中提取,请求签名使用
71+
'cert_serial' => '49055D67B2XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
72+
// 必填,微信商户证书公钥,支持证书内容或文件路径,仅用于提取序号
73+
'cert_public' => $certPublic,
74+
// 必填,微信商户证书私钥,支持证书内容或文件路径,用于请求数据签名
75+
'cert_private' => $certPrivate,
76+
// 批量设置自定义证书内容,支持平台证书或支付公钥,可填写文件路径及内容
77+
'cert_package' => [
78+
'PUB_KEY_ID_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' => $certPayment
79+
],
80+
// 可选,微信平台证书序号或支付证书序号,直接支持平台证书或支付公钥
81+
// 'mp_cert_serial' => 'PUB_KEY_ID_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
82+
// 可选,微信平台证书内容或支付证书内容,直接支持平台证书或支付公钥
83+
// 'mp_cert_content' => $certPayment,
84+
// 可选,运行时的文件缓存路径
85+
'cache_path' => ''
8286
];

0 commit comments

Comments
 (0)