基础准备
1、注册小程序https://mp.weixin.qq.com/
2、小程序需要交300大洋做认证,可以关联以后商户号(如果没有商户需注册开通)
代码
主体代码
var express = require("express");
var router = express.Router();
var db = require("../src/mysql");
var crypto = require('crypto');
var xml2js = require('xml2js');
var request = require('request');
var bodyparser = require('body-parser');
router.use(bodyparser.urlencoded({ extended: false }));
.....................
console.log(req.query, '获取请求参数');
var orderno = new Date().getTime(); //自定义的商户订单号
var nonce_str = randomStr(); //随机数
var total_fee = Number(req.query.amount) * 100;//订单价格
var reqUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
//签名
var signoption = {
appid: global.appid,//小程序appid
body: 'xxxx',//商品描述
mch_id: global.mch_id,//商户号
nonce_str: nonce_str,//随机字符串
notify_url: global.notify_url, //回调地址
openid: openid,//交易类型是JSAPI的话,此参数必传 可从过code获取openid
out_trade_no:orderno,//商品订单号
spbill_create_ip: '111.111.111.111',//因为微信支付需要有回调url,所以没法确定你的公网ip就没法发送订单支付通知给你,所以提供一个解析的正常ip就好
total_fee: total_fee,//商品价格
trade_type: 'JSAPI'//交易类型,JSAPI为小程序交易类型
};
var sign = createSign(signoption);
//这个顺序要和签名顺序一致
let formData = '<xml><appid>' + signoption.appid + '</appid><body>' + signoption.body + '</body><mch_id>' + signoption.mch_id + '</mch_id><nonce_str>' + signoption.nonce_str + '</nonce_str><notify_url>' + signoption.notify_url + '</notify_url><openid>' + signoption.openid + '</openid><out_trade_no>' + signoption.out_trade_no + '</out_trade_no><spbill_create_ip>' + signoption.spbill_create_ip + '</spbill_create_ip><total_fee>' + signoption.total_fee + '</total_fee><trade_type>' + signoption.trade_type + '</trade_type><sign>' + sign + '</sign></xml>';
//发起请求,获取微信支付的一些必要信息
request({
url: reqUrl,
method: "POST",
json: true,
headers: {
"content-type": "application/json"
},
body: formData
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
console.log(body, '统一下单接口返回的数据');
try {
xml2js.parseString(body, function (error, result) {
console.log(JSON.stringify(result), 'xml解析成字符串');
let reData = result.xml;
let responseData = {
timeStamp: new Date().getTime(),
nonceStr: reData.nonce_str[0],
package: reData.prepay_id[0],
paySign: reData.sign[0]
};
//更新记录支付结果
//................
//个性化业务处理
res.json({ error_code: 0, result: responseData });
});
} catch (e) {
console.log(e);
}
}
});
产生随机数
function randomStr() {
var str = "";
var arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
for (var i = 1; i <= 32; i++) {
var random = Math.floor(Math.random() * arr.length);
str += arr[random];
}
return str;
}
签名
//签名算法(把所有的非空的参数,按字典顺序组合起来+key,然后md5加密,再把加密结果都转成大写的即可)
function createSign(obj) {
var stringA = 'appid=' + obj.appid + '&body=' + obj.body + '&mch_id=' + obj.mch_id + '&nonce_str=' + obj.nonce_str + '¬ify_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=' + obj.trade_type;
var stringSignTemp = stringA + '&key=' + global.mch_key;
var hash = crypto.createHash('md5');
stringSignTemp = hash.update(stringSignTemp);
var signValue = hash.digest('hex');
return signValue.toUpperCase();
}
以下是遇到的坑
- spbill_create_ip一定要给一个公网地址,不然可能会报total_fee的问题
- 加密的顺序一定不要错,保持一致,不然会签名错误
- 参数顺序后最后要加上
key=xxxxx
商户的mch_key
小程序端
wx.request({
url: '接口地址?code=用户code&amount=0.01',
success(res) {
console.log(res, '统一下单接口返回信息')
if (res.data.error_code == 0) {
//成功
var _signtext = "appId=小程序appid&nonceStr=" + res.data.result.nonceStr + "&package=prepay_id=" + res.data.result.package + "&signType=MD5&timeStamp=" + res.data.result.timeStamp + "&key=商户mch_key";**//商户key一定要补,顺序不要随便调整**
var paysign = md5.hexMD5(_signtext);**//md5重新加密生成签名**
//开始调起支付
wx.requestPayment({
timeStamp: res.data.result.timeStamp + '', //统一下单返回
nonceStr: res.data.result.nonceStr, //统一下单返回
package: "prepay_id=" + res.data.result.package, **//一定要写prepay_id= 不然会报total_fee错误**
signType: 'MD5', //签名算法
paySign: paysign.toUpperCase(), **//不能用统一下单的签名,需重新签名!!!!**
success(res) {
console.log(res, '微信支付成功!!!')
//个性化业务处理
})
}
})
} else {
console.log(res, "支付失败!"+res.msg);
}
}
})
注意:小程序的官方文档一定要小心看,看少一点错误都看不懂
签名测试地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=20_1
小程序端签名文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5
小程序支付API:https://developers.weixin.qq.com/miniprogram/dev/api/wx.requestPayment.html
参考引用:https://blog.csdn.net/yemuxia_sinian/article/details/86672495