前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Egg 中实现微信支付

Egg 中实现微信支付

作者头像
越陌度阡
发布2021-08-24 10:27:12
1.2K0
发布2021-08-24 10:27:12
举报

1. 接入微信支付应用

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

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

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

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

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

2. 项目中使微信支付

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

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

代码语言:javascript
复制
// 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

};

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

代码语言:javascript
复制
// 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);

}

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

代码语言:javascript
复制
// 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;

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

代码语言:javascript
复制
// 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;

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

代码语言:javascript
复制
// 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;
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/08/22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 接入微信支付应用
  • 2. 项目中使微信支付
相关产品与服务
云开发 CloudBase
云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档