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 中实现微信支付的主要内容,如果未能解决你的问题,请参考以下文章