NodeJS服务端实现微信小程序支付

本文主要介绍用过nodejs实现实现微信小程序支付。支持post请求,安全性更高。

一、前期准备

1.开发前必读
2.申请微信支付-小程序支付

(流程略,若有需要可以留言,另立专题)
获取appid、appsecret、mchid、mchkey

3.支持https

NodeJS免费开通https接口访问

二、nodejs开发阶段

1.在根目录下的routes下新建utils.js和wxapi.js
2.utils.js 方法封装

主要是封装金额转换、随机字符串、时间戳、签名加密算法、签名加密二次算法

var xmlreader = require("xmlreader");

var wxpay = {

    //把金额转为分
    getmoney: function (money) {
        return parseFloat(money) * 100;
    },

    // 随机字符串产生函数  
    createNonceStr: function () {
        return Math.random().toString(36).substr(2, 15);
    },

    // 时间戳产生函数  
    createTimeStamp: function () {
        return parseInt(new Date().getTime() / 1000) + '';
    },

    //签名加密算法
    paysignjsapi: function (appid, body, mch_id, nonce_str, notify_url, openid, out_trade_no, spbill_create_ip, total_fee, trade_type, mchkey) {
        var ret = {
            appid: appid,
            mch_id: mch_id,
            nonce_str: nonce_str,
            body: body,
            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
        };
        console.log('ret==', ret);
        var string = raw(ret);
        var key = mchkey;
        string = string + '&key=' + key;
        console.log('string=', string);
        var crypto = require('crypto');
        return crypto.createHash('md5').update(string, 'utf8').digest('hex').toUpperCase();
    },
    // 小程序签名
    paysignjsapimini: function (appId, nonceStr, package, signType, timestamp, mchkey) {
        var ret = {
            appId: appId,
            nonceStr: nonceStr,
            package: package,
            signType: signType,
            timeStamp: timestamp,
        };
        console.log('Miniret==', ret);
        var string = raw(ret);
        var key = mchkey;
        string = string + '&key=' + key;
        console.log('Ministring>>>>>>', string);
        var crypto = require('crypto');
        return crypto.createHash('md5').update(string, 'utf8').digest('hex').toUpperCase();
    },
    getXMLNodeValue: function (xml) {
        xmlreader.read(xml, function (errors, response) {
            if (null !== errors) {
                console.log(errors)
                return;
            }
            console.log('长度===', response.xml.prepay_id.text().length);
            var prepay_id = response.xml.prepay_id.text();
            console.log('解析后的prepay_id==', prepay_id);
            return prepay_id;
        });
    }

}
function raw(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;
}

module.exports = wxpay;

3.wxapi.js 接口封装
var express = require('express');
var router = express.Router();
var request = require('request');
var xmlreader = require("xmlreader");
var wxpay = require('./util');

var appid = '******';  // 小程序的appid
var appsecret = '******';// 小程序的appSecret
var mchid = '******'; // 微信商户号
var mchkey = '******';  // 微信商户的key 32位
var wxurl = '******'; //通知地址

router.post('/wxpay', function (req, res) {
  //首先拿到前端传过来的参数
  let orderCode = req.body.orderCode;
  let money = req.body.money;
  let orderID = req.body.orderID;
  let openid = req.body.openid;

  console.log('APP传过来的参数是', orderCode + '----' + money + '------' + orderID + '----' + appid + '-----' + appsecret + '-----' + mchid + '-----' + mchkey);

  //首先生成签名sign
  // appid
  let mch_id = mchid;
  let nonce_str = wxpay.createNonceStr();
  let timestamp = wxpay.createTimeStamp();
  let body = '测试微信支付';
  let out_trade_no = orderCode;
  let total_fee = wxpay.getmoney(money);
  let spbill_create_ip = req.connection.remoteAddress; // 支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP
  let notify_url = wxurl;
  let trade_type = 'JSAPI';  // 'APP';公众号:'JSAPI'或'NATIVE'

  let sign = wxpay.paysignjsapi(appid, body, mch_id, nonce_str, notify_url, openid, out_trade_no, spbill_create_ip, total_fee, trade_type, mchkey);

  console.log('sign==', sign);

  //组装xml数据
  var formData = "<xml>";
  formData += "<appid>" + appid + "</appid>";  //appid
  formData += "<body><![CDATA[" + "测试微信支付" + "]]></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>" + out_trade_no + "</out_trade_no>";
  formData += "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>";
  formData += "<total_fee>" + total_fee + "</total_fee>";
  formData += "<trade_type>" + trade_type + "</trade_type>";
  formData += "<sign>" + sign + "</sign>";
  formData += "</xml>";

  console.log('formData===', formData);

  var url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';

  request({ url: url, method: 'POST', body: formData }, function (err, response, body) {
    if (!err && response.statusCode == 200) {
      console.log(body);

      xmlreader.read(body.toString("utf-8"), function (errors, response) {
        if (null !== errors) {
          console.log(errors)
          return;
        }
        console.log('长度===', response.xml.prepay_id.text().length);
        var prepay_id = response.xml.prepay_id.text();
        console.log('解析后的prepay_id==', prepay_id);


        //将预支付订单和其他信息一起签名后返回给前端
        let package = "prepay_id=" + prepay_id;
        let signType = "MD5";
        let minisign = wxpay.paysignjsapimini(appid, nonce_str, package, signType, timestamp, mchkey);
        res.end(JSON.stringify({ status: '200', data: { 'appId': appid, 'partnerId': mchid, 'prepayId': prepay_id, 'nonceStr': nonce_str, 'timeStamp': timestamp, 'package': 'Sign=WXPay','paySign': minisign } }));

      });
    }
  });

})

//微信获取sessicon
router.post('/jscode2session', function (req, res) {
  let APPID = appid;
  let SECRET = appsecret;
  let CODE = req.body.code;
  let _res = res;
  let url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' + APPID + '&secret=' + SECRET + '&js_code=' + CODE + '&grant_type=authorization_code'
  request({ url: url, method: 'GET' }, function (err, res, body) {
    body = JSON.parse(body)
    _res.json(body);
    // _res.end(JSON.stringify({ "openid": body.openid, "session_key": body.session_key }));
  })
})

module.exports = router;
4.在根目录下的app.js挂载使用
var wxapiRouter = require('./routes/wxapi');

*
*
*

app.use('/wxapi', wxapiRouter);

三、小程序调用接口

login.js

function json2Form(json) {
  var str = [];
  for (var p in json) {
    str.push(encodeURIComponent(p) + "=" + encodeURIComponent(json[p]));
  }
  return str.join("&");
}
Page({

  /**
   * 页面的初始数据
   */
  data: {},
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function(options) {
	this.login();
  },
  login: function() {
	    wx.login({
	      success: res => {
	        let that = this;
	        let data = {};
	        data.code = res.code;
	        let url = "https://******/wxapi/jscode2session";
	        wx.request({
	          method: 'POST',
	          url: url,
	          data: json2Form(data),
	          header: {
	            'Content-Type': 'application/x-www-form-urlencoded', // 默认值
	          },
	          success: res => {
	            console.log(res.data.openid)
	            this.wxapi(res.data.openid);
	          }
	        })
	      }
	    })
	  },
	  wxapi: function(openid) {
	    let that = this;
	    let data = {};
	    data.openid = openid;
	    data.orderCode = "20150909125346"; // 订单号
	    data.money = "0.01";
	    data.orderID = "21";  // 订单id
	    let url = "https://******/wxapi/wxpay"; 
	    wx.request({
	      method: 'POST',
	      url: url,
	      data: json2Form(data),
	      header: {
	        'Content-Type': 'application/x-www-form-urlencoded', // 默认值
	      },
	      success: function(res) {
	        let data = res.data.data;
	        console.log(res.data.data);
	        wx.requestPayment({
	          timeStamp: data.timeStamp,
	          nonceStr: data.nonceStr,
	          // package: data.package,
	          package: 'prepay_id=' + data.prepayId,
	          signType: 'MD5',
	          paySign: data.paySign,
	          success(res) {
	            console.log(res)
	          },
	          fail(res) {
	            console.log(res)
	          }
	        })
	      }
	    })
	  },
})

四、效果图如下

在这里插入图片描述
在这里插入图片描述

吴维炜 HTTPS CSS ECMAScript 6
Q: 有多少程序员会去更换灯泡?
A: 没有,那是硬件问题。
Q:为什么程序员总是把圣诞节与万圣夜搞混?
A:因为 DEC 25 = OCT 31