Skip to content

Commit

Permalink
feat 添加微信支付
Browse files Browse the repository at this point in the history
  • Loading branch information
tumobi committed Oct 20, 2017
1 parent 2359b45 commit 885ee4c
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 52 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"jsonwebtoken": "^8.0.0",
"kcors": "^2.2.1",
"lodash": "^4.17.4",
"md5": "^2.2.1",
"moment": "^2.18.1",
"request": "^2.81.0",
"request-promise": "^4.2.1",
Expand All @@ -21,7 +22,9 @@
"think-logger3": "^1.0.0",
"think-model": "^1.0.0",
"think-model-mysql": "^1.0.0",
"thinkjs": "^3.0.0"
"thinkjs": "^3.0.0",
"weixinpay": "^1.0.12",
"xml2js": "^0.4.19"
},
"devDependencies": {
"babel-cli": "^6.24.1",
Expand Down
3 changes: 2 additions & 1 deletion src/api/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module.exports = {
'cart/checked',
'cart/update',
'cart/delete',
'cart/goodscount'
'cart/goodscount',
'pay/notify'
]
};
11 changes: 1 addition & 10 deletions src/api/controller/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,6 @@ const rp = require('request-promise');
const _ = require('lodash');

module.exports = class extends Base {
/**
* index action
* @return {Promise} []
*/
async indexAction() {
const avatarPath = think.RESOURCE_PATH + '/static/user/avatar/1.' + _.last(_.split('https://img6.bdstatic.com/img/image/smallpic/liutaoxiaotu.jpg', '.'));
return this.success(avatarPath);
}

async loginByWeixinAction() {
const code = this.post('code');
const fullUserInfo = this.post('userInfo');
Expand Down Expand Up @@ -43,7 +34,7 @@ module.exports = class extends Base {
return this.fail('登录失败');
}

// 析用户数据
// 解释用户数据
const WeixinSerivce = this.service('weixin', 'api');
const weixinUserInfo = await WeixinSerivce.decryptUserInfoData(sessionData.session_key, fullUserInfo.encryptedData, fullUserInfo.iv);
if (think.isEmpty(weixinUserInfo)) {
Expand Down
75 changes: 37 additions & 38 deletions src/api/controller/pay.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
/* eslint-disable no-multi-spaces */
const Base = require('./base.js');
const rp = require('request-promise');

module.exports = class extends Base {
// 支付类型 1 微信支付 2支付宝
// TODO 支付功能由于没有公司账号和微信支付账号,所以没有经过测试,如您可以提供相关账号测试,可联系 [email protected]

/**
* 获取支付的请求参数
* @returns {Promise<PreventPromise|void|Promise>}
*/
async payPrepayAction() {
async prepayAction() {
const orderId = this.get('orderId');
// const payType = this.get('payType');

const orderInfo = await this.model('order').where({id: orderId}).find();
if (think.isEmpty(orderInfo)) {
Expand All @@ -20,42 +17,44 @@ module.exports = class extends Base {
if (parseInt(orderInfo.pay_status) !== 0) {
return this.fail(400, '订单已支付,请不要重复操作');
}
const openid = await this.model('user').where({id: orderInfo.user_id}).getField('weixin_openid', true);
if (think.isEmpty(openid)) {
return this.fail('微信支付失败');
}
const WeixinSerivce = this.service('weixin', 'api');
try {
const returnParams = await WeixinSerivce.createUnifiedOrder({
openid: openid,
body: '订单编号:' + orderInfo.order_sn,
out_trade_no: orderInfo.order_sn,
total_fee: parseInt(orderInfo.actual_price * 100),
spbill_create_ip: ''
});
return this.success(returnParams);
} catch (err) {
return this.fail('微信支付失败');
}
}

// 微信支付统一调用接口,body参数请查看微信支付文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_sl_api.php?chapter=9_1
const options = {
method: 'POST',
url: 'https://api.mch.weixin.qq.com/pay/unifiedorder',
body: {
appid: 'payload',
mch_id: '',
sub_appid: '',
sub_mch_id: '',
device_info: '',
nonce_str: think.uuid(32),
sign: '',
sign_type: 'MD5',
body: '',
out_trade_no: '',
total_fee: orderInfo.actual_price * 100,
spbill_create_ip: '',
notify_url: '',
trade_type: 'JSAPI',
openid: '',
sub_openid: ''
}
};
const payParam = await rp(options);
if (payParam) {
async notifyAction() {
const WeixinSerivce = this.service('weixin', 'api');
const result = WeixinSerivce.payNotify(this.post('xml'));
console.log('WeixinSerivce.payNotify ' + result);
if (!result) {
return `<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[支付失败]]></return_msg></xml>`;
}

const orderModel = this.model('order');
const orderInfo = await orderModel.getOrderByOrderSn(result.out_trade_no);
if (think.isEmpty(orderInfo)) {
return `<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[订单不存在]]></return_msg></xml>`;
}

if (orderModel.updatePayStatus(orderInfo.id, 2)) {
} else {
return `<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[订单不存在]]></return_msg></xml>`;
}

// 统一返回成功,方便测试
return this.success({
'timeStamp': this.getTime(),
'nonceStr': think.uuid(16),
'package': 'prepay_id=wx201410272009395522657a690389285100',
'signType': 'MD5',
'paySign': 'jdsdlsdsd'
});
return `<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>`;
}
};
22 changes: 22 additions & 0 deletions src/api/model/order.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,26 @@ module.exports = class extends think.Model {

return statusText;
}

/**
* 更改订单支付状态
* @param orderId
* @param payStatus
* @returns {Promise.<boolean>}
*/
async updatePayStatus(orderId, payStatus = 0) {
return this.where({id: orderId}).limit(1).update({pay_status: parseInt(payStatus)});
}

/**
* 根据订单编号查找订单信息
* @param orderSn
* @returns {Promise.<Promise|Promise<any>|T|*>}
*/
async getOrderByOrderSn(orderSn) {
if (think.isEmpty(orderSn)) {
return {};
}
return this.where({order_sn: orderSn}).find();
}
};
108 changes: 108 additions & 0 deletions src/api/service/weixin.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
const crypto = require('crypto');
const md5 = require('md5');

module.exports = class extends think.Service {
/**
* 解析微信登录用户数据
* @param sessionKey
* @param encryptedData
* @param iv
* @returns {Promise.<string>}
*/
async decryptUserInfoData(sessionKey, encryptedData, iv) {
// base64 decode
const _sessionKey = new Buffer(sessionKey, 'base64');
Expand All @@ -26,4 +34,104 @@ module.exports = class extends think.Service {

return decoded;
}

/**
* 统一下单
* @param payInfo
* @returns {Promise}
*/
createUnifiedOrder(payInfo) {
const WeiXinPay = require('weixinpay');
const weixinpay = new WeiXinPay({
appid: think.config('weixin.appid'), // 微信小程序appid
openid: payInfo.openid, // 用户openid
mch_id: think.config('weixin.mch_id'), // 商户帐号ID
partner_key: think.config('weixin.partner_key') // 秘钥
});
return new Promise((resolve, reject) => {
weixinpay.createUnifiedOrder({
body: payInfo.body,
out_trade_no: payInfo.out_trade_no,
total_fee: payInfo.total_fee,
spbill_create_ip: payInfo.spbill_create_ip,
notify_url: think.config('weixin.notify_url'),
trade_type: 'JSAPI'
}, (res) => {
if (res.return_code === 'SUCCESS' && res.result_code === 'SUCCESS') {
const returnParams = {
'appid': res.appid,
'timeStamp': parseInt(Date.now() / 1000) + '',
'nonceStr': res.nonce_str,
'package': 'prepay_id=' + res.prepay_id,
'signType': 'MD5'
};
const paramStr = `appId=${returnParams.appid}&nonceStr=${returnParams.nonceStr}&package=${returnParams.package}&signType=${returnParams.signType}&timeStamp=${returnParams.timeStamp}&key=` + think.config('weixin.partner_key');
returnParams.paySign = md5(paramStr).toUpperCase();
resolve(returnParams);
} else {
reject(res);
}
});
});
}

/**
* 生成排序后的支付参数 query
* @param queryObj
* @returns {Promise.<string>}
*/
buildQuery(queryObj) {
const sortPayOptions = {};
for (const key of Object.keys(queryObj).sort()) {
sortPayOptions[key] = queryObj[key];
}
let payOptionQuery = '';
for (const key of Object.keys(sortPayOptions).sort()) {
payOptionQuery += key + '=' + sortPayOptions[key] + '&';
}
payOptionQuery = payOptionQuery.substring(0, payOptionQuery.length - 1);
return payOptionQuery;
}

/**
* 对 query 进行签名
* @param queryStr
* @returns {Promise.<string>}
*/
signQuery(queryStr) {
queryStr = queryStr + '&key=' + think.config('weixin.partner_key');
const md5 = require('md5');
const md5Sign = md5(queryStr);
return md5Sign.toUpperCase();
}

/**
* 处理微信支付回调
* @param notifyData
* @returns {{}}
*/
payNotify(notifyData) {
if (think.isEmpty(notifyData)) {
return false;
}

const notifyObj = {};
let sign = '';
for (const key of Object.keys(notifyData)) {
if (key !== 'sign') {
notifyObj[key] = notifyData[key][0];
} else {
sign = notifyData[key][0];
}
}
if (notifyObj.return_code !== 'SUCCESS' || notifyObj.result_code !== 'SUCCESS') {
console.log('return_code false');
return false;
}
const signString = this.signQuery(this.buildQuery(notifyObj));
if (think.isEmpty(sign) || signString !== sign) {
return false;
}
return notifyObj;
}
};
7 changes: 5 additions & 2 deletions src/common/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
module.exports = {
default_module: 'api',
weixin: {
secret: '',
appid: ''
appid: '', // 小程序 appid
secret: '', // 小程序密钥
mch_id: '', // 商户帐号ID
partner_key: '', // 微信支付密钥
notify_url: '' // 微信异步通知,例:https://www.nideshop.com/api/pay/notify
}
};

0 comments on commit 885ee4c

Please sign in to comment.