node 微信支付
基于node 实现微信支付功能
需要了解的网站:微信支付
流程图:
1.
1.我的路由:
const Koa = require('koa') const app = new Koa() const path = require('path') const route = require('koa-route'); const static = require('koa-static'); const keyBody = require('koa-body') // routes const { oauth } = require('./routes/accredit/oauth'); const { token } = require('./routes/accredit/token'); const { order } = require('./routes/order/order'); const { payComplete } = require('./routes/order/payComplete'); const rootPath = path.join(__dirname + '/View') const _static = static(rootPath) // 中间件 const logger = async(ctx, next) => { const rt_start = Date.now() await next() const rt_end = Date.now() ctx.set('X-Response-Time', `${rt_end - rt_start}ms`); console.log(ctx.request.method, ctx.url, `${rt_end - rt_start}ms`) } app.use(_static) // 静态资源 app.use(keyBody()) // req body数据获取 (非参数序列化) app.use(logger) // 日志 // page route app.use(route.get('/oauth', oauth)); //授权 app.use(route.get('/token', token)); //获取openid app.use(route.get('/order', order)); //下订单 1 app.use(route.post('/payComplete', payComplete)); //支付成功回复通知 3 app.listen(8088, (err) => { if (err) { console.error(err) } console.log('Listening At:', 8088) })
2. 通过访问 /order (微信支付) 下单
var request = require('request'); var wxpay = require('./wxpay'); var config = require('./../config'); var axios = require('axios') /* 订单 */ const order = async(ctx, next) => { const { request: req, response: res } = ctx // 发送 统一下单参数 var orderInfo = { attach: req.query.attach, // 深圳分店 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 body: req.query.num, //腾讯充值中心-QQ会员充值 商品简单描述, 该字段请按照规范传递,具体请见参数规定 mch_id: config.mch_id, //微信支付分配的商户号 openid: req.query.openid, //trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。 total_fee: req.query.money, //payFee, 订单总金额,单位为分,详见支付金额 notify_url: config.notify_url, //异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 out_trade_no: req.query.OrderNum, //商户订单号 out_trade_no ip: '127.0.0.1', // 127.0.0.1, spbill_create_ip 终端IP APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP };
//存储订单//
// 参数成功回调 var result = await wxpay.order(orderInfo).then(function(data) { console.log('支付成功 返回下单参数', JSON.stringify(data)); // 成功返回 下单参数 return JSON.stringify(data); }); ctx.type = 'json'; ctx.body = result; }; module.exports = { order };
注意:这里我存储订单是为了微信支付成功后获取支付时的订单金额,以便判断订单的安全。
1.1 下单成功后(前端页面调用)
//弹框确认支付 jsApiCall: function() { var self = this; WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId": this.user.appId, //公众号名称,由商户传入 "timeStamp": this.user.timeStamp, //时间戳,自1970年以来的秒数 "nonceStr": this.user.nonceStr, //随机串 "package": this.user.package, "signType": this.user.signType, //微信签名方式: "paySign": this.user.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") { console.log('res', res); } else { console.log('err', res); } // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 } ); }, callpay: function() { if (typeof WeixinJSBridge == "undefined") { if (document.addEventListener) { document.addEventListener('WeixinJSBridgeReady', jsApiCall, false); } else if (document.attachEvent) { document.attachEvent('WeixinJSBridgeReady', jsApiCall); document.attachEvent('onWeixinJSBridgeReady', jsApiCall); } } else { this.jsApiCall(); } },
2.1 wxpay.js
var express = require('express'); var request = require('request'); var Q = require("q"); var crypto = require('crypto'); var ejs = require('ejs'); var fs = require('fs'); // 需要的参数设置 自行定义 var config = require("./../config"); var router = express.Router(); var axios = require('axios') /* 微信支付 */ var key = config.baibu.key; //此处为申请微信支付的API密码 var APPID = config.AppID; var WxPay = { //解析XML getXMLNodeValue: function(node_name, xml) { var tmp = xml.split("<" + node_name + ">"); 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); string = string + '&key=' + key; //key为在微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 var crypto = require('crypto'); var sign = crypto.createHash('md5').update(string, 'utf8').digest('hex'); return sign.toUpperCase(); }, // 随机字符串产生函数 createNonceStr: function() { return Math.random().toString(36).substr(2, 15); }, // 时间戳产生函数 createTimeStamp: function() { return parseInt(new Date().getTime() / 1000) + ''; }, // 此处的attach不能为空值 否则微信提示签名错误 order: function(_order) { var deferred = Q.defer(); var appid = APPID; 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>" + _order.attach + "</attach>"; //附加数据 formData += "<body>" + _order.body + "</body>"; formData += "<mch_id>" + _order.mch_id + "</mch_id>"; //商户号 formData += "<nonce_str>" + nonce_str + "</nonce_str>"; //随机字符串,不长于32位。 formData += "<notify_url>" + _order.notify_url + "</notify_url>"; formData += "<openid>" + _order.openid + "</openid>"; formData += "<out_trade_no>" + _order.out_trade_no + "</out_trade_no>"; // 建议根据当前系统时间加随机序列来生成订单号 formData += "<spbill_create_ip>" + _order.ip + "</spbill_create_ip>"; formData += "<total_fee>" + _order.total_fee + "</total_fee>"; formData += "<trade_type>JSAPI</trade_type>"; //交易类型 formData += "<sign>" + this.paysignjsapi(appid, _order.attach, _order.body, _order.mch_id, nonce_str, _order.notify_url, _order.openid, _order.out_trade_no, _order.ip, _order.total_fee, 'JSAPI') + "</sign>"; formData += "</xml>"; var self = this; request({ url: url, method: 'POST', body: formData }, function(err, response, body) { if (!err && response.statusCode == 200) { console.log('11支付成功', body); var prepay_id = self.getXMLNodeValue('prepay_id', body.toString("utf-8")); var tmp = prepay_id.split('['); var tmp1 = tmp[2].split(']'); console.log('prepay_id', tmp1[0]); //签名 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: 'prepay_id=' + tmp1[0], paySign: _paySignjs }; console.log('11.1', args); deferred.resolve(args); } else { console.log('12支付失败', body); } }); return deferred.promise; }, // 支付成功 返回验证 签名 MakeSign: function(MaSign) { var ret = { appid: MaSign.appid, attach: MaSign.attach, bank_type: MaSign.bank_type, cash_fee: MaSign.cash_fee, fee_type: MaSign.fee_type, is_subscribe: MaSign.is_subscribe, mch_id: MaSign.mch_id, nonce_str: MaSign.nonce_str, openid: MaSign.openid, out_trade_no: MaSign.out_trade_no, result_code: MaSign.result_code, return_code: MaSign.return_code, time_end: MaSign.time_end, total_fee: MaSign.total_fee, trade_type: MaSign.trade_type, transaction_id: MaSign.transaction_id, }; var string = this.raw(ret); string = string + '&key=' + key; //key为在微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 var crypto = require('crypto'); var sign = crypto.createHash('md5').update(string, 'utf8').digest('hex'); return sign.toUpperCase(); }, //支付回调通知 notify: function(obj) { var output = ""; if (obj.return_code == "SUCCESS") { var reply = { return_code: "SUCCESS", return_msg: "OK" }; output = "<xml><return_code><![CDATA[" + reply.return_code + "]]></return_code><return_msg><![CDATA[" + reply.return_msg + "]]></return_msg></xml>"; } else { var reply = { return_code: "FAIL", return_msg: "FAIL" }; output = "<xml><return_code><![CDATA[" + reply.return_code + "]]></return_code><return_msg><![CDATA[" + reply.return_msg + "]]></return_msg></xml>"; } return output; }, }; module.exports = WxPay;
3. 支付成功通知
var request = require('request'); var config = require("./../config"); // 需要的参数设置 自行定义 var WxPay = require("./wxpay"); // 支付成功回调接口 const payComplete = async(ctx, next) => { const { request: req, response: res } = ctx let obj = await postData(ctx); //签名验证,并校验返回的订单金额是否与商户侧的订单金额一致 var MaSign = WxPay.MakeSign(obj); //生成验签签名 var fee = await WxPay.GetOrderInfo(obj).then(function(res) { if (res.status === 200) { var info = res.data; if (info.code === '1') { return info.data[0]; } } }).catch(function(err) { console.log('err', err); }); var Results = ''; if (MaSign === obj.sign && fee.total_fee == obj.total_fee) { //签名验证 // console.log('MaSign,obj.sign', MaSign, obj.sign); // console.log('fee', fee.total_fee, obj.total_fee); Results = WxPay.notify(obj); //判断微信返回 -->通知结果 并 -->返回通知给微信 } ctx.body = Results; }; function postData(ctx) { return new Promise((resolve, reject) => { try { var _da = ''; ctx.req.on('data', (data) => { /*微信服务器传过来的是xml格式的,是buffer类型,因为js本身只有字符串数据类型,所以需要通过toString把xml转换为字符串*/ _da += data.toString("utf-8"); }) ctx.req.on('end', () => { console.log("end: " + _da); let postdata = parser(_da); resolve(postdata) }) } catch (err) { reject(err) } }) } function parser(_da) { var xml = _da; xml = xml.slice('<xml>'.length, xml.indexOf('</xml>')); var tag = xml.match(/<\w+>/g); var len = tag.length; var obj = new Object(); //将微信 支付成功 返回的 xml 数据处理为json格式 for (var i = 0; i < len; i++) { var node_name = tag[i].replace(/\<|\>/g, ''); var tmp = xml.split("<" + node_name + ">"); var _tmp = tmp[1].split("</" + node_name + ">"); var result = _tmp[0]; if (result.match(/^(?!.*CDATA)/)) { obj[node_name] = result; } else { obj[node_name] = result.split('[')[2].split(']')[0]; } } return obj; } module.exports = { payComplete };
在这里再次感谢在人生路上帮助我的人!谢谢你们。
如果以上代码对您有用,欢迎打赏!如有错误的地方也请您留言指出。
(支付宝) (微信)