废话简述
前段时间搞了个jsapi v3的支付功能,挺费时间的,细节很多,以下是个人的经验分享,包含代码过程。
开发前提
根据我自身的开发需求,我选择了jsapi v3的支付方式,根据这个方式要做以下准备。
1)要有一个认证好的服务号,有一个注册认证好的商户号。
2)服务号要先申请微信支付功能
3)在微信支付平台开通jsapi支付功能

4)在微信支付平台的产品中心 关联服务号

5)在微信支付平台搞定如下东西,记得做好保存工作

代码过程
1)前端代码,vue-cli。 getzhifu函数是传递数据给后台并获取到发起支付的数据。zhifu函数正式发起支付,请注意参数的大小写问题。本人的参考地址是 https://www.jianshu.com/p/f71648a8a2ad //
/**
//记得安装weixin-jsapi,并导入
//npm install weixin-jsapi --save-dev
//import wx from 'weixin-jsapi'
*/
// 后台获取支付需要的数据
getzhifu:async function(){
let a='';
var openid=this.openid;
var updata={
"body": this.data.pname,
"out_trade_no":this.ocode,
"total_fee":this.total_fee,
"notify_url":'回调地址',
"openid":openid,
"trade_type":'JSAPI'
};
console.log(updata);
await axios.post('获取支付数据的后台接口',qs.stringify(updata)).then(function(res){
a= res.data;
}).catch(function (error) {
a= error;
});
console.log('aaa');
console.log(a);
if(a.return_code == 'SUCCESS'){
this.timeStamp=a.timeStamp,
this.nonceStr=a.nonceStr, // 支付签名随机串,不长于 32 位
this.package=a.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
this.signType=a.signType, // 签名方式,默认为'SHA1',这里用'RSA'
this.paySign=a.paySign,
this.$nextTick(()=>{
this.zhifu();
})
}
},
// 发起支付
zhifu:function(){
let that=this;
wx.config({
debug: true, // 这里一般在测试阶段先用ture,等打包给后台的时候就改回false,
appId: that.Appid, // 必填,公众号的唯一标识
timestamp: that.timeStamp, // 必填,生成签名的时间戳 ,注意是小写的s
nonceStr: that.nonceStr, // 必填,生成签名的随机串
signature: that.paySign, // 必填,签名
jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表,如果你的业务需求和我一样,照填即可
})
wx.ready(() => {
wx.checkJsApi({
jsApiList: ['chooseWXPay'],
success:function(res){
console.log("seccess")
console.log('hskdjskjk', res)
},
fail:function(res){
console.log("fail");
console.log(res) }
})
wx.chooseWXPay({
nonceStr: that.nonceStr, // 支付签名随机串,不长于 32 位
package: that.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
signType: that.signType, // v3支付一般用 'RSA'
paySign: that.paySign, // 支付签名
timestamp: that.timeStamp, // 注意参数是小写的s
success: function (res) { // 支付成功后的回调函数
alert(res)
},
fail: function (res) {
alert("支付失败");
alert(res.errMsg);
}
})
})
wx.error(err => {
alert(err)
})
},
2)后端代码,tp5。本人的参考地址是:https://blog.csdn.net/qq_43278317/article/details/119249920
我的做法是封装支付代码在extent目录下(目前只封装了jsapi v3的,尴尬),在业务控制器中调取。
业务控制器的方法
// 获取前端数据,生成支付数据返回前端。
public function getzhifu(){
Hook::listen('my_cors');//我这边需要这个做跨域的,不需要可注释
$request=new request();
$body=$request->post('body');
$out_trade_no=$request->post('out_trade_no');
$total_fee=$request->post('total_fee');
$notify_url=$request->post('notify_url');
$openid=$request->post('openid');
$spbill_create_ip=$_SERVER["REMOTE_ADDR"];
$trade_type=$request->post('trade_type');
$total_fee = '0.01';
$wxpay = new Wxpay();//这个要先在控制器的头部引入先,//use Wxpay;
$date = [
'out_trade_no' => $out_trade_no,
'total_fee' => $total_fee,
'body' => $body,
'spbill_create_ip' => $spbill_create_ip,
'notify_url' => $notify_url,
'trade_type' => $trade_type,
'openid'=>$openid,
];
$res =$wxpay->start($date);
if($res){exit(json_encode($res,JSON_UNESCAPED_UNICODE))}
}
extent目录下的代码
<?php
class Wxpay{
/**
* 微信公共配置方法
*/
/*备注:apiclient_cert.p12是商户证书文件,除PHP外的开发均使用此证书文件*/
#微信基础信息
// $wxMerchantId = "商户号"; //商户号
// $wxMerchantApiCertificate = __DIR__ . "/cert/apiclient_cert.pem"; //商户证书
// $wxMerchantApiPrivateKey = __DIR__ . "/cert/apiclient_key.pem"; //商户私钥
// $wxApiV3Key = "微信API v3密钥"; //微信API v3密钥
// $wxApiSerialNo = "商户API证书序列号"; //商户API证书序列号
//这里的参数命名过长了,可以优化一下,我就不优化了
private $config =[
'wxMerchantId'=>"", //商户号
'wxMerchantApiCertificate' =>"", //商户证书
'wxMerchantApiPrivateKey' =>"", //商户私钥
'wxApiV3Key' => "", //微信API v3密钥,32位
'wxApiSerialNo' => "", //商户API证书序列号
'appid'=>'' //服务号appid
];
//这里的参数也可以发到上面$config去,我就不放了
private $common=[
//'url'=>"https://api.mch.weixin.qq.com/v3/certificates",
'xdurl'=>'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi',//统一下单地址
'params'=>[],
];
//这里是记录生成签名后的几个参数,用来返回给接口的,返回不止这几个,还有prepay_id参数值package,签名方式signtype和支付签名paySign
private $backdata=[
'timestamp'=>'',
'nonce_str'=>'',
'signType'=>'RSA',
];
/**
* #############微信构造签名流程#############
* 生成签名
* @param string $method 请求方法 POST 或者GET
* @param string $url 请求URL
* @param string $request 请求中的请求报文主体
* @param $wxMerchantId商户号
* @param $certKey商户私钥
* @param $wxApiSerialNo商户API证书序列号
* @return string
*/
public function start($fdata){
$url=$this->common['xdurl'];
$wxMerchantId=$this->config['wxMerchantId'];//商户号
$wxMerchantApiPrivateKey=$this->config['wxMerchantApiPrivateKey'];//商户私钥
$wxApiSerialNo=$this->config['wxApiSerialNo'];//商户API证书序列号
$hdata = array();
$time = time();
$hdata['appid'] = $this->config['appid'];
$hdata['mchid'] = $wxMerchantId;
$hdata['description'] = $fdata['body'];//商品描述
$hdata['out_trade_no'] = $fdata['out_trade_no'];//支付订单编号
$hdata['notify_url'] = $fdata['notify_url'];//回调接口
$hdata['amount']['total'] = intval($fdata['total_fee']*100);
$hdata['payer']['openid'] = $fdata['openid'];//用户付款的openID
$hdata = json_encode($hdata); //转json串
$authorization=$this->RequestSign("POST",$url,$hdata,$wxMerchantId,$wxMerchantApiPrivateKey,$wxApiSerialNo);
$xdurl=$this->common['xdurl'];
$token=$authorization;
$header = array(
'Content-Type:'.'application/json; charset=UTF-8',
'Accept:application/json',
'User-Agent:*/*',
'Authorization: WECHATPAY2-SHA256-RSA2048 '.$token
);
$ret =$this->curl_post_https($xdurl,$hdata,$header);
// var_dump($ret);exit;
$ret = json_decode($ret, true);
// var_dump($ret);exit;
if (isset($ret['prepay_id'])) { //拉起预支付成功
$res['return_code']='FAIL';
$res['timeStamp'] = $this->backdata['timestamp'];//时间戳
$res['nonceStr'] = $this->backdata['nonceStr'];//随机字符串
$res['signType'] = $this->backdata['signType'];//签名算法,暂支持 RSA
$res['package'] = 'prepay_id=' . $ret['prepay_id'];//统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*
$res['paySign'] = $this->getWechartSign($this->config['appid'],$res['timeStamp'],$res['nonceStr'],$res['package'],$wxMerchantApiPrivateKey);//签名
// var_dump($res);exit;
if($res['paySign']){$res['return_code']='SUCCESS';}
return $res; //返回给前端需要的串
}
return $ret;
}
public function RequestSign($method = "POST", $url = "", $request = array(), $wxMerchantId, $certKey, $wxApiSerialNo)
{
#截取获取当前请求地址【去除域名】
$url_parts = parse_url($url);
$path = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
#获取当前时间戳
$timeStamp = time();
$this->backdata['timestamp']=$timeStamp;
#生成一个随机字符串
$nonceStr = $this->getNonceStr();
$this->backdata['nonceStr']=$nonceStr;
#构造签名串
// $requestSign = sprintf("%s\n%s\n%s\n%s\n%s\n",$method,$path,$timeStamp,$nonceStr,$request);
$requestSign = "POST"."\n".$path."\n".$timeStamp."\n".$nonceStr."\n".$request."\n";
// echo $requestSign;
#计算计算签名值
$sign = $this->calculateSignatureValue($requestSign, $certKey);
// echo $sign;exit;
#设置HTTP头获取Authorization
$token = $this->createToken($wxMerchantId, $nonceStr, $timeStamp, $wxApiSerialNo, $sign);
// echo $token;exit;
#返回
return $token;
}
/**
* 计算签名值
* @param $requestSign
* @param $certKey
* @return string
* 使用商户私钥对待签名串进行SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值
*/
public function calculateSignatureValue($requestSign, $certKey)
{
// $raw_sign="";
$certKey = file_get_contents($certKey);
// echo $certKey;exit;
openssl_sign($requestSign, $raw_sign, $certKey, 'sha256WithRSAEncryption');
// openssl_sign($requestSign, $raw_sign, $certKey, 'sha256');
$sign = base64_encode($raw_sign);
return $sign;
}
/**
* 获取token
* @param $merchant_id
* @param $nonce
* @param $timestamp
* @param $serial_no
* @param $sign
* @return string
*/
public function createToken($merchant_id, $nonce, $timestamp, $serial_no, $sign)
{
$schema = 'WECHATPAY2-SHA256-RSA2048';
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
$merchant_id, $nonce, $timestamp, $serial_no, $sign);
return $token;
}
/**
* 产生随机字符串,不长于32位
* @param int $length
* @return string
*/
public function getNonceStr($length = 32)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
/**
* post请求
* @param $url
* @param $authorization
* @return mixed
*/
public function curlPostWithWx($url, $authorization)
{
$paramsString = json_encode($this->common['params']);
// 初始化curl
$ch = curl_init();
// 设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_HEADER, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
// post数据
curl_setopt($ch, CURLOPT_POST, 1);
// post的变量
curl_setopt($ch, CURLOPT_POSTFIELDS, $paramsString);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json; charset=utf-8',
'Content-Length: ' . strlen($paramsString),
'Authorization: ' . "WECHATPAY2-SHA256-RSA2048 " . $authorization,
'Accept: application/json',
'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
)
);
// 运行curl,结果以jason形式返回
$res = curl_exec($ch);
curl_close($ch);
// 取出数据
$data = json_decode($res, true);
return $data;
}
/**
* get请求
* @param $url
* @param $authorization
* @return mixed
*/
public function curlGetWithWx($url, $authorization)
{
// $paramsString = json_encode($this->common['params']);
// 初始化curl
$ch = curl_init();
// 设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_HEADER, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json; charset=utf-8',
// 'Content-Length: ' . strlen($paramsString),
'Authorization: ' . "WECHATPAY2-SHA256-RSA2048 " . $authorization,
'Accept: application/json',
'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
)
);
// 运行curl,结果以jason形式返回
$res = curl_exec($ch);
curl_close($ch);
// 取出数据
$data = json_decode($res, true);
//返回
return $data;
}
public function curl_post_https($url,$data,$header){ // 模拟提交数据函数
$curl = curl_init(); // 启动一个CURL会话
curl_setopt($curl, CURLOPT_URL, $url); // 要访问的地址
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); // 从证书中检查SSL加密算法是否存在
curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
curl_setopt($curl, CURLOPT_POST, 1); // 发送一个常规的Post请求
curl_setopt($curl, CURLOPT_POSTFIELDS, $data); // Post提交的数据包
curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
$tmpInfo = curl_exec($curl); // 执行操作
if (curl_errno($curl)) {
echo 'Errno'.curl_error($curl);//捕抓异常
}
curl_close($curl); // 关闭CURL会话
return $tmpInfo; // 返回数据,json格式
}
/*
* 统一下单调起支付的签名
* */
public function getWechartSign($appid, $timeStamp, $noncestr, $prepay_id, $sy)
{
$str = $appid . "\n" . $timeStamp . "\n" . $noncestr . "\n" . $prepay_id . "\n";
$str2 = $this->calculateSignatureValue($str, $sy);
return $str2;
}
}
完成!成功在前端调起支付。