nodejs实现小程序微信支付

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yemuxia_sinian/article/details/86672495

最近做小程序时用到了微信支付很是开心,因为之前支付一直都没有做过,终于又可以学点东西了。于是很开心的去看了下微信小程序的支付接口,没想到,事情基本都是后端做的,前端只要调用wx.requestPayment接口就可以了,于是决定自己用node来做接口测试一遍。废话不多说,先整理下支付的流程。

一、微信小程序支付(支付API传送门

首先借用下官方的图片:

å°ç¨åºæ¯ä»æ¶åºå¾

大概流程就是:前端请求后端支付接口,并把登录的code(因为是自己测试所以要把code传给后端,用来获取用户的openID)和支付的money传给后端;后端调用小程序的登录API获取用户的openID,然后调用统一下单API,把必要的参数传过去,就可以获取预付单信息(prepay_id),然后获取当前的时间搓(timeStamp),整合预付单信息(prepay_id,参数是packpage)、随机字符串(nonceStr)、支付签名(paySign),签名算法(signType,这个我没有返回,前端直接写死MD5了)一起返回给前端就可以。最后前端对后端返回的数据进行二次加密(传送门),然后调用wx.requestPayment接口,并把对应参数传给微信就可以发起支付了。

二、统一下单接口调用分解

调用预下单时,自己只传了一些必要的参数过去,其他非必填的都没有传(非必填这边有一个参数需要注意下:attach,附加参数,在查询API和支付通知中原样返回,可作为自定义参数使用。意思就是我可以在这边传一些自定义的参数,比如商品id,用户id,订单id等,这样在通知接口那边就可以知道是那个用户支付了哪个商品了,从而改变订单的状态)。参数的话,部分写死,其他的就需要自己获取。

(1)获取用户openID

function getOpenid(code,appid){	//发起请求获取用户的openID
	return new Promise(function(resolve,reject){	
		request('https://api.weixin.qq.com/sns/jscode2session?appid=wx33ff58965464254d&secret=39afb1e055fadb6e3906c2c26ed2e9a8&js_code='+code+'&grant_type=authorization_code',function(error,response,body){
			if(!error && response.statusCode == 200){
				var bodyJson = JSON.parse(body)
				//console.log(bodyJson,'获取openID返回信息')
				resolve(bodyJson.openid);
			}
		})
	})
}

直接调用微信的jscode2session接口就可以获取了,这里用到了promise(这个在处理异步问题时是真的好用),因为需要先获取到openID后,才能调用统一下单接口。这边遇到了个问题就是:一直报错40013,提示code无效,仔细排查后,原来是小程序的appid弄错了,写成了另外一个小程序的了。

(2)获取随机字符串(这个很简单,没什么好说的)

function randomStr(){	//产生一个32位随机字符串	
    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;
}

(3)签名算法(传送门

function createSign(obj){	//签名算法(把所有的非空的参数,按字典顺序组合起来+key,然后md5加密,再把加密结果都转成大写的即可)
	var stringA = 'appid='+obj.appid+'&body='+obj.body+'&mch_id='+obj.mch_id+'&nonce_str='+obj.nonce_str+'&notify_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;
	
	
	let stringSignTemp = stringA+'&key=自己的商户号秘钥';
		stringSignTemp = md5(stringSignTemp);
	let signValue = stringSignTemp.toUpperCase();
	return signValue
}

签名算法是比较麻烦的一步,整个步骤就是:把你所有要传的非空参数,按字典顺序拼接起来得到stringA,然后加上key, (key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置)得到stringSignTemp,然后对stringSignTemp进行md5加密,再把加密后的字符串都转换为大写即可。md5加密这边用到了md5-node模块,安装命令:cnpm install md5-node --save,然后引入模块即可使用。

(4)xml数据解析

请求统一下单接口时,所传的参数需拼接成xml数据,然后调用成功后,微信会返回一串xml数据给你,然后再把这串数据解析成json数据就可以了。xml数据解析这边用到了xml2js模块,安装与使用和上面的md5-node一样。解析代码如下:

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.end(JSON.stringify(responseData))

		        })

完整的js代码如下:

var express = require('express');
var app = express();

var request = require('request')	//引入request请求模块
var md5 = require('md5-node')	//引入md5加密模块
var xml2js = require('xml2js');	//引入xml解析模块

app.get('/',function(req,res){
	res.send('hello nodejs')
})

app.get('/pay',function(req,res){	//小程序微信支付
	let query = req.query;
	console.log(query,'获取请求参数')	
	let time = new Date().getTime();	//商户订单号
	let nonce_str = randomStr();
	let openid = null;
	let total_fee = Number(query.money)*100;
	let appid = '';	//自己的小程序appid
	let mch_id ='';	//自己的商户号id
	
	
	getOpenid(query.code,appid).then(function(res1){
		openid = res1;
		
		let sign = createSign({	//签名
			appid: appid,
			body: '微信支付,商品详细描述',
			mch_id: mch_id,
			nonce_str: nonce_str,
			notify_url: 'https://www.kdsou.com/kdchange/service_bak/notify.php',
			openid: openid,
			out_trade_no: time,
			spbill_create_ip: '127.0.0.1',
			total_fee: total_fee,
			trade_type: 'JSAPI'
		});
		
		let reqUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
		/*var reqData = {
			appid: '',	//小程序appid
			mch_id: '',	//商户号
			nonce_str: nonce_str,	//随机字符串
			sign: sign,	//签名
			body: '微信支付,商品详细描述',	//商品描述
			out_trade_no: time,	//商品订单号
			total_fee: total_fee,	//商品价格
			spbill_create_ip: '127.0.0.1',	//本地服务器地址
			notify_url: 'https://www.kdsou.com/kdchange/service_bak/notify.php',	//通知地址
			trade_type: 'JSAPI',	//交易类型,JSAPI为小程序交易类型
			openid: openid,	//交易类型是JSAPI的话,此参数必传
		}*/
		
		let formData = `<xml>
							<appid>${appid}</appid>
							<mch_id>${mch_id}</mch_id>
							<nonce_str>${nonce_str}</nonce_str>
							<sign>${sign}</sign>
							<body>微信支付,商品详细描述</body>
							<out_trade_no>${time}</out_trade_no>
							<total_fee>${total_fee}</total_fee>
							<spbill_create_ip>127.0.0.1</spbill_create_ip>
							<notify_url>https://www.kdsou.com/kdchange/service_bak/notify.php</notify_url>
							<trade_type>JSAPI</trade_type>
							<openid>${openid}</openid>
						</xml>`;
		
		//console.log(formData,'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,'统一下单接口返回的数据') // 请求成功的处理逻辑
		        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.end(JSON.stringify(responseData))

		        })
		    }
		}); 
		
	});
	
})

function getOpenid(code,appid){	//发起请求获取用户的openID
	return new Promise(function(resolve,reject){	
		request('https://api.weixin.qq.com/sns/jscode2session?appid='+appid+'&secret=39afb1e055fadb6e3906c2c26ed2e9a8&js_code='+code+'&grant_type=authorization_code',function(error,response,body){
			if(!error && response.statusCode == 200){
				var bodyJson = JSON.parse(body)
				//console.log(bodyJson,'获取openID返回信息')
				resolve(bodyJson.openid);
			}
		})
	})
}

function createSign(obj){	//签名算法(把所有的非空的参数,按字典顺序组合起来+key,然后md5加密,再把加密结果都转成大写的即可)
	var stringA = 'appid='+obj.appid+'&body='+obj.body+'&mch_id='+obj.mch_id+'&nonce_str='+obj.nonce_str+'&notify_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=自己的商户号key';
		stringSignTemp = md5(stringSignTemp);
	var signValue = stringSignTemp.toUpperCase();
	return signValue
}

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;
}

var server = app.listen(8099,function(){ 
	var host = server.address().address;
	var port = server.address().port;
	
	console.log('应用实例,访问地址为:tttp://%s:%s',host,port)
})


前端发起的请求的代码如下:

 pay(){
    console.log('发起微信支付')
    let money = 0.01;

    wx.login({
      success(res){
        if(res.code){
          wx.request({
            url: 'http://localhost:8099/pay?code='+res.code+'&money='+money,
            success(res){
              console.log(res,'统一下单接口返回信息')
              
              let appid = 'wx33ff58965464254d';
              let timeStamp = res.data.timeStamp;
              let nonceStr = res.data.nonceStr;
              let packages = 'prepay_id='+res.data.package;
              let signType = 'MD5';
              let key = '商户后台设置的key';

              let str = 'appId=' + appid + '&nonceStr=' + nonceStr + '&package=' + packages + '&signType=' + signType +'&timeStamp='+timeStamp+'&key='+key;
              let paySign = md5.md5(str)

              wx.requestPayment({
                timeStamp: timeStamp+'',  //时间搓
                nonceStr: nonceStr, //随机字符串
                package: packages,  //repay_id
                signType: 'MD5', //签名算法
                paySign: paySign,  //签名
                success(res){
                  console.log(res,'微信支付成功!!!')
                }
              })

            }
          })
        }
      }
    })

  }

本地编辑器调试效果如下:

 

 

展开阅读全文

没有更多推荐了,返回首页