准备工作:
PHP版本>=7.3
appid // 应用id
merchantId // 商户id
v3_key // v3支付秘钥
serial_no // api证书序列号
merchantPrivateKey // 商户私钥证书
wechatpayCertificate // 商户公钥证书
1.下载官方sdk并添加依赖
sdk下载地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay6_0.shtml
composer require wechatpay/wechatpay-guzzle-middleware // 下载完成后在项目目录下执行
// 或者 在项目的composer.json中加入以下配置
"require": {
"wechatpay/wechatpay-guzzle-middleware": "^0.2.0"
}
// 之后执行安装
composer install
2.获取签名字符串
官方地址: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_0.shtml
// 组合数据
GET\n // 提交方式
/v3/certificates\n // 签名接口 固定
1554208460\n // 时间戳
593BEC0C930BF1AFEB40B4A08C8FB242\n // 随机字符串 32位 数字+大写字母
// 执行命令获得签名字符串
echo -n -e \
"GET\n/v3/certificates\n1554208460\n593BEC0C930BF1AFEB40B4A08C8FB242\n\n" \
| openssl dgst -sha256 -sign apiclient_key.pem \
| openssl base64 -A
3.生成加密报文
// 需要参数
mchid // 商户id
serial_no // 证书序列号
nonce_str // 随机数
timestamp // 时间戳
signature // 上一步获取的签名字符串
// 执行命令获取签名
curl https://api.mch.weixin.qq.com/v3/certificates -H 'Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1900009191",nonce_str="593BEC0C930BF1AFEB40B4A08C8FB242",signature="uOVRnA4qG/MNnYzdQxJanN+zU+lTgIcnU9BxGw5dKjK+VdEUz2FeIoC+D5sB/LN+nGzX3hfZg6r5wT1pl2ZobmIc6p0ldN7J6yDgUzbX8Uk3sD4a4eZVPTBvqNDoUqcYMlZ9uuDdCvNv4TM3c1WzsXUrExwVkI1XO5jCNbgDJ25nkT/c1gIFvqoogl7MdSFGc4W4xZsqCItnqbypR3RuGIlR9h9vlRsy7zJR9PBI83X8alLDIfR1ukt1P7tMnmogZ0cuDY8cZsd8ZlCgLadmvej58SLsIkVxFJ8XyUgx9FmutKSYTmYtWBZ0+tNvfGmbXU7cob8H/4nLBiCwIUFluw==",timestamp="1554208460",serial_no="1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C"'
3.1 获取证书序列号
方法1. 登陆商户平台【API安全】->【API证书】->【查看证书】,可查看商户API证书序列号
方法2. 通过命令行 openssl x509 -in 公钥证书地址 -noout -serial
得到 serial=1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C
方法3. 通过PHP方法 openssl_x509_read(file_get_contents($filepath));
方法4. 通过解析工具 工具地址:https://myssl.com/cert_decode.html 上传公钥证书
正确返回后可得如下参数:
{
"data": [
{
"effective_time": "2020-10-21T10:15:24+08:00",
"encrypt_certificate": {
"algorithm": "AEAD_AES_256_GCM", // 加密算法
"associated_data": "certificate", // 附加数据包(可能为空)
"ciphertext": "XvGG+ezFmogjOojaNWUvjLmMLl0Y0MZCWr9bp9dm5Pvv9WxdHswPb7K8ynrattd51rg/m9hw1+S51mkKz63kMfjDdR03G5M1rnEog9Aq0ITbqc3nAxGh8EsTh0jd6StGefOkVqCKydUuSg5IJpL3cqB0hz6ILrTp96V7Cb+id9z9R+rr2QUe05o8u3Uxone7VezSm3qzj4/Gberb1irVuqbqkaMxCGH4XU+PeMXds5DeYHhM23HxuOmyD4b1sSg/YuUWqU1XmBwZdgt/X/eykWixfnwfB3r8LxYEisu5uHev6PgFECnq0oeiTHiEiIFNLLme5mqCyise+R7i1XwFQXiguQLeYOVTmS0vjwdFVvu6QJfxn0FPd6qPc0Kb3D5+xZsEMrxuvGT0sjqix2Df/o/+j8W4fr6gMOlge8EVB4UWni4fVT14zp53OTFQuhnw3kmSSUztVnVhYwwaWvVLnHkxMm87Va3dKubvO/O2TvutmDmgGPydpvqgXdcWBd1E/kYk/kRB+V6/M5sCYb7QM3HiQvdKBxbnuaQCBrnZ5tHPZdxpqmxsh7ZoKLqsTSUXYyMs+rRr1LKpEUbtUNtG3aIwpNNaQe3SU8cpw0QhFMTS4UPOErfY2pL9h/oPKtq4SG6hZtwsJdfXgSfQtBv1fhngHSdoMsG4KNGQAIwT4utmzu07LLYBQg5g0bznEQ+OHisDlDUI1w2lwbLTei8LE9o/rdV5Sty9VHHR1e8CqzuY84cnxlHa2NBmtIF47q5tpnNYHznODjWvWIOtKOY4Z83t7qgyX9S355+9vQYXin4mxiPTnhrmeoBI8Us4AeUsCqI9qkozbnyjkHQ4jdW8LJnUcKj0suseuVbEykN0OsFYplQ2Z1S62EPn8034KFVCaCLbJKrqkbBKQ9rSoDdYtgug780q4akT81jBxIYv7fx7ARRh8rOsSTSdhOjc6rnRoLBdsp4+zGoAFPjiIXieBAO8bpw/nlnWR60evcnvbEFeusvMHAEqjbdMN/nIB6Y6Ii4LYziOpc8nVlPd18rAOVa6CpuIpRXgUdNQl2G9KOpSJ3jTYJSG+jwdQHowQLp9aUdCA2YpGf4pMMgRz8yhK+2J0d2eqALnYog/5AuWjs8BF65odMxKYQ9WUSjei/x4kFbAPxd6fPnvtnFVk+Qo7IZ47N/5RRoBCEpDxK0dLY7uJF1gblKbWWbn+c0bQqzVkfhCBObOvP/u9YiDnuhHD6yBOkYhkAtNeamITNie7KN3/XqlbVmh9TJkaYzlLw8PGQ1K4La6q9wBdE95IKInsrIFc9urrF8dn2f7v/k3iaCUf9y10BD6K21zoOpIUN55Eh5s+e54Nh/1G5gVT3fbA2nZ3/Bs8jbkyIpplRzzG75zX5flLJuxBmJPblhpLJqpCWZeXcBOXe2SiDQTwpnDqyKpxPXqYQWKVc+cxaekc1iSBs/g6C51lkinGMTn/npbTuI6No01v/JA925+MFLRxBWZ6Lsd3WBQZVqgbLpNre6/Bm63lMkA4NfBcdfhd9ffhHKuol2hRLzhIPSnrEFQgsx8tkUASjYIEKR+IBJZ3vlmCjBYPJJZUbal0qj4Ing0JWEoZh28ehOs1+7CBZWTbapPe/M887PPsDQP5gN6Jjn19PubCwOPP7NxCUm0rrAzaY17QN7oIE26uSp1ZRD4j/2v6Lhzhu+X7qD7FYtqEo54Fg1ZqcZ4vDrAdidkrfjNupyKytnTacjM3ZWusHNB1DzqImlx82/3aZDnhiMv1nySjjO2zdkapl5OIxjERhyeC6anQqDprSFRtU3KqudA6/tfAUfN0g9RaUlDmz0vv8Tm29uo9/ThCSscMi73npYzrR8HvUS4pvg2slzJJtV5IYnZSGsD0w==", // Base64编码后的密文
"nonce": "5a45b7152b39" // 加密使用的随机串初始化向量
},
"expire_time": "2025-10-20T10:15:24+08:00", // 过期时间
"serial_no": "6AD4207FA35A4AB5CE3BC166AD08F58BEBFD4A4B"
}
]
}
4.解密报文 获得证书
const KEY_LENGTH_BYTE = 32;
const AUTH_TAG_LENGTH_BYTE = 16;
public function decryptToString(){
$aesKey = ''; // v3支付秘钥
$associatedData = ''; // 同上 associated_data
$nonceStr = ''; // 同上 nonce
$ciphertext = ''; // 同上 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, $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, $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);
dump(44);
return \openssl_decrypt($ctext, 'aes-256-gcm', $aesKey, \OPENSSL_RAW_DATA, $nonceStr,
$authTag, $associatedData);
}
throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
}
5.将证书放到项目中与私钥文件同一目录
6.使用(以App支付为例) 支付接口: https://api.mch.weixin.qq.com/v3/pay/transactions/app
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Client;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
protected $appid = ''; // 应用ID
protected $merchantId = ''; // 商户ID
protected $AppSecret = ''; // AppSecret
protected $v3_key = '';
protected $serial_no = ''; // api证书序列号
protected $merchantPrivateKey = './cert/apiclient_key.pem'; // 商户私钥
protected $wechatpayCertificate = './cert/cert.pem'; // 上面解密后得到的证书
protected $apppay = 'https://api.mch.weixin.qq.com/v3/pay/transactions/app';
public function return_data($code,$msg,$data=array()){
return array('code' => $code,'msg'=>$msg,'data'=>$data);
}
/**
* @Notes: app支付
* @Interface pay
* @param $description 商品描述
* @param $out_trade_no 商户单号
* @param $notify_url 回调地址
* @param $total 支付金额 分
* @author: Chengzhitao
* @Time: 2021/6/9 15:43
*/
public function pay($description,$out_trade_no,$notify_url,int $total){
// 构造一个WechatPayMiddleware
$wechatpayMiddleware = WechatPayMiddleware::builder()
$merchantPrivateKey = PemUtil::loadPrivateKey($this->merchantPrivateKey);
$wechatpayCertificate = PemUtil::loadCertificate($this->wechatpayCertificate);
->withMerchant($this->merchantId, $this->serial_no, $merchantPrivateKey) // 传入商户相关配置
->withWechatPay([ $wechatpayCertificate ]) // 可传入多个微信支付平台证书,参数类型为array
->build();
// 将WechatPayMiddleware添加到Guzzle的HandlerStack中
$stack = HandlerStack::create();
$stack->push($wechatpayMiddleware, 'wechatpay');
// 创建Guzzle HTTP Client时,将HandlerStack传入
$client = new Client(['handler' => $stack]);
// 接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
try {
$resp = $client->request('POST', $this->apppay, [
'json' => [ // JSON请求体
'appid' => $this->appid,
'mchid' => $this->merchantId,
'description' => $description,
'out_trade_no' => $out_trade_no,
'notify_url' => $notify_url,
'amount' => [
'total' => $total,
'currency' => 'CNY'
]
],
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'User-Agent' => 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36' // 模拟手机
]
]);
if ($resp->getStatusCode() == 200) {
$rep = json_decode($resp->getBody(),true);
$data = [
'appid' => $this->appid,
'partnerid' => $this->merchantId, // 商户号
'prepayid' => $rep['prepay_id'], // 预支付id
'package' => 'Sign=WXPay',
'noncestr' => $this->nonce_str(),
'timestamp' => time()
];
$data['sign'] = $this->sign($data);
$data = $this->return_data(1,'成功',$data);
}else{
$data = $this->return_data(0,$resp->getReasonPhrase());
}
} catch (RequestException $e) {
$data = $this->return_data(0,$e->getMessage());
}
return $data;
}
/**
* v3 二签签名
* [sign description]
* @Author 念天地之悠悠
* @DateTime 2021-06-11
* @param [type] $data [description]
* @return [type] [description]
*/
public function sign($data){
$message = $data['appid']."\n".
$data['timestamp']."\n".
$data['noncestr']."\n".
$data['prepayid']."\n";
openssl_sign($message, $raw_sign, openssl_get_privatekey(file_get_contents($this->merchantPrivateKey)), 'sha256WithRSAEncryption');
return base64_encode($raw_sign);
}