来讲讲微信支付吧,看到微信官方文档里好像没有node写的库和sdk,就去百度,然后看到有个哔哩哔哩视频教程。怕以后找不到了就写个博客记录一下,如有问题或者有侵权请及时联系。
//微信官方文档
https://developers.weixin.qq.com/miniprogram/dev/api/payment/wx.requestPayment.html//微信支付官方文档
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
这是后端的文件目录
如下图所示需要的参数这里一一填写:
1.将公众账号ID和商号以及商户秘钥写在一个js文件里导出
2.随机字符串
注意:随机字符串,不长于32位
let str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
let res = ''
for(let i=0;i<32;i++){
let num = Math.floor(Math.random()*str.length); //随机生成32位数字
res+=str[num] //拿取str里的下标生成32位随机字符串
};
3.商品描述
body:"test", //随便写一个
4.商户订单号
注意:商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一。为了防止重复我们获取时间这样就不会重复了。如2009年12月25日9点10分10秒表示为20091225091010。
let date = new Date();
let arr = [
date.getFullYear(),
(date.getMonth()+1),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds(),
Math.floor(Math.random()*100000)
];
arr.join('')
5.订单金额随便写了
注意:订单总金额,单位为分,只能为整数
6.获取终端IP
这里百度有挺多的我就随便拿了一个
var os = require('os');
let IPAddress = '';
var interfaces = os.networkInterfaces();
for (var devName in interfaces) {
var iface = interfaces[devName];
for (var i = 0; i < iface.length; i++) {
var alias = iface[i];
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
IPAddress = alias.address;
}
}
}
7.通知地址
注意:异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http。
我这里也是随便写了一个
8.交易类型
注意:JSAPI -JSAPI支付、NATIVE -Native支付。APP -APP支付。我这里用的JSAPI支付。
9.用户标识
注意:trade_type=JSAPI时(即JSAPI支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。这也是必传参数。
let config = {
appid:'11111', //微信小程序AppID
secret:'2222 ' //微信小程序AppSecret
}
//获取openid
router.get('/getopenid',(req,res)=>{
if(req.query.code){
request({
url:'https://api.weixin.qq.com/sns/jscode2session',
qs: {
appid: config.appid,
secret: config.secret,
js_code: req.query.code,
grant_type: 'authorization_code'
}
},
(err,response,body)=>{
if(err){
return res.json(err)
}
return res.json(JSON.parse(response.body))
})
}
})
最后整合一下
index.js文件
let express = require("express")
const app = new express();
let wx_router = require('./router/wx')
app.use('/wx',wx_router)
//设置监听端口
app.listen(3000);
console.log("[server] start listening at port 3000");
utils下的util.js
var os = require('os');
// 32位随机字符串
function getRnd32(){
let str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
let res = ''
for(let i=0;i<32;i++){
let num = Math.floor(Math.random()*str.length);
res+=str[num]
};
return res;
}
//时间戳
function getTimeStamp(){
return Math.round(new Date().getTime()/1000)
}
//商品订单号
function getTradeNo(){
let date = new Date();
let arr = [
date.getFullYear(),
(date.getMonth()+1),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds(),
Math.floor(Math.random()*100000)
];
return arr.join('')
}
// 获取ip
function getIPAddress() {
let IPAddress = '';
var interfaces = os.networkInterfaces();
for (var devName in interfaces) {
var iface = interfaces[devName];
for (var i = 0; i < iface.length; i++) {
var alias = iface[i];
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
IPAddress = alias.address;
}
}
}
return IPAddress;
};
module.exports = {
getRnd32,getTimeStamp,getTradeNo,getIPAddress,
}
config文件夹下的index.js
let WX_INFO = {
WX_APP_ID:"111", //公众号appid
WX_MCH_ID:'111', //商号
WX_SHOP_KEY:'111' //商户秘钥
}
module.exports = {
WX_INFO
}
router下的wx.js
let express = require('express')
let router = express.Router()
let request = require('request')
let WX_INFO = require('../config/index')
let util = require('../utils/util')
let config = {
appid:'1111', //微信小程序AppID
secret:'2222' //微信小程序AppSecret
}
//获取openid
router.get('/getopenid',(req,res)=>{
if(req.query.code){
request({
url:'https://api.weixin.qq.com/sns/jscode2session',
qs: {
appid: config.appid,
secret: config.secret,
js_code: req.query.code,
grant_type: 'authorization_code'
}
},
(err,response,body)=>{
if(err){
return res.json(err)
}
return res.json(JSON.parse(response.body))
})
}
})
//支付
router.get('/getPayid',(req,res)=>{
let openid = req.query.openid;
let payobj = {
appid:WX_INFO.WX_INFO.WX_APP_ID, //公众号appid
body:"test", //商品描述
mch_id:WX_INFO.WX_INFO.WX_MCH_ID, //商户号
nonce_str:util.getRnd32(), //自己生成的32位随机字符串
notify_url:"127.0.0.1:3000/wx/success", //支付结果通知地址
openid, //用户的openid
out_trade_no:util.getTradeNo(), //自己生成的商户订单号
spbill_create_ip:util.getIPAddress(), //终端ip
total_fee:1, //标价金额
trade_type:"JSAPI" //交易类型
}
})
module.exports = router;
最难的地方来了那就是签名
我们要对数据进行拼接加密以及转义具体看如下
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
我们需要循环router下的wx.js里支付下的payobj转换一下数据形式进行拼接。
我们先npm一下md5
npm i md5
//拼接加密
function getSign(payobj,wx_key){
let arr = [];
for(let key in payobj){
arr.push(key + "=" +payobj[key]) //转换数据的形式appid=wxd930ea5d5a258f4f
}
arr = arr.sort(function(a,b){
return a.localeCompare(b) //用本地特定排序规则对字符串数组进行排序:
})
//以上是开发文档里的stringA我们需要在最后拼接key微信商户平台
arr.push("key="+wx_key);
let str = arr.join("&") //最后转换字符串以&拼接
return md5(str) //md5加密
}
以上是对象形式我们需要转义
先npm一下fast-xml-parser
npm i fast-xml-parser --save
最后我们进行转义
let express = require('express')
let router = express.Router()
let request = require('request')
let WX_INFO = require('../config/index')
let util = require('../utils/util')
let Parser = require('fast-xml-parser')
var fs = require("fs")
let j2xParser = new Parser.j2xParser()
//支付
router.get('/getPayid',(req,res)=>{
let openid = req.query.openid;
let payobj = {
appid:WX_INFO.WX_INFO.WX_APP_ID, //公众号appid
body:"test", //商品描述
mch_id:WX_INFO.WX_INFO.WX_MCH_ID, //商户号
nonce_str:util.getRnd32(), //自己生成的32位随机字符串
notify_url:"127.0.0.1:3000/wx/success", //支付结果通知地址
openid, //用户的openid
out_trade_no:util.getTradeNo(), //自己生成的商户订单号
spbill_create_ip:util.getIPAddress(), //终端ip
total_fee:1, //标价金额
trade_type:"JSAPI" //交易类型
}
})
payobj.sign = util.getSign(payobj,WX_INFO.WX_INFO.WX_SHOP_KEY) //md5加密
let obj ={xml:payobj}
let sendXml = j2xParser.parse(obj) //XML转义
最后请求微信接口
request({
method:"POST",
url:"https://api.mch.weixin.qq.com/pay/unifiedorder", //下单接口链接
form:sendXml //转义的数据
},(err,response,data)=>{
if(err) return res.json(err)
if(Parser.validate(data)){ //判断是否是有效值
var resObj = Parser.parse(data) //是有效值就转换为json
}
console.log(resObj) //拿到返回值
let resJson = {
appId: resObj.xml.appid, //小程序ID
nonceStr: resObj.xml.nonce_str, //随机串是上面的支付里的字符串需要一致
package: 'prepay_id='+resObj.xml.prepay_id, //数据包
signType: 'MD5', //签名方式
timeStamp: util.getTimeStamp()+'' //时间戳字符串形式
}
resJson.paySign = util.getSign(resJson,WX_INFO.WX_INFO.WX_SHOP_KEY); //paySign加密返回小程序端
return res.json(resJson)