微信支付开发文档 https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml
微信支付开发SDK https://github.com/wechatpay-apiv3/wechatpay-php
微信支付官网 https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
// 商户号,假定为`1000100`
$merchantId = '1000100';
// 商户私钥,文件路径假定为 `/path/to/merchant/apiclient_key.pem`
$merchantPrivateKeyFilePath = 'file:///path/to/merchant/apiclient_key.pem';// 注意 `file://` 开头协议不能少
// 加载商户私钥
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
$merchantCertificateSerial = '可以从商户平台直接获取到';// API证书不重置,商户证书序列号就是个常量
// // 也可以使用openssl命令行获取证书序列号
// // openssl x509 -in /path/to/merchant/apiclient_cert.pem -noout -serial | awk -F= '{print $2}'
// // 或者从以下代码也可以直接加载
// // 「商户证书」,文件路径假定为 `/path/to/merchant/apiclient_cert.pem`
// $merchantCertificateFilePath = 'file:///path/to/merchant/apiclient_cert.pem';// 注意 `file://` 开头协议不能少
// // 解析「商户证书」序列号
// $merchantCertificateSerial = PemUtil::parseCertificateSerialNo($merchantCertificateFilePath);
// 「平台证书」,可由下载器 `./bin/CertificateDownloader.php` 生成并假定保存为 `/path/to/wechatpay/cert.pem`
$platformCertificateFilePath = 'file:///path/to/wechatpay/cert.pem';// 注意 `file://` 开头协议不能少
// 加载「平台证书」公钥
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 解析「平台证书」序列号,「平台证书」当前五年一换,缓存后就是个常量
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
// 工厂方法构造一个实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
// APIv2密钥(32字节)--不使用APIv2可选
// 'secret' => 'exposed_your_key_here_have_risks',// 值为占位符,如需使用APIv2请替换为实际值
// 'merchant' => [// --不使用APIv2可选
// // 商户证书 文件路径 --不使用APIv2可选
// 'cert' => $merchantCertificateFilePath,
// // 商户API私钥 文件路径 --不使用APIv2可选
// 'key' => $merchantPrivateKeyFilePath,
// ],
]);
按照教程获取这两个文件 https://kf.qq.com/faq/161222NneAJf161222U7fARv.html
代码
//获取平台证书
public function aes_util()
{
//获取平台证书列表
$url = 'https://api.mch.weixin.qq.com/v3/certificates';
$method = 'GET';
$token = $this->sign($url,$method,'');
$response = $this->curlGet($url,$token);
//证书和回调报文解密
$api_v3_key = '';//APIv3秘钥
$associatedData = '';//平台列表接口返回的 associated_data
$nonceStr = '';//平台列表接口返回的 nonce
$ciphertext = '';//平台列表接口返回的 ciphertext
$AesUtil= new AesUtil($api_v3_key);
$back=$AesUtil->decryptToString($associatedData,$nonceStr,$ciphertext);
$back=$AesUtil->decryptToString($associatedData,$nonceStr,$ciphertext);
}
//获取签名
public function sign($url,$method,$body)
{
$timestamp = time();
$url_parts = parse_url($url);
$nonce = $timestamp.rand('10000','99999');
$serial_no = '';//API证书序列号
$merchant_id = '';//商户号
$canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
$message = $method."\n".
$canonical_url."\n".
$timestamp."\n".
$nonce."\n".
$body."\n";
$apiclient_key_path = INDEX_PATH . '\certificate\apiclient_key.pem';//商户私钥文件路径
$pkeyid = openssl_get_privatekey(file_get_contents($apiclient_key_path));
openssl_sign($message, $raw_sign, $pkeyid, 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);//签名
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
$merchant_id, $nonce, $timestamp, $serial_no, $sign);
return $token;
}
function curlGet($url,$token)
{
//初始化
$ch = curl_init();
//设置抓取的url
curl_setopt($ch, CURLOPT_URL, $url);
//设置获取的信息以文件流的形式返回,而不是直接输出。
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); // https请求 不验证证书
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); // https请求 不验证hosts
//设置header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
$schema = 'WECHATPAY2-SHA256-RSA2048';
$header=array(
'Authorization:' . $schema . ' ' . $token,
'Accept: application/json',
'Content-Type: application/json; charset=utf-8',
'User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);//模拟头部信息
//执行命令
$output = curl_exec($ch);//返回api的json对象
curl_close($ch); //释放curl句柄
return json_decode($output,true);
//1.1、组装请求携带数据(初始化)
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
$schema = 'WECHATPAY2-SHA256-RSA2048';
$header = [
"Content-Type" => "application/json",
"Accept" => "application/json",
"User-Agent" => "*/*",
"Authorization" => $schema . ' ' . $token
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);//模拟头部信息
//按需打开
// $headerArray = array("Content-Type:application/json;charset=utf-8", "Accept:application/json");
// curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArray);
//1.2、发送请求
$output = curl_exec($ch);
//按需打开
// $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 这一步必须在curl_close()调用之前调用
//1.3、关闭请求通道
curl_close($ch);
//2、解析数据
//2.1、解决中文乱码。【返回的字符(数据)编码格式可能是:UTF-8、GBK、GB2312、BIG5等,这里统一转换成UTF-8编码格式】
$output = mb_convert_encoding($output, 'UTF-8', 'UTF-8,GBK,GB2312,BIG5');
//2.2、将数据转换成数组格式。【PHP最常用的数据结构就是数组,固这里转换成数组】
$output = json_decode($output, true);
//3、数据传给调用者
return $output;
}
v3版本请求都需要带个头部信息 主要难点是 签名生成
如果没有带头部信息会报以下错误
//获取签名
public function sign($url,$method,$body)
{
$timestamp = time();
$url_parts = parse_url($url);
$nonce = $timestamp.rand('10000','99999');
$serial_no = '';//API证书序列号
$merchant_id = '';//商户号
$canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
$message = $method."\n".
$canonical_url."\n".
$timestamp."\n".
$nonce."\n".
$body."\n";
$apiclient_key_path = '';//商户私钥文件路径
$pkeyid = openssl_get_privatekey(file_get_contents($apiclient_key_path));
openssl_sign($message, $raw_sign, $pkeyid, 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);//签名
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
$merchant_id, $nonce, $timestamp, $serial_no, $sign);
return $token;
}
通过调取平台证书列表接口 返回的数据还得经过解密才是我们想要的数据
<?php
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');
}
}
引用之后调用
$api_v3_key = '';//APIv3秘钥
$associatedData = '';//平台列表接口返回的 associated_data
$nonceStr = '';//平台列表接口返回的 nonce
$ciphertext = '';//平台列表接口返回的 ciphertext
$AesUtil= new AesUtil($api_v3_key);
$back=$AesUtil->decryptToString($associatedData,$nonceStr,$ciphertext);
APIv3秘钥
调取成功之后 返回类似下面的数据
把上面的返回的 复制 然后创建一个 .pem 文件 复制进去
平台证书列表 下面有个主意事项
可以写个定时任务 把返回的数据写入文件里
try {
$resp = $instance
->v3->pay->transactions->native
->post(['json' => [
'mchid' => '1900006XXX',
'out_trade_no' => 'native12177525012014070332333',
'appid' => 'wxdace645e0bc2cXXX',
'description' => 'Image形象店-深圳腾大-QQ公仔',
'notify_url' => 'https://weixin.qq.com/',
'amount' => [
'total' => 1,
'currency' => 'CNY'
],
]]);
echo $resp->getStatusCode(), PHP_EOL;
echo $resp->getBody(), PHP_EOL;
} catch (\Exception $e) {
// 进行错误处理
echo $e->getMessage(), PHP_EOL;
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
}
echo $e->getTraceAsString(), PHP_EOL;
}
use WeChatPay\Formatter;
use WeChatPay\Crypto\Rsa;
$merchantPrivateKeyFilePath = 'file:///path/to/merchant/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath);
$params = [
'appId' => 'wx8888888888888888',
'timeStamp' => (string)Formatter::timestamp(),
'nonceStr' => Formatter::nonce(),
'package' => 'prepay_id=',//微信小程序下单返回的prepay_id 主意格式
];
$params += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
$merchantPrivateKeyInstance
), 'signType' => 'RSA'];
echo json_encode($params);
前端调用wx.requestPayment发起微信支付
其他的接口调用方式都一样的 按照git教程来
注:如果不想用SDK的 调用各个接口的时候 记得带上头部信息 和 请求平台证书列表一样的做法 只是 传参 不同