(一)微信公众号和微信商户平台配置
根据微信公众平台的使用教程配置
一、设置支付目录
请确保实际支付时的请求目录与后台配置的目录一致,否则将无法成功唤起微信支付。
在微信商户平台(pay.weixin.qq.com)设置您的公众号支付支付目录,设置路径:商户平台-->产品中心-->开发配置,如图7.7所示。公众号支付在请求支付的时候会校验请求来源是否有在商户平台做了配置,所以必须确保支付目录已经正确的被配置,否则将验证失败,请求支付不成功。
二、设置授权域名
开发公众号支付时,在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。具体界面如图7.8所示:
三、配置商户秘钥key
参考文章https://jingyan.baidu.com/article/75ab0bcbbf7034d6864db2c3.html
注意配置秘钥后要自己保存秘钥,商户平台上不能查看秘钥。后面的签名算法需要用到商户秘钥。
(二)统一下单
一、配置和参数
参考文档,需要注意的是:
1.微信支付结果通知的回调地址notify_url必须是80端口,如果不是80端口的话可以通过nginx反向代理
2.交易类型这里是公众号支付,填JSAPI
3.openid,公众号支付即trade_type=JSAPI时,此参数必传,此参数是前端微信登录后获取的openid
二、代码实现
1.nodejs做后台实现统一下单接口
h5_order: function(attach, body, mch_id, openid, out_trade_no, total_fee, notify_url,spbill_create_ip,scene_info) { var deferred = Q.defer(); var appid = "wx4280cd3b0ecf2a38";//"wx7df91d705b3f0a15"; // h5的appid var nonce_str = this.createNonceStr(); var trade_type = "JSAPI"; var url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; var spbill_create_ip = "115.192.87.205";//写死了这边 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>" + out_trade_no + "</out_trade_no>"; //必填 formData += "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>"; //IP地址需要动态获取 //必填 formData += "<total_fee>" + total_fee + "</total_fee>"; //必填 formData += "<trade_type>" + trade_type + "</trade_type>"; //必填 //formData += "<sign>" + this.getSign(appid, mch_id, "", body, nonce_str) + "</sign>"; // getSign: function(appid, mch_id, device_info, body, nonce_str) { //formData += "<scene_info>" + JSON.stringify(scene_info) + "</scene_info>"; formData += "<sign>" + this.signOne(appid, attach, body, mch_id, nonce_str, notify_url, openid, out_trade_no, spbill_create_ip, total_fee, "JSAPI") + "</sign>"; formData += "</xml>"; //console.log("formData:"+formData); var self = this; request({ url: url, method: 'POST', body: formData }, function(err, response, body) { if (!err && response.statusCode == 200) { console.log(body); var parser = new xml2js.Parser({ trim:true, explicitArray:false, explicitRoot:false });//解析签名结果xml转json parser.parseString(body, function(err, result){ var prepay_id = result['prepay_id']; var timeStamp = self.createTimeStamp(); var package="prepay_id="+prepay_id; var _paySignjs = self.signTwoH5(appid, nonce_str, package,timeStamp,"MD5"); var args = { appId: appid, package: package, timeStamp: timeStamp, nonceStr: nonce_str, paySign: _paySignjs }; //console.log("进行二次签名后数据", args); deferred.resolve(args); }); } else { console.log(body); } }); return deferred.promise; },
2.签名函数signOne:
signOne: function(appid, attach, body, mch_id, nonce_str, notify_url, openid, out_trade_no, spbill_create_ip, total_fee, trade_type) { //参数名ASCII码从小到大排序(字典序) var ret = { appid: appid,//ok //attach: attach, body: body,//ok mch_id: mch_id,//ok nonce_str: nonce_str,//ok 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安全-->**设置 console.log("signOne string:"+string); var crypto = require('crypto'); var sign = crypto.createHash('md5').update(string, 'utf8').digest('hex');//md5加密操作 return sign.toUpperCase();//转换成大写字母 },
//拼接 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; }
2.签名函数signTwoH5:
signTwoH5: function(appid, nonceStr, package,timestamp,signType) { var ret = { appId: appid, nonceStr: nonceStr, package: package, timeStamp: timestamp, signType:signType, }; var string = this.raw(ret); string = string + '&key=' + key; console.log("signTwo string: " + string); var sign = crypto.createHash('md5').update(string, 'utf8').digest('hex'); return sign.toUpperCase(); }
3.生成时间戳和随机字符串函数
// 随机字符串产生函数 createNonceStr: function() { return Math.random().toString(36).substr(2, 15); }, // 时间戳产生函数 createTimeStamp: function() { return parseInt(new Date().getTime() / 1000) + ''; },
4.服务端生成预支付订单号等信息后发送给前端,前端微信内置浏览器调起支付的代码如下: