VUE+node 实现微信支付功能
初始微信公众号有个机会去做了一个微信公众号的项目,功能很简单就是支付商城。是需要从微信服务号中跳转web页面,本人第一次接触所以上手后各种问题!首先公众号申请就浪费了好长时间,这里建议提前一个月申请,各种认证,唯一要注意一点是!!!!现在的微信支付都是在商户号里注册和管理的,不是只需要注册微信支付就可以了。其次、就是公众号中自己的服务器配置和服务器与微信端认证。这里认证需要将他指定的文...
初始微信公众号
有个机会去做了一个微信公众号的项目,功能很简单就是支付商城。是需要从微信服务号中跳转web页面,本人第一次接触所以上手后各种问题!
首先公众号申请就浪费了好长时间,这里建议提前一个月申请,各种认证,唯一要注意一点是!!!!现在的微信支付都是在商户号里注册和管理的,不是只需要注册微信支付就可以了。
其次、就是公众号中自己的服务器配置和服务器与微信端认证。这里认证需要将他指定的文件放到服务器中就可以了。微信文档中有详解。
搭建环境
vue环境
除了基本配置环境以外还需引入weixin-js-sdk
直接npm install weixin-js-sdk
node环境
express框架 、wechat(token认证时用的)、node-scheduel(计时器作用)、request、ejs(模板引擎)、q(解决回掉地域问题)、crypto(加密解密)
前期准备
废话不多说!先说思想!首先、点击导航按钮进入网站,进入后需要得到用户的信息和唯一的id也就是openid。然后用户发起支付请求,也就是吊起微信支付。完成支付和取消支付。前端就这些内容!
上代码!!!!
按钮的链接为:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxea9b4122f726a558&redirect_uri=你的网站域名?@&response_type=code&scope=snsapi_userinfo&state=login#wechat_redirect
跳转之后域名后的code=******就是请求openid的code值
将code传到服务器端
//前端请求接口getOpenid node获取openid
router.post('/getOpenid', (req, res) => {
var params = req.body;
var url="https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
url = url.replace("APPID",APP_ID);//你的app_id
url = url.replace("SECRET",APP_SECRET);//你的aoo_secret
url = url.replace("CODE",params.code);//传上来的code
request(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log("openid",body);
jsonWrite(res, body);
}
});
});
code请求成功后会返回openid、access_token。然后将openid传回去后获取微信用户信息。
//获取userinfo
router.post('/userinfo', (req, res) => {
var params = req.body;
var sql1 = $sql.wxuser.MakeSureUser;//先判断自己的数据库中是不是存在用户信息 如果存在就直接从数据库中获取避免多次请求
console.log("请求接口","userinfo");
console.log("sql",sql1);
conn.query(sql1,[
params.openid
], function(err, result) {
if (err) {
console.log(err);
}else if (result) {
console.log("数据库中存在用户信息",params.openid,result);
if (result!="") {
jsonWrite(res, result);
return;
}else{
var content = req.body;
var access_token =Trim(main.access_token,':g');//这里是全局的access_token服务器端的access_token 不是前端的access_token
//这里access_token太长传过来后容易存在换行 所以要去掉空格
var url="https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
url = url.replace("ACCESS_TOKEN",access_token);
url = url.replace("OPENID",content.openid);
request(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log("userinfo",body);
body = JSON.parse(body);
var sql = $sql.wxuser.userinfo;
console.log("接口名称将用户信息存入数据库中:/userinfo","sql",sql);
console.log("请求参数:",params);
conn.query(sql,[
params.openid,
body.nickname,
body.sex,
body.city,
body.province,
body.country,
body.headimgurl,
body.remark,//运营者对用户的备注
body.groupid,
body.tagid_list+"",
body.subscribe_scene,//返回用户关注的渠道来源,ADD_SCENE_SEARCH 公众号搜索,ADD_SCENE_ACCOUNT_MIGRATION 公众号迁移,ADD_SCENE_PROFILE_CARD 名片分享,ADD_SCENE_QR_CODE 扫描二维码,ADD_SCENEPROFILE LINK 图文页内名称点击,ADD_SCENE_PROFILE_ITEM 图文页右上角菜单,ADD_SCENE_PAID 支付后关注,ADD_SCENE_OTHERS 其他
], function(err, result) {
if (err) {
console.log(err);
}else if (result) {
var str ={'0':body};
str = JSON.stringify(str);
jsonWrite(res, str);
console.log("请求结果 Home!",result)
}
})
}
})
}}
});
});
function Trim(str,is_global)
{
var result;
result = str.replace(/(^\s+)|(\s+$)/g,"");
if(is_global.toLowerCase()=="g")
{
result = result.replace(/\s/g,"");
}
return result;
}
这里最重要的是access_token的获取,access_token需要7200ms后刷新。这里就需要用到计时器(node-scheduel)。
function getToken(){
request.get({
uri: 'https://api.weixin.qq.com/cgi-bin/token',
json: true,
qs: {
grant_type: 'client_credential',
appid: APPID, // APPID请换成你的 appid
secret: APPSECRET // APPSECRET请换成你的 appsecret
}
}, (err, res, body) => {
if (err) {
console.log(err)
return
}
console.log(body)
if (body.errcode) {
// 返回错误时的处理
console.log("请求token错误!");
return
}else{
var token = body.access_token;
console.log("请求token成功",token);
access_token=token;
module.exports.access_token = access_token;
var rule = new schedule.RecurrenceRule();//建立计时器
var times = [];
for(var i=1; i<60; i++){
times.push(i);
}
rule.second = times;//每一秒都在进行判断 schedule没有7200ms所以我想到了没秒都执行用reqtime来起到计时的作用
var reqtime = 0;//请求时间
var j = schedule.scheduleJob(rule, function(){
reqtime =reqtime+1;
if(reqtime > 7199){//到了7200秒后 重新申请access_token
reqtime = 0;
getToken();
}
});
j;
}
})
}
前期准备就这些。最主要的就是openid的获取和access_token的获取。
开始支付
支付的pay.vue文件
引入wxpay.vue
import wexinpay from './wxpay'
postData:function(data) {
var params = {
openId: data.openid,
ordersall: data.ordersall,
orderid: data.orderid,
content: "你买了导弹!"
};
var vm = this;
this.$http.post("/api/payReseredFee",params,{}).then(function (result) {
console.log("支付返回",result,wexinpay);
wexinpay.wexinPay(result.body,vm.postOrderlist,vm.closeOrderlist);
})
}
wxpay.vue
<script>
import wx from 'weixin-js-sdk'
function wexinPay(data,cb,errorCb){
var appId = data.appId;
var timestamp = data.timeStamp;
var nonceStr = data.nonceStr;
var signature = data.signType;
var packages = data.package;
var paySign = data.paySign;
console.log("微信支付参数",appId,timestamp,nonceStr,signature,packages,paySign);
wx.config({ //微信的相应配置
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: appId, // 必填,公众号的唯一标识
timestamp: timestamp, // 必填,生成签名的时间戳
nonceStr: nonceStr, // 必填,生成签名的随机串
signature: signature, // 必填,签名,见附录1
jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
wx.ready(function(){
wx.chooseWXPay({
timestamp: timestamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: nonceStr, // 支付签名随机串,不长于 32 位
package: "prepay_id="+packages, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=*** 这里注意一下
signType: 'MD5', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: paySign, // 支付签名
success: function(res) {
// 支付成功后的回调函数
cb();
},
fail:function(res){
errorCb();
}
});
});
wx.error(function(res) {
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
/*alert("config信息验证失败");*/
});
}
export default{
wexinPay
}
</script>
服务器端接受数据
api.js
//微信支付接口
router.post('/payReseredFee', function(req, res, next){
var attach = "123";
var body = "text";
var mch_id = "150096****"; //商户ID
var openid = req.body.openId;
var bookingNo = req.body.orderid; //订单号
var total_fee = req.body.ordersall*100; //以分为单位
console.log("请求体",req.body);
var notify_url = "http://*****.cn/notify";//通知地址
var spbill_create_ip = getClientIp(req);//这里获取用户ip
if(spbill_create_ip.split(",").length>1){
spbill_create_ip= spbill_create_ip.split(",")[0];
}
console.log("支付请求",attach, body, mch_id, openid, bookingNo, total_fee, notify_url,spbill_create_ip);
wxpay.order(attach, body, mch_id, openid, bookingNo, total_fee, notify_url,spbill_create_ip).then(function(data){
console.log("支付返回",data);
jsonWrite(res, data);
});
});
//获取用户ip
function getClientIp(req) {
return req.headers['x-forwarded-for'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress;
};
服务器端处理数据,需要传输相应的编码和拼接
wxpay.js
var Q = require("q");
var request = require("request");
var crypto = require('crypto');
var ejs = require('ejs');
var fs = require('fs');
var key = "6jDfpmTSJm67maNZRKezGRFfH6******"; //商户号中预留的key
var messageTpl = fs.readFileSync(__dirname + '/message.ejs', 'utf-8');
var WxPay = {
getXMLNodeValue: function(node_name, xml) {
var tmp = xml.split("<" + node_name + ">");
if(tmp.length <2){
return "";
}else{
var _tmp = tmp[1].split("</" + node_name + ">");
return _tmp[0];
}
},
raw: function(args) {
var keys = Object.keys(args);
keys = keys.sort()
var newArgs = {};
keys.forEach(function(key) {
newArgs[key] = args[key];
});
var string = '';
for (var k in newArgs) {
string += '&' + k + '=' + newArgs[k];
}
string = string.substr(1);
return string;
},
paysignjs: function(appid, nonceStr, package, signType, timeStamp) {
var ret = {
appId: appid,
nonceStr: nonceStr,
package: package,
signType: signType,
timeStamp: timeStamp
};
var string = this.raw(ret);
string = string + '&key=' + key;
var sign = crypto.createHash('md5').update(string, 'utf8').digest('hex');
return sign.toUpperCase();
},
paysignjsapi: function(appid, attach, body, mch_id, nonce_str, notify_url, openid, out_trade_no, spbill_create_ip, total_fee, trade_type) {
var ret = {
appid: appid,
attach: attach,
body: body,
mch_id: mch_id,
nonce_str: nonce_str,
notify_url: notify_url,
openid: openid,
out_trade_no: out_trade_no,
spbill_create_ip: spbill_create_ip,
total_fee: total_fee,
trade_type: trade_type
};
var string = this.raw(ret);
console.log("#1.生成字符串",string);
string = string + '&key=' + key; //key为在微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
console.log("#2.连接商户key:",string);
var crypto = require('crypto');
var sign = crypto.createHash('md5').update(string, 'utf8').digest('hex');
console.log("#3.md5编码并转成大写",sign.toUpperCase());
return sign.toUpperCase();
},
// 随机字符串产生函数
createNonceStr: function() {
return Math.random().toString(36).substr(2, 15);
},
// 时间戳产生函数
createTimeStamp: function() {
return parseInt(new Date().getTime() / 1000) + '';
},
order: function(attach, body, mch_id, openid, bookingNo, total_fee, notify_url,spbill_create_ip) {
var deferred = Q.defer();
var appid = 'wxea9b4122f7****';
var nonce_str = this.createNonceStr();
var timeStamp = this.createTimeStamp();
var url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
var formData = "<xml>";
formData += "<appid>" + appid + "</appid>"; //appid
formData += "<attach>" + attach + "</attach>"; //附加数据
formData += "<body>" + body + "</body>";
formData += "<mch_id>" + mch_id + "</mch_id>"; //商户号
formData += "<nonce_str>" + nonce_str + "</nonce_str>"; //随机字符串,不长于32位。
formData += "<notify_url>" + notify_url + "</notify_url>";
formData += "<openid>" + openid + "</openid>";
formData += "<out_trade_no>" + bookingNo + "</out_trade_no>";
formData += "<spbill_create_ip>"+spbill_create_ip+"</spbill_create_ip>";
formData += "<total_fee>" + total_fee + "</total_fee>";
formData += "<trade_type>JSAPI</trade_type>";
formData += "<sign>" + this.paysignjsapi(appid, attach, body, mch_id, nonce_str, notify_url, openid, bookingNo,spbill_create_ip , total_fee, 'JSAPI') + "</sign>";
formData += "</xml>";
var self = this;
console.log("签名xml",formData);
request({
url: url,
method: 'POST',
body: formData
}, function(err, response, body) {
console.log("请求支付返回结果",body);
if (!err && response.statusCode == 200) {
console.log(body);
var prepay_id = self.getXMLNodeValue('prepay_id', body.toString("utf-8"));
if(prepay_id === ""){return;}
var tmp = prepay_id.split('[');
var tmp1 = tmp[2].split(']');
//签名
var _paySignjs = self.paysignjs(appid, nonce_str, 'prepay_id=' + tmp1[0], 'MD5', timeStamp);
var args = {
appId: appid,
timeStamp: timeStamp,
nonceStr: nonce_str,
signType: "MD5",
package: tmp1[0],
paySign: _paySignjs
};
deferred.resolve(args);
} else {
console.log(body);
}
});
return deferred.promise;
},
//支付回调通知
notify: function(obj) {
var output = "";
if (obj.return_code == "SUCCESS") {
var reply = {
return_code: "SUCCESS",
return_msg: "OK"
};
} else {
var reply = {
return_code: "FAIL",
return_msg: "FAIL"
};
}
output = ejs.render(messageTpl, reply);
return output;
},
};
module.exports = WxPay;
最后
记得在商户号中配置支付页面。可以开debug模式这样可以用手机测试了!
更多推荐
所有评论(0)