nodejs微信扫码支付功能实现


Posted in NodeJs onFebruary 17, 2018

前言

本篇文章主要是记录本人在微信扫码支付过程中所遇到的问题,给大家一个借鉴作用,希望对你们有帮助

开发环境

  • nodejs v8.1.0
  • egg v1.1.0

准备工作

微信公众号-appid

微信商户号-mch_id

key值(签名算法所需,其实就是一个32位的密码,可以用md5生成一个)(key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置)

扫码支付-统一下单

以下才用的是微信模式二,因为比较简单

let MD5 = require('md5'),
    xml2js = require('xml2js'),
    url = "https://api.mch.weixin.qq.com/pay/unifiedorder",// 下单请求地址
    appid = '公众号id',
    mch_id = '微信商户号';
    notify_url = '回调地址',
    out_trade_no = '自己设置的订单号',// 微信会有自己订单号、我们自己的系统需要设置自己的订单号
    total_fee = '订单金额',// 注意,单位为分
    body = '商品简单描述', 
    trade_type = 'NATIVE',// 交易类型,JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付
    nonce_str = moment().format('YYYYMMDDHHmmssSSS'),// 随机字符串32位以下
    stringA = `appid=${公众号id}&body=${body}&mch_id=${微信商户号}&nonce_str=${nonce_str}¬ify_url=${
    notify_url}&out_trade_no=${out_trade_no}&spbill_create_ip=${ctx.request.ip}&total_fee=${total_fee}&trade_type=${trade_type}`,
    stringSignTemp = stringA + "&key=xxxxxxxxxxxxxxxxx", //注:key为商户平台设置的密钥key
    sign = MD5(stringSignTemp).toUpperCase(); //注:MD5签名方式

以上就是我们所需要的一些参数

签名生成算法见微信官方:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3

spbill_create_ip 是 终端ip地址

下面把所有的参数拼接成xml

const formData = "<xml>";
    formData += "<appid>" + appid + "</appid>"; //appid
    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>"; //支付成功后微信服务器通过POST请求通知这个地址
    formData += "<out_trade_no>" + out_trade_no + "</out_trade_no>"; //订单号
    formData += "<total_fee>" + total_fee + "</total_fee>"; //金额
    formData += "<spbill_create_ip>" + ctx.request.ip + "</spbill_create_ip>"; //ip
    formData += "<trade_type>NATIVE</trade_type>"; //NATIVE会返回code_url ,JSAPI不会返回
    formData += "<sign>" + sign + "</sign>";
    formData += "</xml>";
  // 这里使用了egg里面请求的方式
  const resultData = yield ctx.curl(url, {
      method: 'POST',
      content: formData,
      headers: {
        'content-type': 'text/html',
      },
    });

  // xml转json格式
  xml2js.parseString(resultData.data, function (err, json) {
    if (err) {
      new Error("解析xml报错")
    } else {
      var result = formMessage(json.xml); // 转换成正常的json 数据
      console.log(result) //打印出返回的结果
    }
  })
  var formMessage = function (result) {
    var message = {};
    if (typeof result === 'object') {
      var keys = Object.keys(result);
      for (var i = 0; i < keys.length; i++) {
        var item = result[keys[i]];
        var key = keys[i];
        if (!(item instanceof Array) || item.length === 0) {
          continue;
        }
        if (item.length === 1) {
          var val = item[0];
          if (typeof val === 'object') {
            message[key] = formMessage(val);
          } else {
            message[key] = (val || '').trim();
          }
        } else {
          message[key] = [];
          for (var j = 0, k = item.length; j < k; j++) {
            message[key].push(formMessage(itemp[j]));
          }
        }
      }
    }
    return message;
  }

上面使用了egg的请求方式,原生node可以使用request

var request = require('request');
  request({
    url: url,
    method: "POST",
    body: formData
  }, function(error, response, body) {
    if (!error && response.statusCode == 200) {
    }
  });

如果请求成功会最终返回一个xml,然后我们进行解析成json的格式,里面会有一个code_url和out_trade_no,我们需要把这两个返回给前端,然后通过生成二维码展示给用户扫码,完成支付

监听支付是否成功

上面操作完成之后,我们需要知道用户是否完成支付,因为用户会停留在该页面,我们需要在用户付完款之后,通知用户支付成功。

首先,用户发起支付的时候我们会生成二维码,让用户完成扫码支付,我们还要做的是,开一个定时器,每隔一段时间去发送一个请求,这个时候,我们node后台就需要写一个查询订单的接口,之前我们拿到了out_trade_no,也就是我们系统内部的订单号,我们把这个数据发送给后台查询订单的接口,然后后台接收到之后会请求微信的查询接口地址https://api.mch.weixin.qq.com/pay/orderquery,流程跟上面一样,只是接口地址和微信返回的xml不一样而已,返回的字段会有一个状态即SUCCESS和NOTPAY,我们可以通过判断是否支付返回给前端,成功之后提示给用户支付成功,关闭定时器。

回调地址

这个是非常重要的一环,大部分的操作其实在上面就可以完成,但是有特殊的情况,比如用户电脑断网发送不了请求,但是手机付款了,这就会导致我们记录不到用户支付的信息。这个时候回调地址就很重要了

设置回调地址

微信商户中心->产品中心->开发配置->扫码支付

之后我们需要做的是后端用post来接收微信发送的异步回调信息,也是xml的格式,这里注意,如果不支持接收xml,可能会得到空的数据

这里还需要注意的是,我们在保存用户支付信息的同时,得先查改订单是否支付,以免重复操作,可能会插入多条记录的情况

总结

微信扫码支付坑还是有的,如果你是第一次摸索的话,下面罗列一下需要注意的地方

  1. 签名算法要写正确,不然是不会成功的,要拼接正确才行
  2. 微信返回的是xml格式的数据,我们得通过插件转成json,这样才方便获取数据
  3. 返回的code_url要给前端生成二维码用,然后需要开一个定时器查询该订单是否完成支付,最终通知用户结果
  4. 回调地址很重要,我们后端需要post接收微信返回的回调信息,然后保存信息,不过在保存用户支付信息的之前,我们得知道该订单是否已经保存过,以免重复添加。还有就是返回的是xml的数据,后端一定要保证能够接收得到,按照正常的方式是接收不了的,得额外设置。
NodeJs 相关文章推荐
Nodejs sublime text 3安装与配置
Jun 19 NodeJs
nodejs事件的监听与触发的理解分析
Feb 12 NodeJs
NodeJs的优势和适合开发的程序
Aug 14 NodeJs
nodejs+express实现文件上传下载管理网站
Mar 15 NodeJs
NodeJS处理Express中异步错误
Mar 26 NodeJs
用Nodejs搭建服务器访问html、css、JS等静态资源文件
Apr 28 NodeJs
nodejs创建简易web服务器与文件读写的实例
Sep 07 NodeJs
webstorm中配置nodejs环境及npm的实例
May 15 NodeJs
Linux Centos7.2下安装nodejs&amp;npm配置全局路径的教程
May 15 NodeJs
Nodejs中获取当前函数被调用的行数及文件名详解
Dec 12 NodeJs
NodeJS模块与ES6模块系统语法及注意点详解
Jan 04 NodeJs
NodeJS读取分析Nginx错误日志的方法
May 14 NodeJs
nodejs+express搭建多人聊天室步骤
Feb 12 #NodeJs
nodeJs实现基于连接池连接mysql的方法示例
Feb 10 #NodeJs
NodeJS简单实现WebSocket功能示例
Feb 10 #NodeJs
nodejs使用redis作为缓存介质实现的封装缓存类示例
Feb 07 #NodeJs
nodejs中Express与Koa2对比分析
Feb 06 #NodeJs
nodejs实现的连接MySQL数据库功能示例
Jan 25 #NodeJs
详解nodeJs文件系统(fs)与流(stream)
Jan 24 #NodeJs
You might like
用文本文件实现的动态实时发布新闻的程序
2006/10/09 PHP
在Mac OS的PHP环境下安装配置MemCache的全过程解析
2016/02/15 PHP
php PDO实现的事务回滚示例
2017/03/23 PHP
JS应用之禁止抓屏、复制、打印
2008/02/21 Javascript
jquery简单实现鼠标经过导航条改变背景图
2013/12/17 Javascript
判断复选框是否被选中的两种方法
2014/06/04 Javascript
Javascript中的arguments与重载介绍
2015/03/15 Javascript
Node.js中Request模块处理HTTP协议请求的基本使用教程
2016/03/31 Javascript
AngularJs 动态加载模块和依赖
2016/09/15 Javascript
js实现右键自定义菜单
2016/12/03 Javascript
NodeJS处理Express中异步错误
2017/03/26 NodeJs
Vue表单验证插件的制作过程
2017/04/01 Javascript
react-native-tab-navigator组件的基本使用示例代码
2017/09/07 Javascript
利用Node.js了解与测量HTTP所花费的时间详解
2017/09/22 Javascript
微信小程序表单验证功能完整实例
2017/12/01 Javascript
vue 虚拟dom的patch源码分析
2018/03/01 Javascript
vue+layui实现select动态加载后台数据的例子
2019/09/20 Javascript
js实现蒙版效果
2020/01/11 Javascript
vue中el-input绑定键盘按键(按键修饰符)
2020/07/22 Javascript
[06:21]2014DOTA2国际邀请赛 庆祝VG首阶段领跑;B叔为挣牛排半夜整理情报
2014/07/13 DOTA
[01:05:00]2018国际邀请赛 表演赛 Pain vs OpenAI
2018/08/24 DOTA
举例讲解Python的lambda语句声明匿名函数的用法
2016/07/01 Python
Python 读写文件和file对象的方法(推荐)
2016/09/12 Python
Virtualenv 搭建 Py项目运行环境的教程详解
2020/06/22 Python
如何基于Python pygame实现动画跑马灯
2020/11/18 Python
HTML5拖放功能_动力节点Java学院整理
2017/07/13 HTML / CSS
玩具反斗城葡萄牙官方商城:Toys"R"Us葡萄牙
2016/10/21 全球购物
俄罗斯汽车零件和配件在线商店:CarvilleShop
2019/11/29 全球购物
AJAX的优缺点都有什么
2015/08/18 面试题
Python中pass语句的作用是什么
2016/06/01 面试题
拉丁舞学习者的自我评价
2013/10/27 职场文书
小学语文新课改心得体会
2016/01/22 职场文书
导游词之茶卡盐湖
2019/11/26 职场文书
Python离线安装openpyxl模块的步骤
2021/03/30 Python
nginx 多个location转发任意请求或访问静态资源文件的实现
2021/03/31 Servers
MySQL中的隐藏列的具体查看
2021/09/04 MySQL