首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Express与JS-SDK实现微信支付

Express与JS-SDK实现微信支付

作者头像
越陌度阡
发布2020-11-26 14:58:09
发布2020-11-26 14:58:09
6.7K0
举报

在微信开发中,最复杂的莫过于微信支付流程了,前端、服务端、微信平台要通过一系列的交互才能成功支付,官方 文档也不是很友好,下面这张图就是来自官方文档的,是不是感觉特别复杂。

看了半天,绕来绕去的,虽然很规范,但却不容易理解,下面给大家上一张简单粗爆一点的。

什么?你还是看不懂,那我只能上代码了。

首先是客户端页面代码

代码语言:javascript
复制
<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>微信支付功能</title>
    <!-- 引入JQuery -->
    <script src="http://cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script>
    <!-- 引入JS-SDK -->
    <script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
    <!-- 引入手机调试工具 -->
    <script src="http://wechatfe.github.io/vconsole/lib/vconsole.min.js?v=3.2.0"></script>

    <script>
        // 全局域名地址
        var URL = 'http://www.xiaoduanfa.com'

        // 手机调试工具
        var vConsole = new VConsole();

        // 获取code的方法
        function getQueryString(name) {
            var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
            var r = window.location.search.substr(1).match(reg);
            if (r != null) return decodeURI(r[2]);
            return null;
        };

        // 获取code
        var code = getQueryString('code');

        // 获取openid
        $(function () {
            // 获取openid
            $.get(URL + '/getOpenId?code=' + code, function (response) {
                var openid = response.openid;
                wxInit(openid)
            })
        });

        // 获取配置信息初始化JS-SDK
        function wxInit(openid) {
            var param = { 'cmd': 'get_js_config', 'url': kk };
            // 发送请求获取config
            $.ajax({
                url: URL + '/wechat',
                type: 'POST',
                data: param,
                datatype: 'json',
                success: function (data) {
                    // 配置JS-SDK
                    wx.config(data);
                    // 微信JS-SDK加载成功
                    wx.ready(function () {
                        // 点击扫码
                        document.getElementById('#scan').onclick = function () {
                            wx.scanQRCode({
                                // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
                                needResult: 1,
                                // 可以指定扫二维码还是一维码,默认二者都有
                                scanType: ["qrCode", "barCode"],
                                success: function (res) {
                                    var result = res.resultStr;
                                    alert(result);
                                }, fail: function (err) {
                                    alert(JSON.stringify(err))
                                }
                            });
                        };
                        // 点击支付
                        document.querySelector('#pay').onclick = function () {
                            // 根据openid获取支付参数
                            $.get(URL + '/order/?openid=' + openid, function (response) {
                                wx.chooseWXPay({
                                    // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。
                                    // 但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
                                    "timestamp": response.timeStamp,
                                    "nonceStr": response.nonceStr,
                                    "package": response.package,
                                    "signType": response.signType,
                                    "paySign": response.paySign,
                                    success: function (res) {
                                        // 支付成功后的回调函数
                                        alert(JSON.stringify(res));
                                    }
                                });

                                // 支付的安全域名配置错误 choosewxpay:fail
                                // 出现此错误以后,用IOS手机调试,这时候会告诉你具体错误
                            })
                        };

                    });
                    // 微信JS-SDK加载失败
                    wx.error(function (res) {
                        alert(JSON.stringify(res));
                        // config信息验证失败会执行error函数,
                        // 如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,
                        // 也可以在返回的res参数中查看,对于SPA可以在这里更新签名。  

                    });
                }
            })
        }
    </script>
</head>

<body>
    <button id="scan">扫一扫</button>
    <button id="pay">支付</button>
</body>

</html>

接下来是服务端的代码。

代码语言:javascript
复制
// 安装wechat-api
// cnpm install wechat-api --save

// 引入封装的全局配置信息
var Config = require('./module/config');

// 引入封装支付模块
var wechatPay = require('./module/wechatPay');

// 引入wechat-api
var API = require('wechat-api');


var express = require('express');
var bodyParser = require('body-parser');
// 引入express-xml-bodyparser让node.js可以接收xml格式请求
var xmlparser = require('express-xml-bodyparser');

// 实例化配置
var api = new API(Config.wxappid, Config.wxappsecret)
var app = new express();

app.use(xmlparser());
app.use(express.static('./public'));
// 使用中间件body-parser获取post参数  
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.set('view engine', 'ejs');


// 生成config的参数
app.post('/wechat', function (req, res, next) {
    var param = {
        debug: false,
        jsApiList: [
            'checkJsApi',
            'onMenuShareTimeline',
            'onMenuShareAppMessage',
            'onMenuShareQQ',
            'onMenuShareWeibo',
            'onMenuShareQZone',
            'hideMenuItems',
            'showMenuItems',
            'hideAllNonBaseMenuItem',
            'showAllNonBaseMenuItem',
            'translateVoice',
            'startRecord',
            'stopRecord',
            'onVoiceRecordEnd',
            'playVoice',
            'onVoicePlayEnd',
            'pauseVoice',
            'stopVoice',
            'uploadVoice',
            'downloadVoice',
            'chooseImage',
            'previewImage',
            'uploadImage',
            'downloadImage',
            'getNetworkType',
            'openLocation',
            'getLocation',
            'hideOptionMenu',
            'showOptionMenu',
            'closeWindow',
            'scanQRCode',
            'chooseWXPay',
            'openProductSpecificView',
            'addCard',
            'chooseCard',
            'openCard'
        ],
        url: req.body.url
    };
    api.getJsConfig(param, function (err, result) {
        res.send(result);
    });

});

// 获取首页路由
app.get('/', function (req, res) {
    res.render('index');
});


// 获取openid返回客户端
app.get('/getOpenId', function (req, res) {
    var code = req.query.code;
    var pay = new wechatPay();
    pay.getAccessToken(code, function (err, data) {
        res.json(data);
    })
});

// 生成订单
app.get('/order', function (req, res) {
    var openid = req.query.openid;
    var pay = new wechatPay();
    pay.createOrder({
        openid: openid,
        // 微信支付完成后的回调
        notify_url: Config.url + '/notifyUrl',
        // 订单号
        out_trade_no: new Date().getTime(),
        attach: '名称',
        body: '购买信息',
        // 此处的额度为分
        total_fee: '1',
        // 处理node.js的异常情况
        spbill_create_ip: req.connection.remoteAddress.replace(/::ffff:/, ''),
    }, function (error, responseData) {
        if (error) {
            console.log(error);
        };
        res.json(responseData);
    });

})

// 获取微信支付的签名
app.post('/notifyUrl', function (req, res) {
    var pay = new wechatPay();
    var notifyObj = req.body.xml;
    var signObj = {};
    for (var attr in notifyObj) {
        if (attr != 'sign') {
            signObj[attr] = notifyObj[attr][0]
        }
    };
    var obj = pay.getSign(signObj);
    res.json(obj);

})

app.listen(8002);  

在上面的代码中我,我引入了两个封装的模块,一个是全局配置文件,另一个是支付一系列的API模块。

以下是引入config.js代码。

代码语言:javascript
复制
var config={
    url:'http://www.xiaoduanfa.com',
    mch_id: '1502539541',
    wxappid: "wx7bf3787c783116e4",
    wxappsecret:'cca407db4d8459c907d2c59274ca6d17',
    wxpaykey: 'zhongyuantengitying6666666666666'
};

module.exports=config;

以下是封装的微信支付相关的API,大家可以复制粘贴拿去复用,如有问题,后果自负,哈哈!

代码语言:javascript
复制
var queryString = require('querystring');
var crypto = require('crypto');
var request = require('request');
var xml2jsparseString = require('xml2js').parseString;
// 引入项目的配置信息
var config = require('./config.js');

// wechat 支付类 
class WechatPay {
    constructor() {

    };

    // 获取微信统一下单参数
    getUnifiedorderXmlParams(obj) {
        var body = '<xml> ' +
            '<appid>' + config.wxappid + '</appid> ' +
            '<attach>' + obj.attach + '</attach> ' +
            '<body>' + obj.body + '</body> ' +
            '<mch_id>' + config.mch_id + '</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;
    };

    // 获取微信统一下单的接口数据
    getPrepayId(obj) {
        var that = this;
        // 生成统一下单接口参数
        var UnifiedorderParams = {
            appid: config.wxappid,
            attach: obj.attach,
            body: obj.body,
            mch_id: config.mch_id,
            nonce_str: this.createNonceStr(),
            // 微信付款后的回调地址
            notify_url: obj.notify_url,
            openid: obj.openid,
            out_trade_no: obj.out_trade_no,
            spbill_create_ip: obj.spbill_create_ip,
            total_fee: obj.total_fee,
            trade_type: 'JSAPI',
            // sign : getSign(),
        };

        // 返回 promise 对象
        return new Promise(function (resolve, reject) {
            // 获取 sign 参数
            UnifiedorderParams.sign = that.getSign(UnifiedorderParams);
            var url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
            request.post({ url: url, body: JSON.stringify(that.getUnifiedorderXmlParams(UnifiedorderParams)) }, function (error, response, body) {
                var prepay_id = '';
                if (!error && response.statusCode == 200) {
                    // 微信返回的数据为 xml 格式, 需要装换为 json 数据, 便于使用
                    xml2jsparseString(body, { async: true }, function (error, result) {
                        prepay_id = result.xml.prepay_id[0];
                        // 放回数组的第一个元素
                        resolve(prepay_id);
                    });
                } else {
                    reject(body);
                }

            });
        })
    };


    // 获取微信支付的签名
    getSign(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=' + config.wxpaykey;
        // 生成签名
        return crypto.createHash('md5').update(queryString.unescape(string), 'utf8').digest("hex").toUpperCase();
    };


    // 获取微信支付的所有参数
    getBrandWCPayParams(obj, callback) {
        var that = this;
        var prepay_id_promise = that.getPrepayId(obj);
        prepay_id_promise.then(function (prepay_id) {
            var prepay_id = prepay_id;
            var wcPayParams = {
                // 公众号名称,由商户传入
                "appId": config.wxappid,
                // 时间戳,自1970年以来的秒数 
                "timeStamp": parseInt(new Date().getTime() / 1000).toString(),
                // 随机串
                "nonceStr": that.createNonceStr(),
                // 通过统一下单接口获取
                "package": "prepay_id=" + prepay_id,
                // 微信签名方式
                "signType": "MD5",
            };

            wcPayParams.paySign = that.getSign(wcPayParams); //微信支付签名

            callback(null, wcPayParams);
        }, function (error) {
            callback(error);
        });
    };

    // 获取随机的NonceStr
    createNonceStr() {
        return Math.random().toString(36).substr(2, 15);
    };

    // 获取微信的 accessToken   openid
    getAccessToken(code, cb) {
        var getAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + config.wxappid + "&secret=" + config.wxappsecret + "&code=" + code + "&grant_type=authorization_code";
        request.post({ url: getAccessTokenUrl }, function (error, response, body) {
            if (!error && response.statusCode == 200) {
                if (40029 == body.errcode) {

                    cb(error, body);

                } else {
                    body = JSON.parse(body);
                    cb(null, body);
                }
            } else {
                cb(error);
            }
        });
    };

    // 创建订单
    createOrder(obj, cb) {
        this.getBrandWCPayParams(obj, function (error, responseData) {
            if (error) {
                cb(error);
            } else {
                cb(null, responseData);
            }
        });
    }
}

module.exports = WechatPay;
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/11/10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档