VUE+node 实现微信支付功能

初始微信公众号

有个机会去做了一个微信公众号的项目,功能很简单就是支付商城。是需要从微信服务号中跳转web页面,本人第一次接触所以上手后各种问题!

首先公众号申请就浪费了好长时间,这里建议提前一个月申请,各种认证,唯一要注意一点是!!!!现在的微信支付都是在商户号里注册和管理的,不是只需要注册微信支付就可以了。

其次、就是公众号中自己的服务器配置和服务器与微信端认证。这里认证需要将他指定的文件放到服务器中就可以了。微信文档中有详解。

搭建环境

vue环境

除了基本配置环境以外还需引入weixin-js-sdk 

直接npm install weixin-js-sdk

node环境

express框架 、wechat(token认证时用的)、node-scheduel(计时器作用)、request、ejs(模板引擎)、q(解决回掉地域问题)、crypto(加密解密)

前期准备

废话不多说!先说思想偷笑!首先、点击导航按钮进入网站,进入后需要得到用户的信息和唯一的id也就是openid。然后用户发起支付请求,也就是吊起微信支付。完成支付和取消支付。前端就这些内容!

上代码!!!!

按钮的链接为:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxea9b4122f726a558&redirect_uri=你的网站域名?@&response_type=code&scope=snsapi_userinfo&state=login#wechat_redirect

跳转之后域名后的code=******就是请求openid的code值

将code传到服务器端

//前端请求接口getOpenid node获取openid
router.post('/getOpenid', (req, res) => {
	var params = req.body; 
	var url="https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
		url = url.replace("APPID",APP_ID);//你的app_id
		url = url.replace("SECRET",APP_SECRET);//你的aoo_secret
		url = url.replace("CODE",params.code);//传上来的code
	request(url, function (error, response, body) {
    if (!error && response.statusCode == 200) {
        console.log("openid",body);
        jsonWrite(res, body);
    }
	});
});

code请求成功后会返回openid、access_token。然后将openid传回去后获取微信用户信息。

//获取userinfo
router.post('/userinfo', (req, res) => {
		var params = req.body;
        var sql1 = $sql.wxuser.MakeSureUser;//先判断自己的数据库中是不是存在用户信息 如果存在就直接从数据库中获取避免多次请求
        console.log("请求接口","userinfo");
        console.log("sql",sql1);
         conn.query(sql1,[
         	params.openid
         ], function(err, result) {
        	if (err) {       
	            console.log(err);
	        }else if (result) {
	        console.log("数据库中存在用户信息",params.openid,result);
	           if (result!="") {
	           	jsonWrite(res, result);
	           	return;
	           }else{
	var content = req.body;
	var access_token =Trim(main.access_token,':g');//这里是全局的access_token服务器端的access_token 不是前端的access_token 
        //这里access_token太长传过来后容易存在换行 所以要去掉空格
	var url="https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
	url = url.replace("ACCESS_TOKEN",access_token);
	url = url.replace("OPENID",content.openid);
	request(url, function (error, response, body) {
    if (!error && response.statusCode == 200) {
        		console.log("userinfo",body);
        		body = JSON.parse(body);
	           	var sql = $sql.wxuser.userinfo;    
			    console.log("接口名称将用户信息存入数据库中:/userinfo","sql",sql);
			    console.log("请求参数:",params);
			    conn.query(sql,[
			    params.openid,
			    body.nickname,
			    body.sex,
			    body.city,
			    body.province,
			    body.country,
			    body.headimgurl,
			    body.remark,//运营者对用户的备注
			    body.groupid,
			    body.tagid_list+"",
			    body.subscribe_scene,//返回用户关注的渠道来源,ADD_SCENE_SEARCH 公众号搜索,ADD_SCENE_ACCOUNT_MIGRATION 公众号迁移,ADD_SCENE_PROFILE_CARD 名片分享,ADD_SCENE_QR_CODE 扫描二维码,ADD_SCENEPROFILE LINK 图文页内名称点击,ADD_SCENE_PROFILE_ITEM 图文页右上角菜单,ADD_SCENE_PAID 支付后关注,ADD_SCENE_OTHERS 其他
			    ], function(err, result) {
			        if (err) {       
			            console.log(err);
			        }else if (result) {
			        	var  str ={'0':body};
			        	str = JSON.stringify(str);
			        	jsonWrite(res, str);
			            console.log("请求结果 Home!",result)
			        }
			    })
	        }
	    })
    }}
	});
	
});
function Trim(str,is_global)
  {
   var result;
   result = str.replace(/(^\s+)|(\s+$)/g,"");
   if(is_global.toLowerCase()=="g")
   {
    result = result.replace(/\s/g,"");
    }
   return result;
}

这里最重要的是access_token的获取,access_token需要7200ms后刷新。这里就需要用到计时器(node-scheduel)。

function getToken(){
	request.get({
	  uri: 'https://api.weixin.qq.com/cgi-bin/token',
	  json: true,
	  qs: {
	   grant_type: 'client_credential',
	   appid: APPID, // APPID请换成你的 appid
	   secret: APPSECRET // APPSECRET请换成你的 appsecret
	  }
	 }, (err, res, body) => {
	  if (err) {
	   console.log(err)
	   return
	  }
	  console.log(body)
	  if (body.errcode) {
	   // 返回错误时的处理
	   console.log("请求token错误!");
	   return
	  }else{
	  	var token = body.access_token;
	  	console.log("请求token成功",token);
	  	access_token=token;
		module.exports.access_token = access_token;
		var rule = new schedule.RecurrenceRule();//建立计时器
		var times = [];
		  for(var i=1; i<60; i++){
		    times.push(i);
		  }
		rule.second = times;//每一秒都在进行判断 schedule没有7200ms所以我想到了没秒都执行用reqtime来起到计时的作用
		var reqtime = 0;//请求时间
		var j = schedule.scheduleJob(rule, function(){
			reqtime =reqtime+1;
			if(reqtime > 7199){//到了7200秒后 重新申请access_token
				reqtime = 0;
				getToken();
			}
		});
		j;
	  }
	})
}

前期准备就这些。最主要的就是openid的获取和access_token的获取。

开始支付

支付的pay.vue文件

引入wxpay.vue

import wexinpay from './wxpay'
postData:function(data) {
  	  	var params = {
  	  	openId: data.openid,
  	  	ordersall: data.ordersall,
	        orderid: data.orderid,
        	content: "你买了导弹!"
    	};
        var vm = this;
        this.$http.post("/api/payReseredFee",params,{}).then(function (result) {
          	console.log("支付返回",result,wexinpay);
            wexinpay.wexinPay(result.body,vm.postOrderlist,vm.closeOrderlist);
        })
     }

wxpay.vue

<script>
import wx from 'weixin-js-sdk'
function wexinPay(data,cb,errorCb){
  var appId = data.appId;
  var timestamp = data.timeStamp;
  var nonceStr = data.nonceStr;
  var signature = data.signType;
  var packages = data.package;
  var paySign = data.paySign;
  console.log("微信支付参数",appId,timestamp,nonceStr,signature,packages,paySign);
  wx.config({ //微信的相应配置
    debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
    appId: appId, // 必填,公众号的唯一标识
    timestamp: timestamp, // 必填,生成签名的时间戳
    nonceStr: nonceStr, // 必填,生成签名的随机串
    signature: signature, // 必填,签名,见附录1
    jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
  });
  wx.ready(function(){
    wx.chooseWXPay({
      timestamp: timestamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
      nonceStr: nonceStr, // 支付签名随机串,不长于 32 位
      package: "prepay_id="+packages, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=*** 这里注意一下
      signType: 'MD5', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
      paySign: paySign, // 支付签名
      success: function(res) {
        // 支付成功后的回调函数
        cb();
      },
      fail:function(res){
        errorCb();
      }
    });
  });
  wx.error(function(res) {
    // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
    /*alert("config信息验证失败");*/
  });
}

export default{
	wexinPay
} 
</script>

服务器端接受数据

api.js

//微信支付接口
router.post('/payReseredFee', function(req, res, next){ 
  var attach = "123"; 
  var body = "text"; 
  var mch_id = "150096****"; //商户ID 
  var openid = req.body.openId; 
  var bookingNo = req.body.orderid; //订单号 
  var total_fee = req.body.ordersall*100; //以分为单位
  console.log("请求体",req.body);
  var notify_url = "http://*****.cn/notify";//通知地址
  var spbill_create_ip = getClientIp(req);//这里获取用户ip
  if(spbill_create_ip.split(",").length>1){
  	spbill_create_ip= spbill_create_ip.split(",")[0];
  }
  console.log("支付请求",attach, body, mch_id, openid, bookingNo, total_fee, notify_url,spbill_create_ip);
  wxpay.order(attach, body, mch_id, openid, bookingNo, total_fee, notify_url,spbill_create_ip).then(function(data){ 
    console.log("支付返回",data);
    jsonWrite(res, data);
  }); 
}); 
//获取用户ip
function getClientIp(req) {
        return req.headers['x-forwarded-for'] ||
        req.connection.remoteAddress ||
        req.socket.remoteAddress ||
        req.connection.socket.remoteAddress;
};

 

 

服务器端处理数据,需要传输相应的编码和拼接

 

 

wxpay.js

var Q = require("q"); 
var request = require("request"); 
var crypto = require('crypto'); 
var ejs = require('ejs'); 
var fs = require('fs'); 
var key = "6jDfpmTSJm67maNZRKezGRFfH6******"; //商户号中预留的key
var messageTpl = fs.readFileSync(__dirname + '/message.ejs', 'utf-8'); 
  
var WxPay = { 
  getXMLNodeValue: function(node_name, xml) { 
    var tmp = xml.split("<" + node_name + ">"); 
    if(tmp.length <2){
    	return "";
    }else{
    	var _tmp = tmp[1].split("</" + node_name + ">"); 
    	return _tmp[0]; 
    }
  }, 
  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; 
  }, 
  
  paysignjs: function(appid, nonceStr, package, signType, timeStamp) { 
    var ret = { 
      appId: appid, 
      nonceStr: nonceStr, 
      package: package, 
      signType: signType, 
      timeStamp: timeStamp 
    }; 
    var string = this.raw(ret); 
    string = string + '&key=' + key; 
    var sign = crypto.createHash('md5').update(string, 'utf8').digest('hex'); 
    return sign.toUpperCase(); 
  }, 
  
  paysignjsapi: function(appid, attach, body, mch_id, nonce_str, notify_url, openid, out_trade_no, spbill_create_ip, total_fee, trade_type) { 
    var ret = { 
      appid: appid, 
      attach: attach, 
      body: body, 
      mch_id: mch_id, 
      nonce_str: nonce_str, 
      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);
    console.log("#1.生成字符串",string);
    string = string + '&key=' + key; //key为在微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 
    console.log("#2.连接商户key:",string);
    var crypto = require('crypto');
    var sign = crypto.createHash('md5').update(string, 'utf8').digest('hex'); 
    console.log("#3.md5编码并转成大写",sign.toUpperCase());
    return sign.toUpperCase();
  }, 
  
  // 随机字符串产生函数 
  createNonceStr: function() { 
    return Math.random().toString(36).substr(2, 15); 
  }, 
  
  // 时间戳产生函数 
  createTimeStamp: function() { 
    return parseInt(new Date().getTime() / 1000) + ''; 
  },
  order: function(attach, body, mch_id, openid, bookingNo, total_fee, notify_url,spbill_create_ip) { 
    var deferred = Q.defer(); 
    var appid = 'wxea9b4122f7****'; 
    var nonce_str = this.createNonceStr(); 
    var timeStamp = this.createTimeStamp(); 
    var url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; 
    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>" + bookingNo + "</out_trade_no>"; 
    formData += "<spbill_create_ip>"+spbill_create_ip+"</spbill_create_ip>"; 
    formData += "<total_fee>" + total_fee + "</total_fee>"; 
    formData += "<trade_type>JSAPI</trade_type>"; 
    formData += "<sign>" + this.paysignjsapi(appid, attach, body, mch_id, nonce_str, notify_url, openid, bookingNo,spbill_create_ip , total_fee, 'JSAPI') + "</sign>"; 
    formData += "</xml>"; 
    var self = this; 
    console.log("签名xml",formData);
    request({ 
      url: url, 
      method: 'POST', 
      body: formData 
    }, function(err, response, body) {
    	console.log("请求支付返回结果",body);
      if (!err && response.statusCode == 200) { 
        console.log(body); 
        var prepay_id = self.getXMLNodeValue('prepay_id', body.toString("utf-8")); 
        if(prepay_id === ""){return;}
        var tmp = prepay_id.split('[');
        var tmp1 = tmp[2].split(']'); 
        //签名 
        var _paySignjs = self.paysignjs(appid, nonce_str, 'prepay_id=' + tmp1[0], 'MD5', timeStamp); 
        var args = { 
          appId: appid, 
          timeStamp: timeStamp, 
          nonceStr: nonce_str, 
          signType: "MD5", 
          package: tmp1[0], 
          paySign: _paySignjs 
        }; 
        deferred.resolve(args); 
      } else { 
        console.log(body); 
      } 
    }); 
    return deferred.promise; 
  }, 
  
  //支付回调通知 
  notify: function(obj) { 
    var output = ""; 
    if (obj.return_code == "SUCCESS") { 
      var reply = { 
        return_code: "SUCCESS", 
        return_msg: "OK"
      }; 
  
    } else { 
      var reply = { 
        return_code: "FAIL", 
        return_msg: "FAIL"
      }; 
    } 
  
    output = ejs.render(messageTpl, reply); 
    return output; 
  }, 
}; 
module.exports = WxPay; 

最后

记得在商户号中配置支付页面。可以开debug模式这样可以用手机测试了!大笑

 

 

 

 

 

 

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值