环境
php版本:7.2*
安装官方SDK
composer require wechatpay/wechatpay
ps: 如果因为第三方包导致提示PHP版本问题而安装不了的(参考下图 图1),可使用
--ignore-platform-reqs
配置,可以忽略。完整命令:
composer require wechatpay/wechatpay --ignore-platform-reqs
(参考下图 图2)
图1:
图2:
安装完成 官方的SDK 就不用去考虑微信的验签加密签名等问题了,只要配置好相关的配置就行。
构造一个 APIv3 客户端实例
<?php
namespace App\Http\Controllers;
use WeChatPay\Crypto\Rsa;
/**
* 微信支付V3版本
*/
class WxPayV3Controller
{
/**
* 微信开发平台审核通过的应用ID
* @var string
*/
protected $appid = 'wxabc1111123213';
/**
* 商户ID
* @var string
*/
protected $merchantId = '1658899999';
/**
* 商户v3版本私钥
* @var string
*/
protected $merchantV3PrivateKey = 'V3e123ABC12312AAALDLSFLSDCDSLDF';
/**
* 「商户API私钥」文件的绝对路径
* @var string
*/
protected $merchantPrivatePath = 'D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem';
/**
* 「商户API证书」的「证书序列号」
* @var string
*/
protected $merchantCertificateSerial = '73021A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A';
/**
* 「微信支付平台证书」文件的绝对路径
* @var string
*/
protected $platformCertificateOrPublicKeyFilePath = 'D:\EMin\xxx\cert\wx_v3\v3_certificate_or_publickey.pem';
/**
* 「微信支付平台证书」的「证书序列号」
* @var string
*/
protected $platformCertificateSerialOrPublicKeyId = '5B1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A';
/**
* APIv3 客户端实例
* @var \WeChatPay\BuilderChainable
*/
protected $instance;
public function __construct()
{
// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
// 文件路径例如:D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem
$merchantPrivateKeyInstance = Rsa::from('file://'.$this->merchantPrivatePath, Rsa::KEY_TYPE_PRIVATE);
// 从本地文件中加载「微信支付平台证书」或者「微信支付平台公钥」,用来验证微信支付应答的签名
// 文件路径例如:D:\EMin\xxx\cert\wx_v3\v3_certificate_or_publickey.pem
$platformPublicKeyInstance = Rsa::from('file://'.$this->platformCertificateOrPublicKeyFilePath, Rsa::KEY_TYPE_PUBLIC);
// 构造一个 APIv3 客户端实例
$instance = Builder::factory([
'mchid' => $this->merchantId,
'serial' => $this->merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$this->platformCertificateSerialOrPublicKeyId => $platformPublicKeyInstance,
],
]);
$this->instance = $instance;
}
参数说明
这里主要搞明白几个重要的配置参数分别去哪里获取。
$appid 微信开发平台审核通过的应用ID
获取途径:
登录 微信开放平台 输入账号密码登录,然后点击
管理后台->移动应用
点击已通过的应用进行查看。
查看这里的APPID即可,这就是 微信开发平台审核通过的应用ID
$appid
$merchantId 商户ID
获取途径:
登录 微信商户后台 输入账号密码登录,点击 右上角的登录头像 就能查看
$merchantV3PrivateKey 商户v3版本私钥
获取途径:
登录 微信商户后台 后,然后点击
API安全
。
如果没设置过,直接设置即可。而设置过却忘记了,那就只能重新更改了。参考下图:
$merchantPrivatePath 「商户API私钥」文件的绝对路径
获取途径:
登录 微信商户后台 后,然后点击
API安全
。
没有证书。那么就自己申请一个新的,
API安全->商户API证书
点击
申请证书
(参考下图)。
如果有证书,忘记了私钥,也可以重新申请一个新的证书,里面包含一个证书文件和一个API私钥文件。
具体操作步骤可参考官方文档:商户API证书获取方法及功能介绍,照着步骤来下载软件生成证书就行。里面的文件大概是这样的(参考下图):
其中的
apiclient_key.pen
文件就是
商户API私钥
,把这个文件放在项目的某个地方,而
$merchantPrivatePath
指的就是这个文件所在的绝对路径,不管是在Windows本地还是Linux服务器上都一样。
Windows上参考路径:D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem
Linux上参考路径:/www/wwwroot/www.xxx.com/cert/wx_v3/v3_apiclient_key.pem
$merchantCertificateSerial 「商户API证书」的「证书序列号」
获取途径:
登录 微信商户后台 输入账号密码登录,点击
API安全->商户API证书
点击管理证书就能查看。
$platformCertificateOrPublicKeyFilePath 「微信支付平台证书」文件的绝对路径
获取途径:
这个证书可以在代码里请求获取保存到项目中,也可以直接在本地先请求保存下来(博主选的是这一种)。
先参考一下官方给的文档:Certificate Downloader。博主这里主要讲一下要注意的一些地方
指令:php vendor/bin/CertificateDownloader.php -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}
参数说明:
-m, --mchid=<merchantId> 这里就是博主代码中的 $merchantId (商户ID)
-s, --serialno=<serialNo> 这里就是博主代码中的 $merchantCertificateSerial (「商户API证书」的「证书序列号」)
-f, --privatekey=<privateKeyFilePath> 这里就是博主代码中的 $merchantPrivatePath(「商户API私钥」文件的绝对路径)
-k, --key=<apiV3key> 这里就是博主代码中的 $merchantV3PrivateKey (商户v3版本私钥)
-o, --output=[outputFilePath] 下载成功后保存证书的路径,可选参数,默认为临时文件目录夹(博主这里写的是:D:\EMin\xxx\public/)
博主的参考指令:
php vendor/bin/CertificateDownloader.php -k 'V3e123ABC12312AAALDLSFLSDCDSLDF -m 1658899999 -f D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem -s 73021A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A -o D:\EMin\xxx\public/
Tip: 在项目的根目录上执行博主的这条参考指令即可。具体参数自己替换哈,这样就会在
D:\EMin\xxx\public/
目录下生成一个pem文件,重命名即可。
$platformCertificateSerialOrPublicKeyId 微信支付平台证书」的「证书序列号」
获取途径:
登录 微信商户后台 输入账号密码登录,点击
API安全->平台证书
点击管理证书就能查看。
至此,微信支付最恶心的配置项已经整完了。
APP下单
博主接着上面的源码,继续写了。封装好的完整源码会放在最后
/**
* APP下单
* @param $out_trade_no string 在自己系统中唯一的订单号
* @param $body string 商品描述
* @param $amount number 订单金额,单位为分
* @param $notify_url string 支付回调地址,必须是https开头。例如:https://www.xxx.com/xxx/xxx
* @return \Psr\Http\Message\ResponseInterface
*/
public function appPay($out_trade_no, $body, $amount,$notify_url)
{
return $this->instance
->chain('v3/pay/transactions/app')
->post(['json' => [
'mchid' => $this->merchantId,
'out_trade_no' => $out_trade_no,
'appid' => $this->appid,
'description' => $body,
'notify_url' => $notify_url,
'amount' => [
'total' => $amount,
'currency' => 'CNY'
],
]]);
}
参考官方文档:APP下单
生成签名
作用:用于返回给APP端 用于调起微信支付
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
/**
* 生成支付签名
* @param $result_data
* @return array
*/
public function generateSignature($result_data)
{
// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
// 文件路径例如:D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem
$merchantPrivateKeyInstance = Rsa::from('file://'.$this->merchantPrivatePath, Rsa::KEY_TYPE_PRIVATE);
$arouse_data = [
'appId' => $this->appid,
'timeStamp' => strval(Formatter::timestamp()),
'nonceStr' => Formatter::nonce(),
//'package' => 'prepay_id=' . $result_data['prepay_id'], // JSAPI下单
'prepay_id => $result_data['prepay_id'], // APP下单
];
$arouse_data += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($arouse_data)),
$merchantPrivateKeyInstance
), 'signType' => 'RSA'];
return $arouse_data;
}
Tip:博主没测JSAPI下单流程,只是看官方文档给的示例是JSAPI,没注意看文档。**APP下单不要加
prepay_id=预交易号
去加密字符串,不然APP唤醒微信时会报支付验证签名失败**
参考官方文档的部分截图:
参考官方文档:APP调起支付
支付回调
use Illuminate\Support\Facades\Request;
use WeChatPay\Crypto\AesGcm;
/**
* v3版本的支付回调
* @return false|mixed|string
*/
public function notify()
{
//获取请求参数
$notifyData = file_get_contents('php://input');
//获取请求头用来签名验签
$header = Request::header();
// 转换通知的JSON文本消息为PHP Array数组
$inBodyArray = (array)json_decode($notifyData, true);
if ($inBodyArray['event_type'] !== 'TRANSACTION.SUCCESS') {
return json_encode(['code' => 400,'message' => '支付失败']);
}
//签名验签
$signStatus = $this->signVerify($header,$notifyData);
if (!$signStatus) {
return json_encode(['code' => 400,'message' => '验签失败']);
}
// 使用PHP7的数据解构语法,从Array中解构并赋值变量
['resource' => [
'ciphertext' => $ciphertext,
'nonce' => $nonce,
'associated_data' => $aad
]] = $inBodyArray;
// 加密文本消息解密
$inBodyResource = AesGcm::decrypt($ciphertext,$this->merchantV3PrivateKey , $nonce, $aad);
// 把解密后的文本转换为PHP Array数组
$inBodyResourceArray = (array)json_decode($inBodyResource, true);
//具体的业务逻辑
return json_encode(['code' => 200]);
}
/**签名验签
* @param $header 请求头
* @param $body 请求参数
* @return bool
*/
public function signVerify($header,$body)
{
$inWechatpaySignature = head($header['wechatpay-signature']);// 请根据实际情况获取 微信方的签名
$inWechatpayTimestamp = head($header['wechatpay-timestamp']);// 请根据实际情况获取 微信方的时间戳
$inWechatpayNonce = head($header['wechatpay-nonce']);// 请根据实际情况获取 微信方的随机字符串
// 根据通知的平台证书序列号,查询本地平台证书文件,
$platformPublicKeyInstance = Rsa::from('file://'.$this->platformCertificateOrPublicKeyFilePath, Rsa::KEY_TYPE_PUBLIC);
// 检查通知时间偏移量,允许5分钟之内的偏移
$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
$verifiedStatus = Rsa::verify(
// 构造验签名串
Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $body),
$inWechatpaySignature,
$platformPublicKeyInstance
);
if ($timeOffsetStatus && $verifiedStatus) {
return true;
}
return false;
}
可参考官方文档:支付通知
完整的封装类源码
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Request;
use WeChatPay\Builder;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
/**
* 微信支付V3版本
*/
class WxPayV3Controller
{
/**
* 微信开发平台审核通过的应用ID
* @var string
*/
protected $appid = 'wxabc1111123213';
/**
* 商户ID
* @var string
*/
protected $merchantId = '1658899999';
/**
* 商户v3版本私钥
* @var string
*/
protected $merchantV3PrivateKey = 'V3e123ABC12312AAALDLSFLSDCDSLDF';
/**
* 「商户API私钥」文件的绝对路径
* @var string
*/
protected $merchantPrivatePath = 'D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem';
/**
* 「商户API证书」的「证书序列号」
* @var string
*/
protected $merchantCertificateSerial = '73021A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A';
/**
* 「微信支付平台证书」文件的绝对路径
* @var string
*/
protected $platformCertificateOrPublicKeyFilePath = 'D:\EMin\xxx\cert\wx_v3\v3_certificate_or_publickey.pem';
/**
* 「微信支付平台证书」的「证书序列号」
* @var string
*/
protected $platformCertificateSerialOrPublicKeyId = '5B1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A';
/**
* APIv3 客户端实例
* @var \WeChatPay\BuilderChainable
*/
protected $instance;
public function __construct()
{
// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
// 文件路径例如:D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem
$merchantPrivateKeyInstance = Rsa::from('file://'.$this->merchantPrivatePath, Rsa::KEY_TYPE_PRIVATE);
// 从本地文件中加载「微信支付平台证书」或者「微信支付平台公钥」,用来验证微信支付应答的签名
// 文件路径例如:D:\EMin\xxx\cert\wx_v3\v3_certificate_or_publickey.pem
$platformPublicKeyInstance = Rsa::from('file://'.$this->platformCertificateOrPublicKeyFilePath, Rsa::KEY_TYPE_PUBLIC);
// 构造一个 APIv3 客户端实例
$instance = Builder::factory([
'mchid' => $this->merchantId,
'serial' => $this->merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$this->platformCertificateSerialOrPublicKeyId => $platformPublicKeyInstance,
],
]);
$this->instance = $instance;
}
/**
* APP下单
* @param $out_trade_no string 在自己系统中唯一的订单号
* @param $body string 商品描述
* @param $amount number 订单金额,单位为分
* @param $notify_url string 支付回调地址,必须是https开头。例如:https://www.xxx.com/xxx/xxx
* @return \Psr\Http\Message\ResponseInterface
*/
public function appPay($out_trade_no, $body, $amount,$notify_url)
{
return $this->instance
->chain('v3/pay/transactions/app')
->post(['json' => [
'mchid' => $this->merchantId,
'out_trade_no' => $out_trade_no,
'appid' => $this->appid,
'description' => $body,
'notify_url' => $notify_url,
'amount' => [
'total' => (int)$amount,
'currency' => 'CNY'
],
]]);
}
/**
* 生成支付签名
* @param $result_data
* @return array
*/
public function generateSignature($result_data)
{
// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
// 文件路径例如:D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem
$merchantPrivateKeyInstance = Rsa::from('file://'.$this->merchantPrivatePath, Rsa::KEY_TYPE_PRIVATE);
$arouse_data = [
'appId' => $this->appid,
'timeStamp' => strval(Formatter::timestamp()),
'nonceStr' => Formatter::nonce(),
'prepayId' => $result_data['prepay_id'],
];
$arouse_data += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($arouse_data)),
$merchantPrivateKeyInstance
), 'signType' => 'RSA'];
return $arouse_data;
}
/**
* v3版本的支付回调
* @return false|mixed|string
*/
public function notify()
{
//获取请求参数
$notifyData = file_get_contents('php://input');
//获取请求头用来签名验签
$header = Request::header();
// 转换通知的JSON文本消息为PHP Array数组
$inBodyArray = (array)json_decode($notifyData, true);
if ($inBodyArray['event_type'] !== 'TRANSACTION.SUCCESS') {
return json_encode(['code' => 400,'message' => '支付失败']);
}
//签名验签
$signStatus = $this->signVerify($header,$notifyData);
if (!$signStatus) {
return json_encode(['code' => 400,'message' => '验签失败']);
}
// 使用PHP7的数据解构语法,从Array中解构并赋值变量
['resource' => [
'ciphertext' => $ciphertext,
'nonce' => $nonce,
'associated_data' => $aad
]] = $inBodyArray;
// 加密文本消息解密
$inBodyResource = AesGcm::decrypt($ciphertext,$this->merchantV3PrivateKey , $nonce, $aad);
// 把解密后的文本转换为PHP Array数组
$inBodyResourceArray = (array)json_decode($inBodyResource, true);
// 具体的业务逻辑
return json_encode(['code' => 200]);
}
/**签名验签
* @param $header 请求头
* @param $body 请求参数
* @return bool
*/
public function signVerify($header,$body)
{
$inWechatpaySignature = head($header['wechatpay-signature']);// 请根据实际情况获取 微信方的签名
$inWechatpayTimestamp = head($header['wechatpay-timestamp']);// 请根据实际情况获取 微信方的时间戳
$inWechatpayNonce = head($header['wechatpay-nonce']);// 请根据实际情况获取 微信方的随机字符串
// 根据通知的平台证书序列号,查询本地平台证书文件,
$platformPublicKeyInstance = Rsa::from('file://'.$this->platformCertificateOrPublicKeyFilePath, Rsa::KEY_TYPE_PUBLIC);
// 检查通知时间偏移量,允许5分钟之内的偏移
$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
$verifiedStatus = Rsa::verify(
// 构造验签名串
Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $body),
$inWechatpaySignature,
$platformPublicKeyInstance
);
if ($timeOffsetStatus && $verifiedStatus) {
return true;
}
return false;
}
业务调用
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\WxPayV3Controller;
use App\Http\Controllers\Controller;
class TestController extends Controller
{
$wxPayV3Controller = new WxPayV3Controller();
// 生成APP下单
$resp = $wxPayV3Controller->appPay($out_trade_no, $body, $amount,$notify_url);
// $out_trade_no 在自己系统中唯一的订单号
// $body 商品描述
// $amount 订单金额,单位为分
// $notify_url 支付回调地址,必须是https开头。例如:https://www.xxx.com/xxx/xxx
$result_code = $resp->getStatusCode();
if ($result_code == 200) {
$result_data = json_decode($resp->getBody(), true);
// 生成支付签名
$arouse_data = $wxPayV3Controller->generateSignature($result_data);
return json_encode(['code' => 200 , 'msg' => '成功','data' => $arouse_data ]);
} else {
return json_encode(['code' => 400 , 'msg' => '失败','data' => [] ]);
}
}
特别注意
报错请求超时
解决方案:在
C:\Windows\System32\drivers\etc\host
文件里添加
113.96.240.143 api.mch.weixin.qq.com
即可。
报错没有安全证书
具体报错:
cURL error 60: SSL certificate problem: unable to get local issuer certificate (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for https://api.mch.weixin.qq.com/v3/pay/transactions/app
解决方案:
windons上 打开cmd 输入指令
curl -O https://curl.se/ca/cacert.pem
,就会在当前目录下生成一个 cacert.pem 文件,这个文件就是 CA证书。
在对应PHP版本的
ini
文件里添加
curl.cainfo="E:\xxx\crt\cacert.pem"
即可,
E:\xxx\crt\cacert.pem
为 CA证书 的绝对路径地址。
特别感谢
参考博客:
微信支付V3文档
微信支付 WeChatPay OpenAPI SDK
微信小程序支付v3版
版权归原作者 ゛人生若只如初见° 所有, 如有侵权,请联系我们删除。