看到網上的大部分問題都集中在簽名部分,請你們必定請熟讀微信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.前臺輪詢判斷訂單支付狀態,成功給用戶提示。