看到网上的大部分问题都集中在签名部分,请你们必定请熟读微信JS-SDK说明文档附录5-常见错误及解决方法
部分。php
注意html
在计算签名的过程当中,若是url老是不对请 实验 首页的url
或 window.location.href。 作到微信拿到真实路径与咱们拿去生成签名的路径是一致的,千万记住这条前端
前端须要用js获取当前页面除去'#'hash部分的连接(可用location.href.split('#')[0]获取,并且须要encodeURIComponentvue
vue每次刷新页面,都须要从新配置SDK,使用JS_SDK必须先注入配置信息html5
IOS:微信IOS版,微信安卓版,每次切换路由,SPA的url是不会变的,发起签名请求的url参数必须是当前页面的url就是最初进入页面时的url(entryUrl.js)git
Android:微信安卓版,每次切换路由,SPA的url是会变的,发起签名请求的url参数必须是当前页面的url(不是最初进入页面时的)(entryUrl.js)github
登陆微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。ajax
域名格式:若是你的项目域名是http://test.domain.com,那么JS接口安全域名为test.domain.comvue-router
timestamp: , // 生成签名的时间戳,精确到秒 秒 秒 秒
nonceStr: '', // 必填,生成签名的随机串
// entryUrl.js 全局存储进入SPA的url(window.entryUrl),Android不变,依旧是获取当前页面的url,IOS就使用window.entryUrl // 记录进入app的url,后面微信sdk if (window.entryUrl === '') { window.entryUrl = location.href.split('#')[0] } // 进行签名的时候 url: isAndroid() ? location.href.split('#')[0] : window.entryUrl
支付流程图
后台生成签名后返回给前台使用,不少微信api须要这个签名。
生成后能够验证一下签名是否正确 签名校验工具
//php <?php namespace Vendor\wxpay;//命名空间 /** * Class wxpay * @package Vendor\wxpay * @name 用于签名生产 * @author weikai */ class wxpay { private $appId; private $appSecret; public function __construct($appId, $appSecret) { $this->appId = $appId; $this->appSecret = $appSecret; } //获取签名 public function getSignPackage() { $jsapiTicket = $this->getJsApiTicket();//获取JsApiTicket // vue管理路由 url参数建议前台传递 $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://"; // $url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";//若是后台获取url $url = I('get.frontUrl');//获取前台传递的url $timestamp = time();//如今时间戳 $nonceStr = $this->createNonceStr();//生成随机字符串 // 这里参数的顺序要按照 key 值 ASCII 码升序排序 $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr×tamp=$timestamp&url=$url"; $signature = sha1($string);//sha1加密排序后的参数生产签名 //将全部参数赋值到数组 $signPackage = array( "appId" => $this->appId, "nonceStr" => $nonceStr, "timestamp" => $timestamp, "url" => $url, "signature" => $signature, "rawString" => $string ); return $signPackage; } //生成随机字符串 private function createNonceStr($length = 16) { $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; $str = ""; for ($i = 0; $i < $length; $i++) { $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); } return $str; } //生成JsApiTicket private function getJsApiTicket() { // jsapi_ticket 全局存储与更新 $data = json_decode(S('jsapi_ticket')); //若是缓存中的JsApiTicket 不在有效期内从新生成JsApiTicket if ($data->expire_time < time()) { $accessToken = $this->getAccessToken();//获取AccessToken // 若是是企业号用如下 URL 获取 ticket // $url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=$accessToken"; $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=$accessToken"; $res = json_decode($this->httpGet($url)); $ticket = $res->ticket; //更新有效期存入缓存 if ($ticket) { $data->expire_time = time() + 7000; $data->jsapi_ticket = $ticket; S('jsapi_ticket',json_encode($data)); } } else { //不然缓存中的JsApiTicket 在有效期内就直接用 $ticket = $data->jsapi_ticket; } return $ticket; } //获取全局AccessToken public function getAccessToken() { // access_token 全局存储与更新 $data = json_decode(S('access_token')); if ($data->expire_time < time()) { // 若是是企业号用如下URL获取access_token // $url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=$this->appId&corpsecret=$this->appSecret"; $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$this->appId&secret=$this->appSecret"; $res = json_decode($this->httpGet($url)); $access_token = $res->access_token; if ($access_token) { $data->expire_time = time() + 7000; $data->access_token = $access_token; S('access_token',json_encode($data)); } } else { $access_token = $data->access_token; } return $access_token; } //curl 请求 private function httpGet($url) { $curl = curl_init(); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_TIMEOUT, 500); // 为保证第三方服务器与微信服务器之间数据传输的安全性,全部微信接口采用https方式调用,必须使用下面2行代码打开ssl安全校验。 // 若是在部署过程当中代码在此处验证失败,请到 http://curl.haxx.se/ca/cacert.pem 下载新的证书判别文件。 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, true); curl_setopt($curl, CURLOPT_URL, $url); $res = curl_exec($curl); curl_close($curl); return $res; } }
/** * WECHAT.js * Created by isam2016 */ import wx from 'weixin-js-sdk'; import axios from 'axios'; var JsWeChatApis = [ 'checkJsApi', 请补全列表 ]; var isWeChatReady = false;// 检查微信wx.ready export default class AlWeChat { constructor(object) { this.object = object;// vue 须要使用vue 解决回调 this.wxConfig();// 初始微信配置 } /** * 微信配置 * @Author Hybrid * @DateTime 2017-11-21 */ wxConfig() { let self = this; axios.get('/home/OrderConfirm/wxConfig', { params: { frontUrl: location.href.split('#')[0]// 前台吧url 传到后台 并且须要encodeURIComponent, } }).then(function(response) { var attachment = response.data.data;// 后台统一调配数据,返回前台 // console.log(attachment); wx.config({ debug: false, appId: attachment.appId, timestamp: attachment.timestamp, // 支付签名时间戳小写s 时间戳(timestamp)值要记住精确到秒,不是毫秒。 nonceStr: attachment.nonceStr,//支付签名随机串,不长于 32 位,大写s signature: attachment.signature, url: attachment.url, jsApiList: JsWeChatApis }); wx.ready(function () { isWeChatReady = true; self.object && self.wxQDetailShare()//分享到朋友圈+朋友的设置 }); wx.error(function (res) { //console.log(JSON.stringify(res)); }); }).catch(function (error) { // console.log(error) }); } /** * 微信扫一扫 * @Author Hybrid * @DateTime 2017-11-21 * @return {[type]} [description] */ wxScanQRCode(fn) { wx.scanQRCode({ needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果, scanType: ["qrCode"], // 能够指定扫二维码仍是一维码,默认两者都有 success: function (res) { var result = res.resultStr; // 当needResult 为 1 时,扫码返回的结果 fn(result); } }); } /** * 分享到朋友圈+朋友的设置 * 能够动态设置 * @Author Hybrid * @DateTime 2017-11-21 * @param {} data [展现数据] * @param {[type]} eqid [description] * @return {[type]} [description] */ wxQDetailShare() { var config = { title: 'XXX', desc: 'XXX', imgUrl: 'XXX', link: 'XXX', }; var shareConfig = { message: config, timeLine: { title: config.title, desc: config.desc, imgUrl: config.imgUrl, link: config.link, }, }; this.wxShare(shareConfig); } /** * 分享的基本配置 * @Author Hybrid * @DateTime 2017-11-21 * @param {} shareConfig [不一样类型的分享有不一样的配置] * @return {[type]} [description] */ wxShare(shareConfig) { let self = this; if (isWeChatReady) { /** * 分享到朋友圈 * @Author Hybrid */ wx.onMenuShareTimeline({ title: shareConfig.timeLine.title, // 分享标题 link: shareConfig.timeLine.link, // 分享连接 imgUrl: shareConfig.timeLine.imgUrl, // 分享图标 success: function () { self.object.closecovershow();// 回调 // 用户确认分享后执行的回调函数 }, cancel: function () { self.object.closecovershow(); // 用户取消分享后执行的回调函数 } }); /** * 分享给朋友 * @Author Hybrid */ wx.onMenuShareAppMessage({ title: shareConfig.message.title, // 分享标题 desc: shareConfig.message.desc, // 分享描述 link: shareConfig.message.link, // 分享连接 imgUrl: shareConfig.message.imgUrl, // 分享图标 type: '', // 分享类型,music、video或link,不填默认为link type: '', // 分享类型,music、video或link,不填默认为link dataUrl: '', // 若是type是music或video,则要提供数据连接,默认为空 success: function () { self.object.closecovershow(); // 用户确认分享后执行的回调函数 }, cancel: function () { self.object.closecovershow(); // 用户取消分享后执行的回调函数 } }); } } /** * 微信支付 * @Author Hybrid * @DateTime 2017-11-21 * @param {string} router 单页面应用,由前台通知URL * @return {[type]} [description] */ payWeChat(order_num) { let self = this; axios.get('/home/OrderConfirm/orderPay', { params: { type: 'weixin', frontUrl: location.href.split('#')[0], order_num// 订单号 } }).then(function (response) { var attachment = response.data.data;// 后台返回参数 localStorage.setItem(wechatCode, ''); //alert(location.href) WeixinJSBridge.invoke('getBrandWCPayRequest', { "appId": attachment.appId, "timeStamp": attachment.timeStamp, // 大写S "nonceStr": attachment.nonceStr, // 大写S "package": attachment.package, "signType": 'MD5', "paySign": attachment.paySign, }, function (res) { // console.log(res); if (res.err_msg == "get_brand_wcpay_request:ok") { self.object.$router.push("/paysuccess"); } else if (res.err_msg == "get_brand_wcpay_request:cancel") { self.object.$router.push("/ordercenter"); } else { // localStorage.setItem(wechatCodeOld, ''); localStorage.setItem(wechatCode, ''); //alert("支付失败!" + JSON.stringify(res) + "当前路径" + location.href); // alert("支付失败!" + JSON.stringify(res)); // resolve(-1); } }) }).catch(function (err) { console.log(JSON.stringify(err)); }) } }
在前端调用
/** * 注入配置 * this 是 vue * 注意: 分享到朋友圈或分享到朋友,每一个页面都须要配置.因此最好在每一个页面调用一下 注入配置 */ var WeChat = new AlWeChat(this) WeChat.payWeChat(12345678) // 调用支付 WeChat.wxScanQRCode(fn) // 扫一扫
支付申请:微信支付 - 公众号支付 -
请仔细阅读公众号支付开发步骤
设置支付目录
设置支付受权目录注意3点:
全部使用公众号支付方式发起支付请求的连接地址,都必须在支付受权目录之下;
最多设置5个支付受权目录,且域名必须经过ICP备案;
头部要包含http或https,须细化到二级或三级目录,以左斜杠“/”结尾。
设置支付受权目录的具体规则是这样的:
单页面应用(vue) 为了解决安卓和IOS 支付效果不一致问题,咱们一般会在url 中添加a=1(前边已经提到过),保留问号以前内容(eg:6)
1 好比:调用支付的页面地址为 http://a.b.com/pay/weixin/c.html,` 那么:受权目录配置为 http://a.b.com/pay/weixin/`
2 好比:调用支付的页面地址为 http://a.b.com/pay/weixin, 那么:受权目录配置为 http://a.b.com/pay/
3 好比:调用支付的页面地址为 http://a.b.com/pay, 那么:受权目录配置为 http://a.b.com/
4 好比:调用支付的页面地址为 http://a.b.com/pay/weixin/c.html?name=mango, 那么:受权目录配置为 http://a.b.com/pay/weixin/
5(vue spa) 好比:调用支付的页面地址为 http://a.b.com/#/pay/weixin/c.html?name=mango 那么:受权目录配置为 http://a.b.com/
6(vue spa) 好比:调用支付的页面地址为 http://a.b.com/#!/cart/index(一般咱们会改变URL http://a.b.com/?#!/cart/index) 那么:受权目录配置为 http://a.b.com/
咱们在网页受权的时候改变url
location.href = `http://m.example.com/?a=1#${location.href.split('#')[1]}`; // 增长a=1 防止支付错误 防止前台死循环
PHP
咱们基于微信支付官方demo作了优化(微信公众号支付)
源码目录 :
wxpay\wxjsapi.php
使用方法:
项目名/ThinkPHP/Library/Vendor/wxpay/wxjsapi.php
只需导入sdk文件,实例化sdk类 调用getParameters方法传入订单数据
/* * php * @name 微信支付 * @author weikai */ public function orderPay(){ $type = I('get.type'); //微信jsapi 支付 if($type=='weixin'){ // 导入微信支付sdk Vendor('wxpay.wxjsapi'); $wxpay=new \Weixinpay();//实例化sdk类 //获取订单数据传入sdk getParameters方法中 $order_num = I('get.order_num'); $orderData = M('order')->where('order_number='.$order_num)->find(); if($orderData){ $data=$wxpay->getParameters($orderData); }else{ return $this->ajaxReturn(show(0,'无此订单')); } //最后返回支付签名信息及预支付id if($data){ return $this->ajaxReturn(show(1,'签名信息',$data)); }else{ return $this->ajaxReturn(show(0,'签名信息失败')); } } }
/** * 获取jssdk须要用到的数据 * @return array jssdk须要用到的数据 */ public function getParameters($orderData){ $order=array( 'body'=>"商品描述".$orderData['order_number'],// 商品描述(须要根据本身的业务修改) 'total_fee'=>$orderData['total_price']*100,// 订单金额 以(分)为单位(须要根据本身的业务修改) 'out_trade_no'=>$orderData['order_number'],// 订单号(须要根据本身的业务修改) 'product_id'=>'10001',// 商品id(须要根据本身的业务修改) 'trade_type'=>'JSAPI',// JSAPI公众号支付 'openid'=>session('openid')// 获取到的openid ); // 统一下单 获取prepay_id 具体参照源文件内 $unified_order=$this->unifiedOrder($order); // 获取当前时间戳 $time=time(); // 组合jssdk须要用到的数据 $config = $this->config; $data=array( 'appId'=>$config['APPID'], //appid 'timeStamp'=>strval($time), //时间戳 'nonceStr'=>$this->getNonceStr(32),// 随机字符串 'package'=>'prepay_id='.$unified_order['prepay_id'],// 预支付交易会话标识 'signType'=>'MD5'//加密方式 ); // 生成签名 $data['paySign']=$this->makeSign($data); return $data; } }
成功返回信息如图:
[图片上传失败...(image-5aa7c9-1522549296208)]
返回参数说明:
appId:微信公众号标识
nonceStr:随机字符串
prepay_id:微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时
paySign:支付签名
signType:签名类型
timeStamp:时间戳
4.前端处理支付
在 WECHAT.js 中paywechat方法
5.支付结果通知
/** * 微信公众号支付回调验证 * @return array 返回数组格式的notify数据 */ public function notify(){ Vendor('wxpay.wxjsapi');//引入sdk $wxpay=new \Weixinpay();//实例化sdk类 // 获取微信支付通知的xml数据 $xml=file_get_contents('php://input', 'r'); // 转成php数组 $data=toArray($xml); // 保存原sign $data_sign=$data['sign']; // sign不参与签名 unset($data['sign']); $sign=$wxpay->makeSign($data); // 判断签名是否正确 判断支付状态 if ($sign===$data_sign && $data['return_code']=='SUCCESS' && $data['result_code']=='SUCCESS') { $result=$data; //将支付状态更新进订单表 //其余业务代码 }else{ $result=false; } // 返回状态给微信服务器 if ($result) { $str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'; }else{ $str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>'; } echo $str; return $result; }
toArray -XML转换数组的函数
/** * php * 将xml转为array * @param string $xml xml字符串 * @return array 转换获得的数组 */ function toArray($xml){ //禁止引用外部xml实体 libxml_disable_entity_loader(true); $result= json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); return $result; }
6.前台轮询判断订单支付状态,成功给用户提示。