程序谷 认真生活 享受生活

Vue+EggJS实现微信支付

⚠️ 本文最后更新于2023年11月15日,已经过了569天没有更新,若内容或图片失效,请留言反馈

微信支付平台配置

微信支付平台配置支付授权目录。

调用微信支付需要在此目录下。
配置如:https://www.xxx.com/pay/

产品中心->开发配置->支付配置
支付目录

公众号后台配置

因为我们需要获得用户的openid,所以微信公众号后台也需要做对应的配置。

微信公众号设置网页授权域名,只有在设定的域名下面才能获取到得到openid的必要参数code.
设置与开发->公众号设置->功能设置->网页授权域名

配置如:wap.xxxx.cn/scan/control

在此页面下通过重定向获取code。
公众号配置

获取openid

获取code

在wap.xxxx.cn/scan/control 页面下进行重定向操作,url会自动添加code参数。

vue静默获取code代码

    //code是自己设置的变量
    //encodeURIComponent是系统自带的encode方法,因为url必须要encode才行
      if (!this.code) {
        window.location.href =
          'https://open.weixin.qq.com/connect/oauth2/authorize?appid=您的微信appid&redirect_uri=' +
          encodeURIComponent(
            'https://wap.xxx.cn/scan/control?sn=' +
              this.sn +
              '&name=' +
              this.name
          ) +
          '&response_type=code&scope=snsapi_base&state=1&connect_redirect=1#wechat_redirect'
      } else {
        this.code = this.$route.query.code
      }

获得openid

Eggjs后台获得openid

           const url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=您的appid&secret=您的secret&code=您刚刚获得的code&grant_type=authorization_code"
           //发起get请求
            const result = await axios(url)
            //在data中获得openid
            const openid = result.data.openid;

配置微信

获得配置参数

前端发起请求获得配置参数

后端安装wechat-api库

npm i wechat-api --save
const API = require('wechat-api');
const api = new API('appid', 'secret');
    var param = {
      debug: false,
      jsApiList: ['chooseWXPay'],//选择jsapi接口
      url: 'https://wap.xxx.cn/'+this.ctx.request.body.path//这里是调用页面的完整地址
    };
    const data = await new Promise((resolve, reject) => {
      api.getJsConfig(param, function (err, result) {
        resolve(result)
      });
    })
    //data就是配置参数

初始化配置

前端获得配置参数后进行初始化操作
前端安装weixin-js-sdk库并引用

npm i weixin-js-sdk --save
const wx = require('weixin-js-sdk')
      wx.config({
        debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
        appId: result.appId, // 必填,公众号的唯一标识
        timestamp: result.timestamp, // 必填,生成签名的时间戳
        nonceStr: result.nonceStr, // 必填,生成签名的随机串
        signature: result.signature, // 必填,签名
        jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表
      })

发起支付

发起支付请求

后端生成订单的参数
这里用到一个工具 wxpay

            //attach订单附加信息可以不填
            //body订单详情,可以使用json字符串
            //mch_id微信支付id,从微信支付后台获取
            //openid前面获取到的
            //bookingNo自定义的订单号
            //total支付金额,以分为单位,只能是整数
            //notify_url这个很重要,微信支付后会不停的像这个url发送post请求。
            const arg = await new Promise((resolve, reject) => {
                try {
                    wxpay.order(attach, body, mch_id, openid, bookingNo, total, notify_url).then(function (data, err) {
                        console.dir(err)
                        resolve(data)
                    }).catch(function (err) {
                        console.dir(err)
                        resolve(err)
                    });
                } catch (error) {
                    resolve(null)
                }
            })
            

获取到的arg返给前端。

前端调用支付

      wx.ready(function() {
        wx.chooseWXPay({
          appId: config.appId,
          timestamp: config.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
          nonceStr: config.nonceStr, // 支付签名随机串,不长于 32 位
          package: 'prepay_id=' + config.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
          signType: config.signType, // 微信支付V3的传入RSA,微信支付V2的传入格式与V2统一下单的签名格式保持一致
          paySign: config.paySign, // 支付签名
          success: function(res) {
            // 支付成功后的回调函数
            if (res.errMsg == 'chooseWXPay:ok') {
                //    成功
            } else {
                //失败
            }
          },

          cancel: function(res) {
            this.$notify({
              type: 'warning',
              message: '您已取消支付'
            })
          },
          // 支付失败回调函数
          fail: function(res) {
            this.$notify({
              type: 'error',
              message: '支付失败'
            })
          }
        })
      })

回调处理

微信支付比较重要的部分,就是notify_url

微信支付成功后会不停的向这个接口发送post请求,直到得到回应。

回复如下内容即可

        this.ctx.body = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'

在post请求里面我们还能获得支付详细信息,该请求是以xml形式发送的。
eggjs获取xml数据需要进行配置才行,具体配置如下

//config.prod.js或config.default.js
  config.bodyParser = {
    enable: true,
    encoding: 'utf8',
    formLimit: '100kb',
    jsonLimit: '100kb',
    strict: true,
    queryString: {
      arrayLimit: 100,
      depth: 5,
      parameterLimit: 1000,
    },
    enableTypes: ['json', 'form', 'text'],
    extendTypes: {
      text: ['text/xml', 'application/xml'],
    },
  };

body中的xml大概如下

            // <attach><![CDATA[你好]]></attach>
            // <bank_type><![CDATA[CMB_CREDIT]]></bank_type>
            // <cash_fee><![CDATA[1]]></cash_fee>
            // <fee_type><![CDATA[CNY]]></fee_type>
            // <is_subscribe><![CDATA[Y]]></is_subscribe>
            // <mch_id><![CDATA[1286586201]]></mch_id>
            // <nonce_str><![CDATA[evoi9w8mwev]]></nonce_str>
            // <openid><![CDATA[oohmmwJCTH7H6PjcbmayUbthUQNo]]></openid>
            // <out_trade_no><![CDATA[ZMDZ_QR_RELAY_1650279755263]]></out_trade_no>
            // <result_code><![CDATA[SUCCESS]]></result_code>
            // <return_code><![CDATA[SUCCESS]]></return_code>
            // <sign><![CDATA[FA5B529FF8497795D477C1BF4533ED3A]]></sign>
            // <time_end><![CDATA[20220418190239]]></time_end>
            // <total_fee>1</total_fee>
            // <trade_type><![CDATA[JSAPI]]></trade_type>
            // <transaction_id><![CDATA[4200001322202204181928545592]]></transaction_id>
            // </xml>

从xml中提取订单号

        //wxpay是上面提到的工具类
       const orderStr = wxpay.getXMLNodeValue('out_trade_no', body)
        let orderId = ''
        if (orderStr) {
            const arr = orderStr.split('![CDATA[')
            orderId = arr[1].substring(0, arr[1].length - 3)
        }

代码示范

前端

第一页,我在这里获取code

<template>
  <div @click="goCharge()">
    初始页面https://xxxx/scan/control
  </div>
</template>

<script>
export default {
  data() {
    return {
      code: '',
    }
  },
  mounted() {
    this.code = this.$route.query.code
      if (!this.code) {
        window.location.href =
          'https://open.weixin.qq.com/connect/oauth2/authorize?appid=您的appid&redirect_uri=' +
          encodeURIComponent(
            'https://xxx/scan/control'
          ) +
          '&response_type=code&scope=snsapi_base&state=1&connect_redirect=1#wechat_redirect'
      } else {
        this.code = this.$route.query.code
      }
  },
  methods: {
    goCharge() {
      if (out == 0) {
        this.$router.push(
          '/scan/pay?code=' +
            this.code
        )
      }
    },
  }
}
</script>

正式开始调用微信支付(/scan/pay)

<template>
  <div @click="toPay()">
    发起支付
  </div>
</template>

<script>
const wx = require('weixin-js-sdk')
export default {
  data() {
    return {
      config: null,
      code: '',
    }
  },
  mounted() {
    this.code = this.$route.query.code
    if (this.code) {
    //初始化配置
      this.initWechat()
    } else {
    //没有code
      this.$alert('非法请求,请重新扫码', '提示', {
        confirmButtonText: '确定',
        callback: (action) => {
          this.$router.go(-1)
        }
      })
    }
  },
  methods: {
    async initWechat() {
    //后端获得配置参数
      const result = await this.$store.dispatch('user/wechat')
      wx.config({
        debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
        appId: result.appId, // 必填,公众号的唯一标识
        timestamp: result.timestamp, // 必填,生成签名的时间戳
        nonceStr: result.nonceStr, // 必填,生成签名的随机串
        signature: result.signature, // 必填,签名
        jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表
      })
    },
    async toPay() {
    //code去获取openid,并获得支付参数
      const config = await this.$store.dispatch('pay/getWechatPayConfig', {
        code: this.code
      })
      const _this = this
      wx.ready(function() {
        wx.chooseWXPay({
          appId: config.appId,
          timestamp: config.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
          nonceStr: config.nonceStr, // 支付签名随机串,不长于 32 位
          package: 'prepay_id=' + config.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
          signType: config.signType, // 微信支付V3的传入RSA,微信支付V2的传入格式与V2统一下单的签名格式保持一致
          paySign: config.paySign, // 支付签名
          success: function(res) {
            // 支付成功后的回调函数
            // console.dir(res)
            alert(res.errMsg)
            if (res.errMsg == 'chooseWXPay:ok') {
              _this.$notify({
                type: 'success',
                message: '开始充电'
              })
            } else {
                //失败
            }
          },
          cancel: function(res) {
            this.$notify({
              type: 'warning',
              message: '您已取消支付'
            })
          },
          // 支付失败回调函数
          fail: function(res) {
            this.$notify({
              type: 'error',
              message: '支付失败'
            })
          }
        })
      })
    }
  }
}
</script>

后端

// 接口/user/wechat
  async wechat() {
    const that = this
    var param = {
      debug: false,
      jsApiList: ['scanQRCode','chooseWXPay'],
      url: 'https://xxx/scan/pay'//这里是调用页面的完整地址
    };
    console.dir(param)
    const data = await new Promise((resolve, reject) => {
      api.getJsConfig(param, function (err, result) {
        resolve(result)
      });
    })
    console.dir(data)
    that.ctx.body = {
      code: 0,
      data
    }
  }
//接口/pay/getWechatPayConfig

    async getWechatPayConfig() {
        const code = this.ctx.request.body.code || ''
        if (code) {
            const url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=您的appid&secret=您的secret&code="
                + code + "&grant_type=authorization_code"
            const result = await axios(url)
            const openid = result.data.openid;
            const attach = '无';
            const body = JSON.stringify(content);
            const mch_id = "微信支付后台获取";
            const total = 1;
            const bookingNo = "ZMDZ_QR_RELAY_" + new Date().getTime();
            const notify_url = "https://xxx/api/v1/pay/notify"; //通知地址
            const arg = await new Promise((resolve, reject) => {
                try {
                    wxpay.order(attach, body, mch_id, openid, bookingNo, total, notify_url).then(function (data, err) {
                        console.dir(err)
                        resolve(data)
                    }).catch(function (err) {
                        console.dir(err)
                        resolve(err)
                    });
                } catch (error) {
                    resolve(null)
                }
            })
            this.ctx.body = {
                code: 0,
                data: arg
            }
        } else {
            this.ctx.body = {
                code: -1,
                message: '无效的支付'
            }
        }

    }
//接口/api/vi/notify
    async notify() {
        const body = this.ctx.request.body
        //提取订单id,其他提取方式一样
         const orderStr = wxpay.getXMLNodeValue('out_trade_no', body)
        let orderId = ''
        if (orderStr) {
            const arr = orderStr.split('![CDATA[')
            orderId = arr[1].substring(0, arr[1].length - 3)
        }
        //回复微信
        this.ctx.body = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'
    }
    
    // body中的xml内容
    // <attach><![CDATA[你好]]></attach>
    // <bank_type><![CDATA[CMB_CREDIT]]></bank_type>
    // <cash_fee><![CDATA[1]]></cash_fee>
    // <fee_type><![CDATA[CNY]]></fee_type>
    // <is_subscribe><![CDATA[Y]]></is_subscribe>
    // <mch_id><![CDATA[1286586201]]></mch_id>
    // <nonce_str><![CDATA[evoi9w8mwev]]></nonce_str>
    // <openid><![CDATA[oohmmwJCTH7H6PjcbmayUbthUQNo]]></openid>
    // <out_trade_no><![CDATA[ZMDZ_QR_RELAY_1650279755263]]></out_trade_no>
    // <result_code><![CDATA[SUCCESS]]></result_code>
    // <return_code><![CDATA[SUCCESS]]></return_code>
    // <sign><![CDATA[FA5B529FF8497795D477C1BF4533ED3A]]></sign>
    // <time_end><![CDATA[20220418190239]]></time_end>
    // <total_fee>1</total_fee>
    // <trade_type><![CDATA[JSAPI]]></trade_type>
    // <transaction_id><![CDATA[4200001322202204181928545592]]></transaction_id>
    // </xml>

结束

简单的微信支付对接结束

By 大芃展翅 On
此页面评论区已关闭
程序谷 |  蜀ICP备2020031553号-1