前言
花費了一天時間,調通了微信公眾號支付。作下記錄,方便以后再次填坑。先聲明,微信公眾號支付,不同於微信H5支付,這點在本文結束時再詳細說明。
微信配置
設置測試目錄
在微信公眾平台設置,欄目見下圖。支付測試狀態下,設置測試目錄,測試人的微信號添加到白名單,發起支付的頁面目錄必須與設置的精確匹配。並將支付鏈接發到對應的公眾號會話窗口中才能正常發起支付測試。注意正式目錄一定不能與測試目錄設置成一樣,否則支付會出錯。

設置正式支付目錄
根據圖中欄目順序進入修改欄目,勾選JSAPI網頁支付開通該權限,並配置好支付授權目錄,該目錄必須是發起支付的頁面的精確目錄,子目錄下無法正常調用支付。具體界面如圖所示:

交互流程
業務流程時序圖
首先看下微信官方給出的交互流程:

服務器交互
微信公眾號支付的流程,簡而言之只有兩步。第一步,下單,拿到prepay_id等信息;第二步,利用第一步拿到的prepay_id等信息進行支付。

代碼
Node端
// 微信支付
exports.weixinpay = function(req, res){
var orderId = req.params.orderId;
var selectSum = req.params.selectSum;
var token = req.params.token;
// 獲取code
if(!req.query.code){
var r_url = 'http://' +config.host+'/artist/weixinpay/'+orderId+'/'+selectSum+'/'+token;
var url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid='+config.weixin.appid+'&redirect_uri='+urlencode(r_url)+'&response_type=code&scope=snsapi_userinfo&state=111#wechat_redirect';
res.redirect(url);
}else{
var code = req.query.code;
var state = req.query.state;
var ep = new eventproxy();
ep.all('userInfo',function(userInfoData){
var userInfoData = JSON.parse(userInfoData);
res.render('artist/weixinpay',{
// 其他數據
openid: userInfoData.openid,
orderId: orderId,
selectSum: selectSum,
token: token
});
});
// 獲取用戶信息userInfo
}
}
exports.weixinpay2 = function(req, res){
var wxParamUrl = config.apihost + '/order/getH5SubOrderforWechat';
var param = {
orderId: req.query.orderId,
selectSum: req.query.selectSum,
accessToken: req.query.token,
openId: req.query.openid
};
request.post({url: wxParamUrl,form: JSON.stringify(param)},function(error, response, body){
res.render('artist/weixinpay2',{
// 其他數據
wxParam: JSON.parse(response.body)
});
});
}
上面的代碼看起來很奇怪,為什么需要一個weixinpay2函數?代碼全部放在weixinpay函數里面,然后在weixinpay.html里調用微信支付接口,不是很好么?這個,就涉及到支付目錄的問題了。weixinpay函數中,微信授權獲取用戶信息后的回調url,目錄太深,而且不確定,因為我們是通過目錄來傳遞參數的。這樣,就無法在微信公眾號上面設置支付授權目錄。所以,我們不能在weixinpay.html這個頁面發起支付請求,只能把參數轉發給下一個weixinpay2.html頁面,在weixinpap2.html中發起支付請求。
那么,為什么獲取用戶信息后的回調url目錄太深?直接把orderId等信息當做參數放在回調url后面不就可以了嗎?很遺憾,回調url無法帶入你的自定義參數。因此,只能把參數當做回調url的一部分,也就是目錄的一部分。
綜上,weixinpay函數負責獲取用戶的openid,weixinpay.html負責跳轉weixinpay2.html;weixinpay2函數負責獲取調用微信JSAPI的參數,weixinpay2.html負責調用微信JSAPI。
html
<!--weixinpay.html-->
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>微信支付</title>
</head>
<body>
<a id="weixin-link" href="/artist/weixinpay2?orderId=<%= orderId%>&selectSum=<%= selectSum%>&token=<%= token%>&openid=<%= openid%>" style="display: none">微信支付</a>
<script>
var link = document.getElementById('weixin-link');
link.click();
</script>
</body>
</html>
<!--weixinpay2.html-->
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>微信支付</title>
</head>
<body>
<input id="appId" type="hidden" value="<%= wxParam.obj.appid%>">
<input id="timeStamp" type="hidden" value="<%= wxParam.obj.timestamp%>">
<input id="nonceStr" type="hidden" value="<%= wxParam.obj.noncestr%>">
<input id="package" type="hidden" value="<%= wxParam.obj.packagestr%>">
<input id="signType" type="hidden" value="MD5">
<input id="paySign" type="hidden" value="<%= wxParam.obj.sign%>">
<script>
function onBridgeReady(){
var appId = document.getElementById('appId').value;
var timeStamp = document.getElementById('timeStamp').value;
var nonceStr = document.getElementById('nonceStr').value;
var package = document.getElementById('package').value;
var signType = document.getElementById('signType').value;
var paySign = document.getElementById('paySign').value;
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": appId, //公眾號名稱,由商戶傳入
"timeStamp":timeStamp, //時間戳,自1970年以來的秒數
"nonceStr": nonceStr, //隨機串
"package": package,
"signType": signType, //微信簽名方式:
"paySign": paySign //微信簽名
},
function(res){
WeixinJSBridge.log(res.err_msg);
//alert(res.err_code + res.err_desc + res.err_msg);
if (res.err_msg == "get_brand_wcpay_request:ok") {
window.location.href = '/artist/alipayresult?trade_status=TRADE_SUCCESS';
// 執行跳轉頁面....
} else if (res.err_msg == "get_brand_wcpay_request:cancel") {
alert ("用戶取消支付!");
} else {
alert ("支付失敗!");
}
}
);
}
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>
</body>
</html>
為了加快執行速度,這里的兩個頁面中,都使用原生的js來實現頁面邏輯。
錯誤
支付驗證簽名失敗

很明顯,支付驗證簽名失敗。怎么判斷算出的簽名是否正確?使用微信支付接口簽名校驗工具。
校驗方式選擇自定義參數,把需要簽名的參數名稱和參數值輸入頁面,點擊生成簽名即可。
當前頁面的URL未注冊
不好意思,這個錯誤忘記截圖了。借來了一張圖,湊合着看。

這個問題,是由於調用支付接口的url不對!那么,怎樣是對的呢?舉個栗子:如果調用支付接口的url為http://wx.voidking.com/pay/weixinpay,那么微信公眾平台上的支付授權目錄應該設置為http://wx.voidking.com/pay/。也就是說,支付授權目錄應該設置為調用支付接口的url的上一級目錄。
支付成功


后記
微信公眾號支付,兩個缺點,一是必須在微信瀏覽器使用,二是必須有一個拉取用戶信息的步驟。但是我們發現,很多網站,在其他瀏覽器也可以使用微信支付,也不需要拉取用戶信息,這是怎么回事?
因為,這是兩種不同的支付方式!今天我們討論的,叫做微信公眾號支付;而在其他瀏覽器中調用微信支付,叫做微信H5支付!
更多關於微信H5支付的內容,請參考微信H5支付官方文檔。
書簽
微信支付開發文檔——公眾號支付
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
微信支付接口簽名校驗工具
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=20_1
非微信內置瀏覽器中的網頁調起微信支付的方案研究
http://blog.csdn.net/ahence/article/details/51317814
微信支付|商戶平台開發者文檔
https://pay.weixin.qq.com/wiki/doc/api/wap.php?chapter=15_1
【微信支付V2.0】H5支付實例
http://wxpay.weixin.qq.com/pub_v2/pay/wap.v2.php
