Egg 中实现微信支付

Posted aiguangyuan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Egg 中实现微信支付相关的知识,希望对你有一定的参考价值。

1. 接入微信支付应用

1. 必须是个体工商户、企业、政府及事业单位之一。

2. 点击 微信支付商户平台 ,点击“接入微信支付”以便注册微信支付商户号,扫码验证后创建申请单、填写商户资料、提交申请后给官方审核,审核时间约为1到5个工作日。

3. 官方审核通过后就会发送审核通过的邮件到你申请时填写的邮箱,邮件里就会包含应用app_id微信支付商户号等信息。

4. 通过邮件的信息登录商户平台,选择“账户中心”选项卡,点击“API密钥”菜单,首次打开时会要求安装操作证书,按要求安装完成后再次打开即可设置密钥,保存好设置的密钥,开发微信支付功能时需要用到。

5. 如果是PC端网站支付,需要在商户平台中选择“产品中心”选项卡,在支付产品中点击“Native支付”,确认此功能是否开通,如果没有开通需要开通。

2. 项目中使微信支付

在通过审核的应用中获取app_id、微信支付商户号、支付密钥,以便在开发中使用。

首先配置微信支付需要的相关信息,主要涉及到支付商户号、app_id、支付密钥、支付成功的异步通知地址、忽略CRSF验证、配置指定的端口号。

// config/config.default.js
'use strict';
module.exports = appInfo => {

  const config = exports = {};
  // use for cookie sign key, should change to your own and keep security
  config.keys = appInfo.name + '_1585450669767_9677';

  // 微信支付的配置
  exports.wechatPayConfig = {
    mch_id: '商户号',
    wxappid: "微信APPID",
    wxpaykey: '支付密钥'
  }

  // 微信支付回调地址
  exports.wechatPayBasicParams = {
    // 此回调地址必须在微信商户平台后台配置
    // 域名必须是http或https开头
    notify_url: "http://xxx.xxx.xx.xx/wechatPay/wechatPayNotify"
  }

  // 忽略安全验证机制
  exports.security = {
    csrf: {
      // 为指定的URL忽略csrf验证
      ignore: ctx => {
        if (ctx.request.url == "/wechatPay/wechatPayNotify") {
          return true;
        }
        return false;
      },
    }
  }


  // 配置默认启动的端口
  config.cluster = {
    listen: {
      path: '',
      port: 8000,
      hostname: '0.0.0.0',
    }
  };

  return config

};

定义微信支付相关路由,主要有调用支付的路由、支付成功后的异步通知路由。

// app/router/default.js

'use strict';

module.exports = app => {

  const {router,controller } = app;

  // 微信支付
  router.get('/wechatPay/pay',controller.default.wechatPay.pay);
  // 异步通知(关闭CSRF验证)
  router.post('/wechatPay/wechatPayNotify',controller.default.wechatPay.wechatPayNotify);

}

由于微信支付的流程相对比较复杂,以下对支付的方法做了一些封装,代码如下:

// app/library/wechatPay.js
const QueryString = require('querystring');

// cnpm install xml2js crypto request   --save
const XmlToJs = require('xml2js').parseString;
const Crypto = require('crypto');
const Request = require('request');

class WechatPay {

  // 构造函数
  constructor(config) {
    this.config = config;
  }

  // 获取微信的accessToken和openid
  getAccessToken(code, callback) {
    let getAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + this.config.wxappid + "&secret=" + this.config.wxappsecret + "&code=" + code + "&grant_type=authorization_code";
    request.post({ url: getAccessTokenUrl }, function (error, response, body) {
      if (!error && response.statusCode == 200) {
        if (40029 == body.errcode) {
          callback(error, body);
        } else {
          body = JSON.parse(body);
          callback(null, body);
        }
      } else {
        callback(error);
      }
    });
  }

  // 生成随机的NonceStr
  createNonceStr() {
    return Math.random().toString(36).substr(2, 15);
  };

  // 获取微信支付的签名
  getSign(signParams) {
    // 按 key 值的ascll 排序
    let keys = Object.keys(signParams);
    keys = keys.sort();
    let newArguments = {};
    keys.forEach(function (val, key) {
      if (signParams[val]) {
        newArguments[val] = signParams[val];
      }
    })

    let string = QueryString.stringify(newArguments) + '&key=' + this.config.wxpaykey;
    // 生成签名
    return Crypto.createHash('md5').update(QueryString.unescape(string), 'utf8').digest("hex").toUpperCase();
  }

  // 获取微信统一下单参数
  getUnifiedOrderXmlParams(obj) {
    let body = '<xml> ' +
      '<appid>' + this.config.wxappid + '</appid> ' +
      '<attach>' + obj.attach + '</attach> ' +
      '<body>' + obj.body + '</body> ' +
      '<mch_id>' + this.config.mch_id + '</mch_id> ' +
      '<nonce_str>' + obj.nonce_str + '</nonce_str> ' +
      '<notify_url>' + obj.notify_url + '</notify_url>' +
      '<openid>' + obj.openid + '</openid> ' +
      '<out_trade_no>' + obj.out_trade_no + '</out_trade_no>' +
      '<spbill_create_ip>' + obj.spbill_create_ip + '</spbill_create_ip> ' +
      '<total_fee>' + obj.total_fee + '</total_fee> ' +
      '<trade_type>' + obj.trade_type + '</trade_type> ' +
      '<sign>' + obj.sign + '</sign> ' +
      '</xml>';
    return body;
  }


  // 获取微信统一下单的接口数据
  getPrepayId(obj) {
    let that = this;
    // 生成统一下单接口参数
    let unifiedOrderParams = {
      appid: this.config.wxappid,
      attach: obj.attach,
      body: obj.body,
      mch_id: this.config.mch_id,
      nonce_str: this.createNonceStr(),
      // 微信付款后的回调地址
      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: 'NATIVE'
      // trade_type : 'JSAPI',
      // sign : getSign(),
    };

    // 返回 promise 对象
    return new Promise(function (resolve, reject) {
      // 获取 sign 参数
      unifiedOrderParams.sign = that.getSign(unifiedOrderParams);
      let url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
      Request.post({ url: url, body: JSON.stringify(that.getUnifiedOrderXmlParams(unifiedOrderParams)) }, function (error, response, body) {
        if (!error && response.statusCode == 200) {
          // 微信返回的数据为xml格式, 需要转换为json数据便于使用
          XmlToJs(body, { async: true }, function (error, result) {
            if (error) {
              reject(error);
            } else {
              // let prepay_id = result.xml.prepay_id[0]; // 小程序支付返回这个
              let code_url = result.xml.code_url[0];
              resolve(code_url);
            }
          });
        } else {
          reject(body);
        }
      });
    })
  }

  // 微信支付的所有参数
  getWechatPayParams(obj, callback) {
    let that = this;
    let prepay_id_promise = that.getPrepayId(obj);
    prepay_id_promise.then((prepay_id) => {

      let wechatPayParams = {
        // 公众号名称,由商户传入
        "appId": this.config.wxappid,
        // 时间戳,自1970年以来的秒数   
        "timeStamp": parseInt(new Date().getTime() / 1000).toString(),
        // 随机字符串       
        "nonceStr": that.createNonceStr(),
        // 通过统一下单接口获取
        // "package" : "prepay_id="+prepay_id,  // 小程序支付用这个
        "code_url": prepay_id,
        // 微信签名方式
        "signType": "MD5",
      };

      // 微信支付签名
      wechatPayParams.paySign = that.getSign(wechatPayParams);
      callback(null, wechatPayParams);

    }, function (error) {
      callback(error);
    });
  }

  // 创建订单
  createOrder(obj, callback) {
    this.getWechatPayParams(obj, function (error, response) {
      if (error) {
        callback(error);
      } else {
        callback(null, response);
      }
    });
  }

}

module.exports = WechatPay;

在控制器中实现上面路由地址中所对应的相关方法。

// app/controller/default/wechatPay.js

'use strict';

const Controller = require('egg').Controller;

// cnpm install xml2js --save
const XmlToJs = require('xml2js').parseString;

class WechatPayController extends Controller {

  // 调用支付
  async pay() {
    let date = (new Date()).getTime();
    // 模拟商品数据
    let data = {
      title:'商品',
      out_trade_no: date.toString(),
      price: '0.1'
    }
    let code_url = await this.service.wechatPay.doPay(data);
    // 调用方法生成二维码
    let qrImage = await this.service.wechatPay.qrImage(code_url);
    this.ctx.type = 'image/png';
    this.ctx.body = qrImage;
  }

  // 异步通知(地址必须在微信商户平台配置)
  async wechatPayNotify() {

    let that = this;
    let data = '';
    this.ctx.req.on('data', function (chunk) {
      data += chunk;
    });

    this.ctx.req.on('end', function () {
      XmlToJs(data, { explicitArray: false }, function (err, json) {
        let mySign = that.service.wechatPay.wechatPayNotify(json.xml);
        if(mySign ==json.xml.sign){
          // 更新数据
        }
      });
    })
  }
}

module.exports = WechatPayController;

控制器中调用的服务代码实现。

// app/service/wechatPay.js

'use strict';

// cnpm install qr-image --save
const QrImage = require('qr-image');

const WechatPay = require('../library/wechatPay.js');
const Service = require('egg').Service;

class WechatPayService extends Service {

  // 执行支付
  async doPay(orderData) {
    return new Promise((resove,reject) => {
      let pay = new WechatPay(this.config.wechatPayConfig);
      let notify_url = this.config.wechatPayBasicParams.notify_url;
      let out_trade_no = orderData.out_trade_no;
      let title = orderData.title;
      let price = orderData.price * 100;
      let ip = this.ctx.request.ip.replace(/::ffff:/, '');
      pay.createOrder({
        openid: '',
        // 微信支付完成后的回调
        notify_url: notify_url, 
        // 订单号
        out_trade_no: out_trade_no, 
        attach: title,
        body: title,
        // 此处的额度为分
        total_fee: price.toString(), 
        spbill_create_ip: ip
      }, function (error, response) {
        if (error) {
          reject(error);
        };
        resove(response.code_url);
      });
    })
  }

  // 生成二维码
  async qrImage(url) {
    let image = QrImage.image(url, { type: 'png' });
    return image;
  }

  // 接收微信官方提交的数据
  wechatPayNotify(params) {
    let pay = new WechatPay(this.config.wechatPayConfig);
    let notifyObj = params;
    let signObj = {};
    for (let attr in notifyObj) {
      if (attr != 'sign') {
        signObj[attr] = notifyObj[attr]
      }
    }
    let sign = pay.getSign(signObj);
    return sign;
  }

}

module.exports = WechatPayService;

以上是关于Egg 中实现微信支付的主要内容,如果未能解决你的问题,请参考以下文章

uni-app中实现微信|支付宝支付

VUE Element UI项目中实现微信扫码登录功能

django中实现微信消息推送

Android中实现微信分享的功能

Android中实现微信分享的功能

PHP实现微信申请退款流程实例源码