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 };

 

 

 在这里再次感谢在人生路上帮助我的人!谢谢你们。

如果以上代码对您有用,欢迎打赏!如有错误的地方也请您留言指出。

          (支付宝)                                             (微信)

 

posted @ 2018-05-02 15:25  jimmy.wang123  阅读(1402)  评论(0编辑  收藏  举报