公司项目要用到微信支付的jsapi统一下单接口调用,由于官方缺乏nodejs版的代码范例,而网上查到的也是五花八门的代码,却没有一个能用,也没有一个能讲清楚的,害得我花了好大的劲,各方求证并积极联系微信支付的技术支持,试了N种方法、手段,终于调通了,现在无偿提供给大家参考:
1. 支付模块wxpay.js
var xmlreader = require("xmlreader");
var request = require('request');
var crypto = require('crypto');
var params {
appid: 'xxxxxxxxxx', //应用程序编号
mch_id: 'xxxxxxxxxx', //商户编号
pay_key: 'xxxxxxxxxx', //微信商户平台-->账户设置-->API安全-->密钥设置
server_ip: 'xxxxxxxxxx', //你的nodejs后端程序运行的服务器的公网IP
//由你的nodejs服务提供的公网(可外网访问)接口,微信服务在支付完成后, 回调该接口通知支付状态
notify_url: 'http://123.123.123.123/wxpay/notify', // 本数据仅为示例,实际根据需要填写
trade_type: "JSAPI", //统一下单服务类型,小程序对应"JSAPI", 如果是app应用,则为"APP"
unified_url: "https://api.mch.weixin.qq.com/pay/unifiedorder" //统一下单接口,固定值
}
/**
* 根据传入的json对象和格式要求,返回格式化后的结果
* @param {*} obj
* @param {*} format
*/
function raw(obj, format) {
var str = '', keys = Object.keys(obj).sort();
keys.forEach(function (k) {
str += format == 'str' ? ('&' + k + '=' + obj[k]) : format == 'xml' ? ('<' + k + '>' + obj[k] + '</' + k + '>') : '';
});
return format == 'str' ? str.substr(1) : format == 'xml' ? ('<xml>' + str + '</xml>') : '';
};
/**
* 根据传入的json对象和支付key,进行签名并返回结果
* @param {*} dt
* @param {*} key
*/
function paySign(dt, key) {
var str = raw(dt, 'str') + '&key=' + key;
return crypto.createHash('md5').update(str, 'utf8').digest('hex').toUpperCase();
};
//微信支付函数
exports.pay = function (req, res, callback) {
var dt = {// 注意, 这里的对象属性不能缺,名称也不能改,切记!
appid: params.appid,
attach: '测试商品', //支付说明,非固定值,自己根据实际情况写
body: req.body.type, //商品说明等内容
mch_id: params.mch_id,
nonce_str: Math.random().toString(36).substr(2, 15), //随机字符串
notify_url: params.notify_url,
openid: req.body.openID, //当前付款微信账号的唯一标识
out_trade_no: req.body.orderCode, //订单编号
spbill_create_ip: params.server_ip,
total_fee: req.body.amount, //订单金额,不建议从前端传进来
trade_type: params.trade_type
};
dt.sign = paySign(dt, params.pay_key); //生成下单签名
var formData = raw(dt, 'xml');
request({
url : params.unified_url,
method : 'POST',
body : formData
}, function (err, response, body){
if (!err && response.statusCode == 200){
xmlreader.read(body.toString("utf-8"), function (errors, response) {
if (errors) {
res.json({
code: 100,
message: errors
})
}else{
var rt = {// 注意, 这里的对象属性不能缺,名称也不能改,切记!
appid: response.xml.appid.text(),
noncestr: response.xml.nonce_str.text(),
package: 'prepay_id=' + response.xml.prepay_id.text(),
signType: 'MD5',
timeStamp: parseInt(new Date().getTime() / 1000, 10)
};
// 再次签名, 生成前端调起支付页面需要的参数对象
rt.timeStamp = rt.timeStamp + ''; //必须字符串,否则前端调不起支付页面
rt.sign = paySign(rt, params.pay_key);
callback(rt);
}
});
}else{
res.json({
code: 100,
message: err
})
}
});
}
今天往正式环境部署服务时,发生了一件无法理解的事情,就是统一下单参数out_trade_no赋值有变化了。。。。。真无语啊。。。。。
我们测试服务器和正式服务器,都是阿里云服务器,本质上没有任何区别,但时,同样的代码,在测试服务器上,这个参数,只需要传正常的的订单号就行了。但到了正式服务器上,这个参数,就必须用out_trade_no = trade_type + orderNo的方式传值,也就是说,必须带上商户应用类型。。。。否则,就会报【201商户订单号重复】的错误。。。。为什么?微信支付的技术人员,你们做了什么????
2. 小程序下单接口与回调接口:
const wxpay = require('../service/wxpay');
const xmlreader = require("xmlreader");
// 前端小程序调用的统一下单接口
Router.post('/wxpay', (req, res)=>{
wxpay.pay(req, res);
})
// 微信支付回调方法
Router.post('/wxpay_notify', (req, res) => {
req.setEncoding("utf8");
req.on("data", function (xml, call) {
xmlreader.read(xml, (err, result) => {
if (err || result.xml.return_code.text() != "SUCCESS") {
res.json({
"code": "ERROR",
"message": err || result.xml.return_msg.text()
})
}else {
// 提取回调数据
var dt = {
nonce_str: result.xml.nonce_str.text(),
openid: result.xml.openid.text(),
total_fee: result.xml.total_fee.text(),
transaction_id: result.xml.transaction_id.text(),
out_trade_no: result.xml.out_trade_no.text(),
time_end: formatTimeStr(result.xml.time_end.text())
}
// do something here ...
res.json({
"code": "SUCCESS",
"message": "成功"
})
}
});
});
req.on("end", function (db) {
db;
});
})