单页面应用接入微信填坑之二(微信支付Nodejs)

来源:互联网 发布:制作歌曲伴奏软件 编辑:程序博客网 时间:2024/06/06 00:25

先记录一下正常接入微信支付步骤

微信公众号配置:

1. 开通微信公众号
这里就没什么要讲的了
2.服务器配置
进入微信公众平台->开发->基本配置->服务器配置,之后填写服务器地址和令牌,并按照微信官方教程配置即可。下面是我自己的一段Node.js版本的简单服务器配置:
var http = require("http");var url = require("url");var crypto = require('crypto');var token = 'abc123'; //令牌function getQuery(name,str) {    var reg = new RegExp(name+'=([^&]*)');    var matches = reg.exec(str);    if (matches) {        return matches[1];    }    return '';}/***监听请求*/function onRequest(request,response){    var urlParams = url.parse(request.url);    var result = verify(urlParams.query);    response.writeHead(200,{"Content-type":"text/plain; charset=UTF-8"});    response.write(result);    response.end();}function verify(query) {    var signature = getQuery('signature',query);    var timestamp = getQuery('timestamp',query);    var nonce = getQuery('nonce',query);    var echostr = getQuery('echostr',query);    var arr = [token,timestamp,nonce];    arr.sort();    var tempStr = arr.join('');    var sha1 = crypto.createHash('sha1');    var resultCode = sha1.update(tempStr,'utf-8').digest('hex');    if (resultCode === signature) {        return echostr;    }    return 'nomatch';}http.createServer(onRequest).listen(80);
3.js域名设置
进入微信公众平台->公众号设置->功能配置,之后填写业务域名、js接口安全域名和网页授权域名,这三个域名的作用设置时看官方解释即可。
4.获取开发者密码与AppId
进入微信公众平台->基本配置,设置或获取开发者密码与AppId,在获取用户user_info中将用到。

微信支付商户平台配置:

公众平台微信支付公众号支付授权目录、扫码支付回调URL配置入口已于8月1日迁移至商户平台(pay.weixin.qq.com)

1.支付授权目录配置
微信支付商户平台->产品中心->开发配置->支付配置->添加授权目录,在微信中调用支付时必须在添加的目录中,否则将无法支付。
2.设置API密钥
微信支付商户平台->账户中心->API安全->设置API密钥,将密钥记下(创建统一支付订单时需要)

开发配置:

1.通过微信授权获取用户openid

第一步:用户同意授权,获取code
访问下面的链接:

'https://open.weixin.qq.com/connect/oauth2/authorize?appid='+微信公众号AppId+'&redirect_uri='+跳转地址+'wxoauth&response_type=code&scope=snsapi_userinfo#wechat_redirect'

还记得我们在上面微信公众号配置->js域名设置,填写的网页授权域名吗?当我们使用上面的链接获取到code之后微信就会在URL query中携带着code跳转到网页授权域名。

第二步:通过code换取网页授权access_token
通过GET方式获取到ACCESS_TOKEN:

getWXToken: function(code) {   let reqUrl = 'https://api.weixin.qq.com/sns/oauth2/access_token?';    let params = {        appid: wxConfig.appid,//微信公众号AppId        secret: wxConfig.appSecret,//微信公众号开发者密码        code: code,//上一步获取到的code        grant_type: 'authorization_code'    };    let options = {        method: 'get',        url: reqUrl + CommonUtil.json2RequestParam(params)    };    return new Promise((resolve, reject) => {        request(options, function(err, res, body) {            if (res) {                resolve(body);            } else {                reject(err);            }        })    });},

在上面的代码中用到了request库,access_token和用户openid就存在于body中.如果不需要获取用户信息,那么到这一步就可以了。

第三步:拉取用户信息(需scope为 snsapi_userinfo)

getWXUserInfo: function(AccessToken, openId) {    let reqUrl = 'https://api.weixin.qq.com/sns/userinfo?';    let params = {        access_token: AccessToken,//上一步获取到的AccessToken        openid: openId,//上一步获取到的用户openId        lang: 'zh_CN'    };    let options = {        method: 'get',        url: reqUrl + CommonUtil.json2RequestParam(params)    };    return new Promise((resolve, reject) => {        request(options, function(err, res, body) {            if (res) {                resolve(body);            } else {                reject(err);            }        });    })},

同样的,获取到的用户信息(头像、昵称等)就存在于body中。
上述代码可查看https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842 微信官方文档

2.创建微信统一订单
//微信支付相关var fs = require('fs');var path = require('path');var wxConfig = require('../config/weixin');var util = require('./weixinUtil');var request = require('request');var md5 = require('MD5');function WXPay() {    if (!(this instanceof WXPay)) {        return new WXPay(arguments[0]);    };    this.options = arguments[0];    this.wxpayID = { appid: wxConfig.appid, mch_id: wxConfig.mch_id };};WXPay.mix = function() {    switch (arguments.length) {        case 1:            var obj = arguments[0];            for (var key in obj) {                if (WXPay.prototype.hasOwnProperty(key)) {                    throw new Error('Prototype method exist. method: ' + key);                }                WXPay.prototype[key] = obj[key];            }            break;        case 2:            var key = arguments[0].toString(),                fn = arguments[1];            if (WXPay.prototype.hasOwnProperty(key)) {                throw new Error('Prototype method exist. method: ' + key);            }            WXPay.prototype[key] = fn;            break;    }};WXPay.mix('option', function(option) {    for (var k in option) {        this.options[k] = option[k];    }});WXPay.mix('sign', function(param) {    var str = '';    var arr = [];    for (var name in param) {        if (param[name] != null && param[name] != '') {            arr.push(name + '=' + param[name]);        }    }    arr.sort();    str = arr.join('&');    str = str + '&key=' + wxConfig.mch_key;    return md5(str).toUpperCase();});WXPay.mix('createWCPayOrder', function(order) {    order.spbill_create_ip = order.spbill_create_ip.match(/\d+.\d+.\d+.\d+/)[0];//请求Ip    order.trade_type = "JSAPI";    order.nonce_str = order.nonce_str || util.generateNonceString();//随机字符串    util.mix(order, this.wxpayID);//加入公众号AppId和微信支付商户Id    order.sign = this.sign(order);    var self = this;    return new Promise(function(resolve, reject) {        self.requestUnifiedOrder(order, function(err, data) {            if (err) {                reject(err);            } else {                if (data.return_code == 'SUCCESS' && data.result_code == 'SUCCESS') {                    var resParam = {                        "appId": data.appid, //公众号名称,由商户传入                             "timeStamp": Math.floor(Date.now() / 1000) + "", //时间戳,自1970年以来的秒数                             "nonceStr": data.nonce_str, //随机串                             "package": "prepay_id=" + data.prepay_id,                        "signType": "MD5" //微信签名方式:                         };                    resParam.paySign = self.sign(resParam);                    resolve(resParam);                } else {                    reject(data);                }            }        }, function(err) {            reject(err);        });    });});WXPay.mix('requestUnifiedOrder', function(order, fn, errFn) {    request({        url: "https://api.mch.weixin.qq.com/pay/unifiedorder",        method: 'POST',        body: util.buildXML(order)    }, function(err, response, body) {        if (err) {            errFn();        } else {            console.log('body:' + body);            util.parseXML(body, fn);        }    });});exports = module.exports = WXPay;

以上代码是一个封装好的创建微信统一支付的类,使用方法如下:

var wxpay = new WeixinPay();var wxOrder = await wxpay.createWCPayOrder({    openid: ctx.session.openid,//用户openid    body: '购买商品',    detail: '购买商品',    out_trade_no: orderDetail.code, //平台内部订单号    total_fee: parseInt(orderDetail.totalPrice*100,10),//总价格    spbill_create_ip: ctx.ip,//ip    notify_url: 'http://baebae.cn/api/order/paynotify'//支付回调地址});

接下来将wxOrder返回给前端即可。

3.前端调用微信支付
wxPay: function(order, fn) {    if (typeof window.WeixinJSBridge === 'undefined') {        return;    }    window.WeixinJSBridge.invoke(        'getBrandWCPayRequest',        order,        function(res) {            if (res.err_msg == "get_brand_wcpay_request:ok") {                fn(true);            } else {                fn(false);            }        }    );},

通过将上一步wxOrder传入上面的方法即可唤起微信支付,当用户支付后微信将回调上一步创建订单时传入的回调地址。

4.回调处理
payNotify: async function(ctx) {    var body = ctx.request.body;    if (body.return_code == 'SUCCESS' && body.result_code == 'SUCCESS') {        // 支付成功处理    }    var message = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';    ctx.body = message;},

这个方法便是回调处理方法,当请求中的return_code和result_code皆为SUCCESS表示支付成功,之后还应返回一段状态XML(即上述代码中的message)给微信表示已获取到微信提示,否则微信将以某种策略一直请求回调地址。微信官方文档: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

遇到的坑

我有两个支付页面,当我按照上面的步骤做了之后发现在我的手机中(IOS)支付没有问题,但是当在安卓手机中却无法正常支付。后来弄了很久,发现在上述:微信支付商户平台配置->支付授权目录时并没有写完全(只写了一个目录)。