Compare commits

...

6 Commits

Author SHA1 Message Date
邹景立
e72641a5ae 修改版本号 2023-06-19 14:20:18 +08:00
邹景立
cc460dbdba Update BasicWePay.php 2023-06-19 14:17:22 +08:00
邹景立
a629b2d676 Update BasicWePay.php 2023-06-19 14:11:56 +08:00
邹景立
b84cc6d54f Update BasicWePay.php 2023-06-19 14:04:13 +08:00
邹景立
9f236c374c 增加批量打款测试案例 2023-06-19 14:02:21 +08:00
邹景立
b9c4823486 修改平台证书机制
#
2023-06-19 13:49:47 +08:00
5 changed files with 160 additions and 43 deletions

2
We.php
View File

@ -93,7 +93,7 @@ class We
* 定义当前版本
* @var string
*/
const VERSION = '1.2.37';
const VERSION = '1.2.47';
/**
* 静态配置

View File

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

View File

@ -41,22 +41,32 @@ abstract class BasicWePay
*/
static $cache = [];
/**
* 自动配置平台证书
* @var bool
*/
protected $autoCert = true;
/**
* 配置参数
* @var array
*/
protected $config = [
'appid' => '', // 微信绑定APPID需配置
'mch_id' => '', // 微信商户编号,需要配置
'mch_v3_key' => '', // 微信商户密钥,需要配置
'cert_serial' => '', // 商户证书序号,无需配置
'cert_public' => '', // 商户公钥内容,需要配置
'cert_private' => '', // 商户密钥内容,需要配置
'appid' => '', // 微信绑定APPID需配置
'mch_id' => '', // 微信商户编号,需要配置
'mch_v3_key' => '', // 微信商户密钥,需要配置
'cert_serial' => '', // 商户证书序号,无需配置
'cert_public' => '', // 商户公钥内容,需要配置
'cert_private' => '', // 商户密钥内容,需要配置
'mp_cert_serial' => '', // 平台证书序号,无需配置
'mp_cert_content' => '', // 平台证书内容,无需配置
];
/**
* BasicWePayV3 constructor.
* @param array $options [mch_id, mch_v3_key, cert_public, cert_private]
* @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException
*/
public function __construct(array $options = [])
{
@ -94,7 +104,6 @@ abstract class BasicWePay
$this->config['mch_v3_key'] = $options['mch_v3_key'];
$this->config['cert_public'] = $options['cert_public'];
$this->config['cert_private'] = $options['cert_private'];
if (empty($options['cert_serial'])) {
$this->config['cert_serial'] = openssl_x509_parse($this->config['cert_public'], true)['serialNumberHex'];
} else {
@ -108,6 +117,11 @@ abstract class BasicWePay
Tools::$cache_path = $options['cache_path'];
}
// 自动配置平台证书
if ($this->autoCert) {
$this->_autoCert();
}
// 服务商参数支持
// if (!empty($options['sp_appid'])) {
// $this->config['sp_appid'] = $options['sp_appid'];
@ -127,6 +141,8 @@ abstract class BasicWePay
* 静态创建对象
* @param array $config
* @return static
* @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException
*/
public static function instance($config)
{
@ -161,7 +177,7 @@ abstract class BasicWePay
'Content-Type: application/json',
'User-Agent: https://thinkadmin.top',
"Authorization: WECHATPAY2-SHA256-RSA2048 {$token}",
"Wechatpay-Serial: {$this->config['cert_serial']}"
"Wechatpay-Serial: {$this->config['mp_cert_serial']}"
],
]);
@ -242,61 +258,95 @@ abstract class BasicWePay
* @throws \WeChat\Exceptions\InvalidResponseException
* @throws \WeChat\Exceptions\LocalCacheException
*/
protected function signVerify($data, $sign, $serial = '')
protected function signVerify($data, $sign, $serial)
{
$cert = $this->tmpFile($serial);
if (empty($cert)) {
Cert::instance($this->config)->download();
$cert = $this->tmpFile($serial);
}
$cert = $this->_getCert($serial);
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 null|string $content
* @return string
* @param null|array|string $content
* @param integer $expire
* @return array|string
* @throws \WeChat\Exceptions\LocalCacheException
*/
protected function tmpFile($name, $content = null)
protected function tmpFile($name, $content = null, $expire = 7200)
{
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 {
return Tools::setCache($name, base64_encode($content), 7200);
return Tools::setCache($name, json_encode([$content], JSON_UNESCAPED_UNICODE), $expire);
}
}
/**
* RSA加密处理
* RSA加密处理-平台证书
* @param string $string
* @return string
* @throws \WeChat\Exceptions\InvalidDecryptException
*/
protected function rsaEncode($string)
{
$publicKey = file_get_contents($this->config['cert_public']);
$publicKey = $this->config['mp_cert_content'];
if (openssl_public_encrypt($string, $encrypted, $publicKey, OPENSSL_PKCS1_OAEP_PADDING)) {
return base64_encode($encrypted);
} else {
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.');
}
}
}

View File

@ -31,14 +31,21 @@ class Transfers extends BasicWePay
* @return array
* @throws \WeChat\Exceptions\InvalidDecryptException
* @throws \WeChat\Exceptions\InvalidResponseException
* @link https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_1.shtml
*/
public function batchs($body)
{
if (empty($body['appid'])) {
$body['appid'] = $this->config['appid'];
}
if (isset($body['transfer_detail_list']) && is_array($body['transfer_detail_list'])) {
foreach ($body['transfer_detail_list'] as &$item) if (isset($item['user_name'])) {
$item['user_name'] = $this->rsaEncode($item['user_name']);
}
}
if (empty($body['total_num'])) {
$body['total_num'] = count($body['transfer_detail_list']);
}
return $this->doRequest('POST', '/v3/transfer/batches', json_encode($body, JSON_UNESCAPED_UNICODE), true);
}

48
_test/pay-v3-transfer.php Normal file
View File

@ -0,0 +1,48 @@
<?php
// +----------------------------------------------------------------------
// | WeChatDeveloper
// +----------------------------------------------------------------------
// | 版权所有 2014~2023 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/WeChatDeveloper
// | github 代码仓库https://github.com/zoujingli/WeChatDeveloper
// +----------------------------------------------------------------------
try {
// 1. 手动加载入口文件
include "../include.php";
// 2. 准备公众号配置参数
$config = include "./pay-v3-config.php";
$pay = \WePayV3\Transfers::instance($config);
$result = $pay->batchs([
'out_batch_no' => 'plfk2020042013',
'batch_name' => '2019年1月深圳分部报销单',
'batch_remark' => '2019年1月深圳分部报销单',
'total_amount' => 100,
'transfer_detail_list' => [
[
'out_detail_no' => 'x23zy545Bd5436',
'transfer_amount' => 100,
'transfer_remark' => '2020年4月报销',
'openid' => 'o-MYE42l80oelYMDE34nYD456Xoy',
'user_name' => '小小邹'
]
]
]);
echo "\n--- 批量打款 ---\n";
var_export($result);
} catch (\Exception $exception) {
// 出错啦,处理下吧
echo $exception->getMessage() . PHP_EOL;
}