暂时看了文档只完成了这部分,感觉封装的不是特别完美,希望有大佬指点一下,想着封装好一个SDK直接在其他项目中使用,不太会
V3Base
namespace app\lib\pay\weixinV3\lib;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
class V3Base
{
protected $client = null;
protected $weChatPayConfig = [];
public function __construct()
{
$this->weChatPayConfig = config("pay.weixin");
//商户相关配置,
$merchantId = $this->weChatPayConfig['merchantId']; // 商户号
$merchantSerialNumber = $this->weChatPayConfig['merchantSerialNumber']; // 商户API证书序列号
$merchantPrivateKey = PemUtil::loadPrivateKey($this->weChatPayConfig['merchantPrivateKey']); // 商户私钥文件路径
// 微信支付平台配置
$wechatpayCertificate = PemUtil::loadCertificate($this->weChatPayConfig['platformCert']); // 微信支付平台证书文件路径
// 构造一个WechatPayMiddleware
$wechatpayMiddleware = WechatPayMiddleware::builder()
->withMerchant($merchantId, $merchantSerialNumber, $merchantPrivateKey) // 传入商户相关配置
->withWechatPay([$wechatpayCertificate]) // 可传入多个微信支付平台证书,参数类型为array
->build();
// 将WechatPayMiddleware添加到Guzzle的HandlerStack中
$stack = HandlerStack::create();
$stack->push($wechatpayMiddleware, 'wechatpay');
// 创建Guzzle HTTP Client时,将HandlerStack传入,接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
$client = new Client(['handler' => $stack, 'verify' => false]);
$this->client = $client;
}
/**
* Desc:签名生成 @serial_no 商户API证书serial_no,用于声明所使用的证书,第一次需要执行官方jar包获取
* User: XiaoYu
* Date: 2021/3/31
* Time: 8:52
* @param $url 请求的接口
* @param $http_method 请求方式
* @param $timestamp 时间戳
* @param $nonce 随机串
* @param string $body 报文主体
* @return array
*/
public function signGeneration($url, $http_method, $timestamp, $nonce, $body = "")
{
$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, PemUtil::loadPrivateKey($this->weChatPayConfig['merchantPrivateKey']), 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);
$schema = 'WECHATPAY2-SHA256-RSA2048';
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
$this->weChatPayConfig['merchantId'], $nonce, $timestamp, $this->weChatPayConfig['serial_no'], $sign);
$header = [
"Content-Type" => "application/json",
"Accept" => "application/json",
"User-Agent" => "*/*",
"Authorization" => $schema . ' ' . $token
];
return $header;
}
}
获取平台证书
namespace app\lib\pay\weixinV3\lib;
use app\lib\Str;
/**
* 平台证书
* @package app\lib\pay\weixinV3\lib
*/
class Certificates extends V3Base
{
/**
* Desc: 获取平台证书 定时每12小时执行-》替换证书号以及serial_no
* User: XiaoYu
* Date: 2021/3/31
* Time: 9:58
* @return string
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function getCertificates()
{
$url = "https://api.mch.weixin.qq.com/v3/certificates";
$method = "GET";
$timestamp = time();
$nonce = Str::createNoncestr();
$resp = $this->client->request(
$method,
$url,
[
'headers' => $this->signGeneration($url, $method, $timestamp, $nonce)
]
);
$cert = json_decode($resp->getBody()->getContents(), true);
$encrypt_certificate = $cert['data'][0]['encrypt_certificate'];
$AesUtil = new AesUtil($this->weChatPayConfig['apiV3']);
$weChatPayPem = $AesUtil->decryptToString($encrypt_certificate['associated_data'], $encrypt_certificate['nonce'], $encrypt_certificate['ciphertext']);
return $weChatPayPem;
}
}
回调报文解密
namespace app\lib\pay\weixinV3\lib;
use InvalidArgumentException;
/**
* 回调报文解密
* @package app\lib\pay\weixinV3\lib
*/
class AesUtil
{
/**
* AES key
*
* @var string
*/
private $aesKey;
const KEY_LENGTH_BYTE = 32;
const AUTH_TAG_LENGTH_BYTE = 16;
/**
* Constructor
*/
public function __construct($aesKey)
{
if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
}
$this->aesKey = $aesKey;
}
/**
* Decrypt AEAD_AES_256_GCM ciphertext
*
* @param string $associatedData AES GCM additional authentication data
* @param string $nonceStr AES GCM nonce
* @param string $ciphertext AES GCM cipher text
*
* @return string|bool Decrypted string on success or FALSE on failure
*/
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');
}
}
统一下单
namespace app\lib\pay\weixinV3\lib;
use app\lib\Str;
use GuzzleHttp\Exception\RequestException;
use think\Exception;
/**
* 统一下单接口-》按照官方文档自行组装数据
* Class UnifiedOrder
* @package app\lib\pay\weixinV3\lib
*/
class UnifiedOrder extends V3Base
{
public function UnifiedOrder($data)
{
switch ($data['trade_type']) {
case "JSAPI":
$url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
break;
case "APP":
$url = "https://api.mch.weixin.qq.com/v3/pay/transactions/app";
break;
case "NATIVE":
$url = "https://api.mch.weixin.qq.com/v3/pay/transactions/native";
break;
case "H5":
$url = "https://api.mch.weixin.qq.com/v3/pay/transactions/app";
break;
default:
throw new Exception("该模式暂时未接入");
}
unset($data['trade_type']);
$data['mchid'] = $this->weChatPayConfig['merchantId'];
$data['notify_url'] = $this->weChatPayConfig['notify'];
$data['appid'] = $this->weChatPayConfig['appid'];
$body = json_encode($data);
try {
$resp = $this->client->request(
'POST',
$url, //请求URL
[
// JSON请求体
'json' => $data,
'headers' => $this->signGeneration($url, "POST", time(), Str::createNoncestr(), $body)
]
);
$statusCode = $resp->getStatusCode();
if ($statusCode == 200) { //处理成功
return ["code_url" => $resp->getBody()->getContents()];
} else if ($statusCode == 204) { //处理成功,无返回Body
return ["result" => "success"];
}
} catch (RequestException $e) {
throw $e;
}
}
}