最新接收了微信公眾號開發(fā)的任務再扭,也是第一次接觸node.js,不過這一篇我暫時先不說node.js的使用蹂窖,由于接通微信支付的確是花費了我好多時間和精力虑省,為了下次能夠快速接通當然也和大家分享一下赌躺,特以此文記錄一下逾滥。
一.首先在微信公眾號后臺
設置 -- > 公眾號設置 --> 功能設置 -- > 網(wǎng)頁授權域名 (填寫微信公眾號菜單跳轉 到 自己的網(wǎng)站的域名 例如wx.mingrivr.com)
其中
設置 -- > 公眾號設置 --> 功能設置 -- > 業(yè)務域名 (最好也設置一下烘贴,這樣如果你的網(wǎng)站需要輸入的時候就不會出現(xiàn)紅色的提示框提示謹慎輸入的字樣)
另外切換到
微信支付 --> 開發(fā)配置 (將支付授權目錄精確到你要發(fā)起支付的請求界面的url地址禁添,并以“/” 結束)
然后添加測試的白名單
將測試的微信號添加上去
二.回到程序中來
支付流程:
選擇充值的金額之后 --> 告訴自家服務器 生成一個訂單號返回 --> 微信支付 --> 支付成功、失敗之后回調(diào) --> 告訴自家服務器 余額中充值 并返回結果 -- > 如果成功 結束 桨踪;不成功 微信原路退款
在node.js 工程中上荡,建議建立一個json文件存放關于wxpay配置的相關信息
{
"wxAppId" : "", // 公眾號后臺 開發(fā) --> 基本配置 中可以看到
"wxAppSecret" : "",// 公眾號后臺 開發(fā) --> 基本配置 中可以看到
"wxMchId" : "", // 支付商戶的id
"wxPayKey" : ""http:// 支付商戶的密鑰
}
調(diào)起微信支付的時候主要就是下面一個方法
WeixinJSBridge.invoke( 'getBrandWCPayRequest',
{
"appId" :paramObj.appId, //公眾號名稱,由商戶傳入
"timeStamp":paramObj.timeStamp, //時間戳馒闷,自1970年以來的秒數(shù)
"nonceStr" :paramObj.nonceStr, //隨機串 "package":paramObj.package,
"signType":paramObj.signType, //微信簽名方式:
"paySign":paramObj.paySign //微信簽名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
//支付成功的回調(diào) 微信團隊說不一定準確 哈哈哈
}
if(res.err_msg == "get_brand_wcpay_request:cancel" || res.err_msg == "get_brand_wcpay_request:fail"){
//微信支付失敗或者取消的回調(diào)
}
});
其實在你需要調(diào)起微信支付的頁面的js里面添加這個方法就能調(diào)起微信支付酪捡,重點來啦,你看到需要傳的參數(shù)了么纳账?雖然只有五個參數(shù)逛薇,但是步驟還真是不少?一起來看看吧疏虫!
一般為了安全起見永罚,獲取這些個參數(shù)放在后臺,我的程序中也就是node.js負責的這部分卧秘。也就是說客戶端發(fā)起一個請求的時候我在route中通知自家服務端呢袱,然后發(fā)起微信請求,獲取所需的參數(shù)之后傳回到客戶端發(fā)起微信支付翅敌,收到微信支付回調(diào)信息之后再發(fā)起請求告訴自家服務器支付結果羞福。
看一下node.js中關于微信支付的代碼吧
第一步.獲取code
請求 code 的目的就是獲取用戶的 openid(用戶相對于當前公眾號的唯一標識) 和access_token,請求的 API:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
1. appid公眾號的 appid蚯涮,可以在公眾號中看到
2. redirect_uri 自定義的微信回調(diào)地址治专, 微信會在你請求完上面的地址后跳轉到你定義的redirect_uri的地址卖陵, 帶著 code,此處的 redirect_url 需要 **url_encode** *php*张峰, 如果你的程序是 node 則需要使用 **encodeURLComponent(url)**編碼
3. response_type=code泪蔫,這個沒什么好說的就是固定的
4. scope=snsapi_userinfo,也可以選擇snsapi_base 就不會出現(xiàn)綠色的授權界面
5. state=STATE 固定這樣寫就好喘批,詳細說明可以查看微信官網(wǎng)的說明
6. wechat_redirect 固定這樣寫就好撩荣,詳細說明可以查看微信官網(wǎng)的說明
第二步.獲取access_token openid
1. appid 微信公眾號 id,微信公眾號后臺獲取
2. secret 微信公眾號的密鑰饶深, 微信公眾號后臺獲取
3. code婿滓, 第一步獲取用到的 code
4. grant_type=authorization_code 固定就好
第三步.先把需要獲取的簡單的幾個參數(shù)的方法展示一下
獲取隨機的NonceStr
voucherControl.createNonceStr = function(){
return Math.random().toString(36).substr(2, 15);
};
第四步.獲取prepay_id
voucherControl.getPrepayId = function(obj){
// 生成統(tǒng)一下單接口參數(shù)
var UnifiedorderParams = {
appid : wxConfig.wxAppId,
body : obj.body,
mch_id : wxConfig.wxMchId,
nonce_str: voucherControl.createNonceStr(),
notify_url : obj.notify_url,// 微信付款后的回調(diào)地址
openid : openid,
out_trade_no : obj.out_trade_no,//new Date().getTime(), //訂單號
spbill_create_ip : obj.spbill_create_ip,
total_fee : obj.total_fee,
trade_type : 'JSAPI',
};
// 返回 promise 對象
return new Promise(function (resolve, reject) {
// 獲取 sign 參數(shù)
UnifiedorderParams.sign = voucherControl.getSign(UnifiedorderParams);
var url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
request.post(
{
url : url,
body:JSON.stringify(voucherControl.getUnifiedorderXmlParams(UnifiedorderParams))
},
function (error, response, body) {
var prepay_id = '';
if (!error && response.statusCode == 200) {
// 微信返回的數(shù)據(jù)為 xml 格式, 需要裝換為 json 數(shù)據(jù)粥喜, 便于使用
xml2jsparseString(body, {async:true}, function (error, result) {
prepay_id = result.xml.prepay_id[0];
// 放回數(shù)組的第一個元素
resolve(prepay_id);
});
} else {
reject(body);
}
});
})}
第五步.獲取sign
獲取微信支付的簽名
voucherControl.getSign = function(signParams){
// 按 key 值的ascll 排序
var keys = Object.keys(signParams);
keys = keys.sort();
var newArgs = {};
keys.forEach(function (val, key) {
if (signParams[val]){
newArgs[val] = signParams[val];
}
})
var string = queryString.stringify(newArgs)+'&key='+wxConfig.wxPayKey;
return crypto.createHash('md5').update(queryString.unescape(string), 'utf8').digest("hex").toUpperCase();
}
第六步.獲取將json數(shù)據(jù)變?yōu)閤ml數(shù)據(jù)(因為微信統(tǒng)一下單的時候body中傳數(shù)據(jù)必須為xml數(shù)據(jù))
獲取微信統(tǒng)一下單參數(shù)
voucherControl.getUnifiedorderXmlParams = function(obj){
var body = '<xml>' +
'<appid>'+wxConfig.wxAppId+'</appid>' +
'<body>'+obj.body+'</body>' +
'<mch_id>'+wxConfig.wxMchId+'</mch_id>' +
'<nonce_str>'+obj.nonce_str+'</nonce_str>' +
'<notify_url>'+obj.notify_url+'</notify_url>' +
'<openid>'+obj.openid+'</openid>' +
'<out_trade_no>'+obj.out_trade_no+'</out_trade_no>'+
'<spbill_create_ip>'+obj.spbill_create_ip+'</spbill_create_ip>' +
'<total_fee>'+obj.total_fee+'</total_fee>' +
'<trade_type>'+obj.trade_type+'</trade_type>' +
'<sign>'+obj.sign+'</sign>' +
'</xml>';
return body;
}
第七步.獲取微信支付的所有參數(shù)
微信支付的所有參數(shù) *
@param req 請求的資源, 獲取必要的數(shù)據(jù) *
@returns {{
appId: string,
timeStamp: Number,
nonceStr: *,
package: string,
signType: string,
paySign: *}}
voucherControl.getBrandWCPayParams = function(obj,callback ){
var prepay_id_promise = voucherControl.getPrepayId(obj);
prepay_id_promise.then(function (prepay_id) {
var prepay_id = prepay_id;
var wcPayParams = {
"appId" : wxConfig.wxAppId, //公眾號名稱凸主,由商戶傳入
"timeStamp" : parseInt(new Date().getTime() / 1000).toString(), //時間戳,自1970年以來的秒數(shù)
"nonceStr" : voucherControl.createNonceStr(), //隨機串 // 通過統(tǒng)一下單接口獲取
"package" : "prepay_id="+prepay_id,
"signType" : "MD5", //微信簽名方式: };
wcPayParams.paySign = voucherControl.getSign(wcPayParams); //微信支付簽名
callback(null, wcPayParams);
},function (error) {
callback(error);
});
}
第八步.將微信支付取得的參數(shù)傳回客戶端去調(diào)用微信支付吧
voucherControl.getBrandWCPayParams(obj, function (error, responseData) {
if (error) {
callback(error);
} else {
callback(null, responseData);
}
});
第九步.支付完成的回調(diào)
微信支付完了后會在 h5 頁面的微信支付的回調(diào)函數(shù)里面放回值额湘,res.err_msg == "get_brand_wcpay_request:ok" 卿吐,這樣就是成功了, 但是不是就完事兒了呢 锋华? 也不是嗡官,為什么呢? 微信真的收到錢了么毯焕? 收到的錢是不是你傳遞給微信的值呢 衍腥?你還需要將支付的結果寫數(shù)據(jù)庫什么的,這些都是未知纳猫。還記的在統(tǒng)一下單接口中有個必須參數(shù)就是notify_url : NOTIFY_URL,// 微信付款后的回調(diào)地址
這個地址是用戶傳遞給微信的婆咸, 微信在收到用戶的付款后會以 post 的方式請求這個接口,微信會傳遞用戶付款的信息過來芜辕, 不過是 xml 格式的尚骄。類系這樣的 xml 格式:
<xml>
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
<attach><![CDATA[支付測試]]></attach>
<bank_type><![CDATA[CFT]]></bank_type>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[Y]]></is_subscribe>
<mch_id><![CDATA[10000100]]></mch_id>
<nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
<openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid> <out_trade_no><![CDATA[1409811653]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign> <sub_mch_id><![CDATA[10000100]]></sub_mch_id>
<time_end><![CDATA[20140903131540]]></time_end>
<total_fee>1</total_fee>
<trade_type><![CDATA[JSAPI]]></trade_type>
<transaction_id><![CDATA[1004400740201409030005092168]]></transaction_id></xml>
根據(jù)自己的業(yè)務邏輯解析這個 xml 格式的數(shù)據(jù)就好了。注意:這里你在獲取到數(shù)據(jù)后微信需要得到你的回應侵续, 如果你一直不回應微信倔丈, 微信會請求你好幾次, 這樣估計你的邏輯會有問題吧状蜗,所以你需要給微信返回 xml 的格式的 回應需五。
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
注意點:node ,express 框架開發(fā)轧坎, 如果你在微信的支付成功后的回調(diào)中沒有獲取到任何 xml 的值
你需要安裝一組件:body-parser-xml宏邮, 你可以使用 npm install body-parser-xml --save
安裝, 在 app.js 里面 require('body-parser-xml')(bodyParser);
,使用中間件的方式
// 解決微信支付通知回調(diào)數(shù)據(jù)
app.use(bodyParser.xml({
limit: '2MB', // Reject payload bigger than 1 MB
xmlParseOptions: { normalize: true, // Trim whitespace inside text nodes normalizeTags: true, // Transform tags to lowercase
explicitArray: false // Only put nodes in array if >1
}
}));
這樣你就可以正常的獲取到微信的 xml 數(shù)據(jù)了。
pay.getAccessToken({
notify_url : 'http://demo.com/', //微信支付完成后的回調(diào)
out_trade_no : new Date().getTime(), //訂單號
attach : '名稱', body : '購買信息',
total_fee : '1', // 此處的額度為分
spbill_create_ip : req.connection.remoteAddress,
}, function (error, responseData) {
res.render('payment', { title : '微信支付',
wxPayParams : JSON.stringify(responseData)
});
});
最后我將node.js關于微信支付的代碼封裝成一個js
使用方法見js中的 useWxPay 方法蜀铲,將所需參數(shù)傳入就會得到微信下單的所有參數(shù)
/**
* Created by haHa on 2016/11/21.
* 微信支付的js文件
*/
var wxPayControl = module.exports;
var request = require("request");
var url = require('url');
var queryString = require('querystring');
var crypto = require('crypto');
var xml2jsparseString = require('xml2js').parseString;
var wxConfig = require('../z_configs/wxconfig.json');
/*
{
"wxAppId" : "",//公眾號appid
"wxAppSecret" : "",//公眾號appsecrect
"wxMchId" : "",//商戶id
"wxPayKey" : ""http://商戶密鑰
}
*/
var access_token;
var expires_in;
var refresh_token;
var openid = "";
var scope;
/*
微信支付的js文件使用
*/
function useWxPay() {
var notify_url = '你發(fā)起支付的頁面地址';
var spbill_create_ip = getClienIp(req);
var obj = {
body : "明日VR-賬戶游戲幣充值", //描述微信支付的意義
notify_url : notify_url,// 微信付款后的回調(diào)地址
out_trade_no : data.orderNo,//new Date().getTime(), //訂單號
spbill_create_ip :spbill_create_ip,
total_fee : total_fee//金額
}
wxPayControl.getAccessToken(obj,wxCode,function (err,wxParam) {
})
}
/**
* 獲取客戶端的ip
* req是發(fā)起請求的req
*/
getClienIp = function(req) {
//固定ip
return "139.224.53.144"
//因為 輸出是 ::fffff:139.224.53.144 所以固定測試一下
// return req.headers['x-forwarded-for'] ||
// req.connection.remoteAddress ||
// req.socket.remoteAddress ||
// req.connection.socket.remoteAddress;
};
/*
第一步 獲取code
*/
/**
* 獲取微信的 AccessToken 和 openid
*/
wxPayControl.getAccessToken = function(obj,wxCode,callback) {
console.log("wecode ~~~");
console.log(wxCode);
var getAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+wxConfig.wxAppId+"&secret="+wxConfig.wxAppSecret+"&code="+wxCode+"&grant_type=authorization_code";
request.post({url : getAccessTokenUrl}, function (error, response, body) {
console.log("微信支付 獲取accesstoken");
console.log(body);
if (!error && response.statusCode == 200){
if (40029 == body.errcode) {
callback(error, body);
} else {
body = JSON.parse(body);
access_token = body.access_token;
expires_in = body.expires_in;
refresh_token = body.refresh_token;
openid = body.openid;
scope = body.scope;
// 拼接微信的支付的參數(shù)
getBrandWCPayParams(obj, function (error, responseData) {
if (error) {
callback(error);
} else {
console.log("微信支付的參數(shù)");
console.log(responseData);
callback(null, responseData);
}
});
}
} else {
callback(error);
}
});
}
/**
* 獲取微信統(tǒng)一下單的接口數(shù)據(jù)
*/
function getPrepayId(obj){
// 生成統(tǒng)一下單接口參數(shù)
var UnifiedorderParams = {
appid : wxConfig.wxAppId,
body : obj.body,
mch_id : wxConfig.wxMchId,
nonce_str: createNonceStr(),
notify_url : obj.notify_url,// 微信付款后的回調(diào)地址
openid : openid,
out_trade_no : obj.out_trade_no,//new Date().getTime(), //訂單號
spbill_create_ip : obj.spbill_create_ip,
total_fee : obj.total_fee,
trade_type : 'JSAPI',
};
// 返回 promise 對象
return new Promise(function (resolve, reject) {
// 獲取 sign 參數(shù)
UnifiedorderParams.sign = getSign(UnifiedorderParams);
var url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
request.post({url : url, body:JSON.stringify(getUnifiedorderXmlParams(UnifiedorderParams))}, function (error, response, body) {
console.log("微信下單返回數(shù)據(jù)");
console.log(response);
var prepay_id = '';
if (!error && response.statusCode == 200) {
// 微信返回的數(shù)據(jù)為 xml 格式, 需要裝換為 json 數(shù)據(jù)属百, 便于使用
xml2jsparseString(body, {async:true}, function (error, result) {
prepay_id = result.xml.prepay_id[0];
// 放回數(shù)組的第一個元素
resolve(prepay_id);
});
} else {
reject(body);
}
});
})
}
/**
* 微信支付的所有參數(shù)
* @param req 請求的資源, 獲取必要的數(shù)據(jù)
* @returns {{appId: string, timeStamp: Number, nonceStr: *, package: string, signType: string, paySign: *}}
*/
function getBrandWCPayParams(obj,callback ){
var prepay_id_promise = getPrepayId(obj);
prepay_id_promise.then(function (prepay_id) {
var prepay_id = prepay_id;
var wcPayParams = {
"appId" : wxConfig.wxAppId, //公眾號名稱记劝,由商戶傳入
"timeStamp" : parseInt(new Date().getTime() / 1000).toString(), //時間戳,自1970年以來的秒數(shù)
"nonceStr" : createNonceStr(), //隨機串
// 通過統(tǒng)一下單接口獲取
"package" : "prepay_id="+prepay_id,
"signType" : "MD5", //微信簽名方式:
};
wcPayParams.paySign = getSign(wcPayParams); //微信支付簽名
console.log(wcPayParams.paySign);
console.log("微信支付的所有參數(shù)");
console.log(wcPayParams);
callback(null, wcPayParams);
},function (error) {
callback(error);
});
}
/**
* 獲取微信統(tǒng)一下單參數(shù)
*/
function getUnifiedorderXmlParams(obj){
var body = '<xml>' +
'<appid>'+wxConfig.wxAppId+'</appid>' +
'<body>'+obj.body+'</body>' +
'<mch_id>'+wxConfig.wxMchId+'</mch_id>' +
'<nonce_str>'+obj.nonce_str+'</nonce_str>' +
'<notify_url>'+obj.notify_url+'</notify_url>' +
'<openid>'+obj.openid+'</openid>' +
'<out_trade_no>'+obj.out_trade_no+'</out_trade_no>'+
'<spbill_create_ip>'+obj.spbill_create_ip+'</spbill_create_ip>' +
'<total_fee>'+obj.total_fee+'</total_fee>' +
'<trade_type>'+obj.trade_type+'</trade_type>' +
'<sign>'+obj.sign+'</sign>' +
'</xml>';
return body;
}
/**
* 獲取微信支付的簽名
* @param payParams
*/
function getSign(signParams){
// 按 key 值的ascll 排序
console.log('支付簽名');
console.log(signParams);
var keys = Object.keys(signParams);
keys = keys.sort();
var newArgs = {};
keys.forEach(function (val, key) {
if (signParams[val]){
newArgs[val] = signParams[val];
}
})
var string = queryString.stringify(newArgs)+'&key='+wxConfig.wxPayKey;
console.log(string);
// 生成簽名
return crypto.createHash('md5').update(queryString.unescape(string), 'utf8').digest("hex").toUpperCase();
}
/**
* 獲取隨機的NonceStr
*/
function createNonceStr(){
return Math.random().toString(36).substr(2, 15);
}