之前的网站涉及到微信支付都是用的v2版本,好久也没更新了,没出问题也没去管,最近新做了一个项目,发现V3都出了好久了,然后就去研究了一下,踩了一点坑,记录一下。(注意:百度出来的大部分都是扯淡的)
老规矩,先看文档( 这里强调一下前提:你要有一个已认证的公众号或小程序,同时要开通了微信支付的功能 )
https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_7_0.shtml
我是先做的PC端的支付,所以先对接的Native支付:
1.先去微信支付的控制台设置密钥;然后申请证书,申请好后将压缩包解压到网站根目录里(或者服务器上,保证路径是对的即可);
然后设置支付回调域名;要记住mchid(商户号),后续会用到。
2.去微信公众平台设置app_id,app_secret(jsapi支付需要用到),设置授权域名、js安全域名,IP白名单;
3.根据文档进行对接:
主要参考这个:https://github.com/wechatpay-apiv3/wechatpay-php
安装比较简单,composer require wechatpay/wechatpay
4.安装完成后,在需要用到支付的控制器里引入:
我是放在了一个单独的控制器里:
<?php
namespace app\index\controller;
use think\Controller;
use think\facade\Config;
use think\facade\Session;
use think\facade\Env;
use think\Db;
class Common extends Controller
{
//微信支付
public function wxpay($id){
$row = Db::name('order')->where(['id'=>$id])->find();
$data = [];
$data['out_trade_no'] = $row['order_number'];
$data['id'] = $row['id'];
$data['description'] = $row['description];
$data['total'] = $row['price]*100;//单位:分
//调用微信nativePay支付
$res = nativePay($data);
//生成付款二维码
if($res['code'] == 200){
$code = getQRCode($id, $res['data']['code_url']);
}else{
$error = 1;
}
//这里判断二维码是否生成
if(!file_exists(Env::get('root_path') .'public'.$code)){
$error = 1;
}
return ['code'=>$code,'error'=>$error];
}
}
然后支付回调和相关的方法写到Payments.php里
<?php
namespace app\index\controller;
use app\ygbxapi\controller\Api;
use think\Controller;
use think\Db;
use think\facade\Env;
use think\facade\Session;
use think\facade\Request;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Formatter;
class Payments extends Common
{
/**
* @function wxpayNotifyCallback
* @intro 微信支付回调
* @return string
*/
public function wxpayNotifyCallback()
{
//这里是用的TP方法
$inWechatpaySignature = Request::header('Wechatpay-Signature');// 请根据实际情况获取
$inWechatpayTimestamp = Request::header('Wechatpay-Timestamp');// 请根据实际情况获取
$inWechatpaySerial = Request::header('Wechatpay-Serial');// 请根据实际情况获取
$inWechatpayNonce = Request::header('Wechatpay-Nonce');// 请根据实际情况获取
$inBody = file_get_contents('php://input');// 请根据实际情况获取,例如: file_get_contents('php://input');
$apiv3Key = config('app.wxpay.key');// 在商户平台上设置的APIv3密钥
// 根据通知的平台证书序列号,查询本地平台证书文件,
// 假定为 `/path/to/wechatpay/inWechatpaySerial.pem`
$platformPublicKeyInstance = Rsa::from('file://'.config('app.wxpay.platform_key_path'), Rsa::KEY_TYPE_PUBLIC);
// 检查通知时间偏移量,允许5分钟之内的偏移
$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
$verifiedStatus = Rsa::verify(
// 构造验签名串
Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
$inWechatpaySignature,
$platformPublicKeyInstance
);
if ($timeOffsetStatus && $verifiedStatus) {
// 转换通知的JSON文本消息为PHP Array数组
$inBodyArray = (array)json_decode($inBody, true);
// 使用PHP7的数据解构语法,从Array中解构并赋值变量
['resource' => [
'ciphertext' => $ciphertext,
'nonce' => $nonce,
'associated_data' => $aad
]] = $inBodyArray;
// 加密文本消息解密
$inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
// 把解密后的文本转换为PHP Array数组
$inBodyResourceArray = (array)json_decode($inBodyResource, true);
$notfiyOutput = [];
$notfiyOutput['appid'] = $inBodyResourceArray['appid'];
$notfiyOutput['attach'] = $inBodyResourceArray['attach'];
$notfiyOutput['bank_type'] = $inBodyResourceArray['bank_type'];
$notfiyOutput['payer_total'] = $inBodyResourceArray['amount']['payer_total'];
$notfiyOutput['currency'] = $inBodyResourceArray['amount']['currency'];
$notfiyOutput['payer_currency'] = $inBodyResourceArray['amount']['payer_currency'];
$notfiyOutput['mch_id'] = $inBodyResourceArray['mchid'];
$notfiyOutput['openid'] = $inBodyResourceArray['payer']['openid'];
$notfiyOutput['out_trade_no'] = $inBodyResourceArray['out_trade_no'];
$notfiyOutput['trade_state'] = $inBodyResourceArray['trade_state'];
$notfiyOutput['success_time'] = strtotime($inBodyResourceArray['success_time']);
$notfiyOutput['trade_state_desc'] = $inBodyResourceArray['trade_state_desc'];
$notfiyOutput['total_fee'] = $inBodyResourceArray['amount']['total'];
$notfiyOutput['trade_type'] = $inBodyResourceArray['trade_type'];
$notfiyOutput['transaction_id'] = $inBodyResourceArray['transaction_id'];
//回调信息存入数据库
$row = Db::name('wxpay_record')->where(['transaction_id'=>$inBodyResourceArray['transaction_id']])->find();
if($row){
if($row['trade_state'] != 'SUCCESS'){
$id = Db::name('wxpay_record')->where(['transaction_id'=>$inBodyResourceArray['transaction_id']])->update($notfiyOutput);
}
}else{
$id = Db::name('wxpay_record')->insertGetId($notfiyOutput);
}
if($id){
//这里是更新订单状态
。。。。。。。
//删除对应的购物车信息
。。。。。。。
//加上邮件通知或短信通知
。。。。。。。
}
}
echo json_encode(['code'=>'SUCCESS','message'=>'成功']);
}
/**
* @function wxpayCheck
* @intro 微信支付结果查询(通过订单ID查询)
* @return string
*/
public function wxpayCheck()
{
$id = input('id');
$row = Db::name('order')->where(['id'=>$id])->find();
if(!$row){
ajaxReturn(0,'订单不存在');
}
$wxpayRow = Db::name('wxpay_record')->where(['out_trade_no'=>$row['order_number']])->find();
if(!$wxpayRow){
ajaxReturn(0,'订单尚未付款');
}
//去微信后台查询
$res = checkOrder($wxpayRow['transaction_id']);//这个地方是个坑,要注意
if($res !== false){
if($res['trade_state'] == 'SUCCESS'){
if($row['status'] == 0){
//如果回调里面没更新数据库,这里可以更新数据库订单支付状态
}
ajaxReturn(1,'付款成功', url('wxpayCheckOrder',['order_number'=>$row['order_number']]));
}else{
ajaxReturn(0,$res['trade_state_desc']);
}
}else{
ajaxReturn(1,'订单查询失败');
}
}
//微信支付(jsapi支付)
public function wxjsapipay(){
$id = input('id');
if(!Session::has('openid')){
$options = array(
'appid' => config('app.wechat.appid'),
'appsecret' => config('app.wechat.appsecret')
);
//没登录的话,先去静默授权,因为支付的时候需要用到openid,这里授权逻辑不敷述了
$weApi = new \wechat\WechatApi($options);
$url = $weApi->getOauthRedirect(getHostDomain().url('login',['id'=>$id]), 'state', 'snsapi_base');
$this->redirect($url);
}
$row = Db::name('order')->where(['id'=>$id])->find();
if(!$row){
ajaxReturn(0,'订单不存在');
}
$error = 0;
$tempDescription = [];//描述最多127个字节
$data = [];
$data['out_trade_no'] = $row['order_number'];
$data['openid'] = Session::get('openid');
$data['description'] = $row['description'];
$data['total'] = $row[' price']*100;//单位:分
$res = jsapiPay($data);
if($res['code'] == 200){
$returnData = getJsParameters($res['data']['prepay_id']);//这里尤其需要注意
$returnData = json_decode($returnData, true);
}else{
exit('支付错误');
}
$this->assign('data', $returnData);
$this->assign('row', $row);
return $this->fetch();
}
我的调用微信支付的方法是放在common.php里的
<?php
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Crypto\Hash;
use WeChatPay\Util\PemUtil;
use WeChatPay\Formatter;
/**
* 获取支付配置信息
*/
function getWxpayInstance(){
// 商户号
$merchantId = config('app.wxpay.mch_id');
// 商户私钥
$merchantPrivateKeyFilePath = 'file://'.config('app.wxpay.ssl_key_path');// 注意 `file://` 开头协议不能少
// 加载商户私钥
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
$merchantCertificateFilePath = 'file://'.config('app.wxpay.ssl_cert_path');// 注意 `file://` 开头协议不能少
// 解析「商户证书」序列号
$merchantCertificateSerial = PemUtil::parseCertificateSerialNo($merchantCertificateFilePath);
// 「平台证书」,可由下载器 `./bin/CertificateDownloader.php` 生成并假定保存为 `/path/to/wechatpay/cert.pem`
//这个平台证书需要用命令行去生成一下,注意参数
$platformCertificateFilePath = 'file://'.config('app.wxpay.platform_key_path');// 注意 `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,
],
]);
return $instance;
}
/**
* Native下单
*/
function nativePay($payData){
$instance = getWxpayInstance();
//Native下单
try {
$resp = $instance
->v3->pay->transactions->native
->post(['json' => [
'mchid' => config('app.wxpay.mch_id'),
'out_trade_no' => $payData['out_trade_no'],
'appid' => config('app.wxpay.app_id'),
'description' => $payData['description'],
'notify_url' => config('app.wxpay.notify_url'),
'amount' => [
'total' => $payData['total'],
'currency' => 'CNY'
],
]]);
$code = $resp->getStatusCode();
$body = $resp->getBody();
$res = [
'code' => $code,
'data' => json_decode($body, true)
];
} catch (\Exception $e) {
// 异常错误处理
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
}
$res = [
'code' => 500,
'data' => $e->getMessage()
];
}
return $res;
}
/**
* Native查询订单
*/
function checkOrder($transaction_id){
$instance = getWxpayInstance();
$res = $instance
->v3->pay->transactions->id->{'{transaction_id}'}
->getAsync([
// 查询参数结构
'query' => ['mchid' => config('app.wxpay.mch_id')],
// uri_template 字面量参数
'transaction_id' => $transaction_id,
])
->then(static function($response) {
// 正常逻辑回调处理
return $response;//这里不是该方法的返回值,注意避坑
})
->otherwise(static function($e) {
// 异常错误处理
$e->getMessage();
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
$r->getStatusCode() . ' ' . $r->getReasonPhrase();
$r->getBody();
}
$e->getTraceAsString();
return false;
})
->wait();
return $res == false ? false : json_decode($res->getBody(), true);//这里避坑,return一定要在最外边
}
/**
* 生成支付二维码
*/
function getQRCode($id, $code_url){
$file_path = '/uploads/qrcode';
$save_path = Env::get('root_path') .'public'.$file_path;
if (!file_exists($save_path) && !mkdir($save_path, 0777, true)) {
return false;
}
$file = '/'.$id.time().'.png';
$code = $save_path.$file;
$url = $code_url;
require Env::get('root_path') .'extend/phpqrcode/phpqrcode.php';
QRcode::png($url,$code,'L',7);
return $file_path.$file;
}
/**
* wxpay jsapi下单
*/
function jsapiPay($payData){
$instance = getWxpayInstance();
//jsapi下单
try {
$resp = $instance
->v3->pay->transactions->jsapi
->post(['json' => [
'mchid' => config('app.wxpay.mch_id'),
'appid' => config('app.wxpay.app_id'),
'out_trade_no' => $payData['out_trade_no'],
'description' => $payData['description'],
'notify_url' => config('app.wxpay.notify_url'),
'amount' => [
'total' => $payData['total'],
'currency' => 'CNY'
],
'payer' => [
'openid' => $payData['openid'],
],
]]);
$code = $resp->getStatusCode();
$body = $resp->getBody();
$res = [
'code' => $code,
'data' => json_decode($body, true)
];
} catch (\Exception $e) {
// 异常错误处理
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
}
$res = [
'code' => 500,
'data' => $e->getMessage()
];
}
return $res;
}
/**
* 获取jaapi支付字段
*/
function getJsParameters($prepay_id){
$merchantPrivateKeyFilePath = 'file://'.config('app.wxpay.ssl_key_path');;
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath);
$params = [
'appId' => config('app.wxpay.app_id'),
'timeStamp' => (string)Formatter::timestamp(),
'nonceStr' => Formatter::nonce(),
'package' => 'prepay_id='.$prepay_id,
];
$params += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
$merchantPrivateKeyInstance
), 'signType' => 'RSA'];//这里避坑,前段页面里的加密方式要和这里的一致,V3版本貌似只支持RSA加密
return json_encode($params);
}
jsapi付款的前台页面里的js如下:
<script type="text/javascript">
var appId = "{$data.appId}";
var timeStamp = "{$data.timeStamp}";
var nonceStr = "{$data.nonceStr}";
var package = "{$data.package}";
var paySign = "{$data.paySign}";
var order_number = "{$row.order_number}";
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":appId,
"timeStamp":timeStamp,
"nonceStr":nonceStr,
"package":package,
"signType":"RSA", //避坑
"paySign":paySign
},
function(result){
if(result.err_msg == "get_brand_wcpay_request:ok" ){
window.location.href = "/index/payments/wxpayCheckOrder/order_number/"+order_number;
}else{
var r=confirm("支付失败");
if (r==true){
window.location.href = "/";
}else{
window.location.href = "/";
}
}
});
}
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
</script>
这样基本的支付流程就走完了,可以根据自己程序的逻辑,在中间穿插自己的代码。
Mysql8新增用户,mysql8配置权限,mysql8配置,mysql8配置文件 Linux命令,scp,scp命令,Linux复制 git commit git add centos git 搭建FTP,Linux FTP,禁止FTP登录ssh 上传文件,阿里云OSS上传,文件上传到OSS,OSS文件上传,OSS上传 微信支付,微信支付V3,PHP微信支付,微信nativePay支付,微信jsapi支付 微信支付,微信支付V3,PHP微信支付 bootstrap4 modal, lavarel The subversion command line tools are no longer provided by Xcode. 银联支付,tp5.1银联支付 支付宝即时到账,PHP支付宝 system libzip must be upgraded to version >= 0.11 CMake 3.0.2 or higher is required Class 'ZipArchive' not found chr() expects parameter 1 to be int php7.4 tcpdf unexpected '='