third-payment
Version:
第三方支付统一支持模块,支持微信支付、支付宝支付。
309 lines (267 loc) • 8.21 kB
JavaScript
var request = require('request-promise'),
moment = require('moment'),
md5 = require('md5'),
xml = require('xml2js'),
_ = require('underscore'),
rand = require('random-gen'),
debug = require('debug')('third-payment'),
common = require('../common');
var xmlBuilder = new xml.Builder({
headless: true,
rootName: 'xml'
});
var xmlParser = new xml.Parser({
explicitArray: false
});
function sign(params, key) {
var temp = common.queryize(params, false) + '&key=' + key;
return md5(temp).toUpperCase();
}
function isValidSignParam(value, key) {
return (value && (key !== 'sign'));
}
class Weixin {
constructor(params){
if(params){
this.config = params;
}else{
throw new Error("缺少配置参数");
}
}
}
Weixin.prototype.verify = function(params){
debug('to verify %j with key %s', params, this.config.key);
var config = this.config;
var sig = params.sign;
if (!sig) {
return {result:false,msg:"verify sig failed"};
}
var temp = common.queryize(_.pick(params, isValidSignParam), false) + '&key=' + config.key;
if( (md5(temp).toUpperCase() === sig ) != true ){
return {result:false,msg:"verify weixin params"}
};
if (params.return_code !== 'SUCCESS') {
return {result:false,msg:'Unified order failed : ' + params.return_msg};
}
if (params.result_code != 'SUCCESS') {
return {result:false,msg:'Unified order failed: [' + params.err_code + '] : ' + params.err_code_des};
}
if (params.appid !== config.appid) {
return {result:false,msg:'Unified order not matched on appid: ' + params.appid + ' ; ' + config.appid};
}
if (params.mch_id !== config.mch_id) {
return {result:false,msg:'Unified order not matched on mch_id: ' + params.mch_id + ' ; ' + config.mch_id};
}
return {result:true};
}
Weixin.prototype.verifyResponse = function(response,format){
var _this = this;
if(format == "xml"){
return xmlToJson(response).then(
res =>{
return Promise.resolve(_this.verify(res));
}
);
}else{
return Promise.resolve(_this.verify(response));
}
}
Weixin.prototype.query = function(data){
if(data.trade_no){
data.transaction_id = data.trade_no;
delete data.trade_no;
}
var config = this.config;
var params = {
appid: config.appid,
mch_id: config.mch_id,
nonce_str: rand.alphaNum(10),
};
params = _.extend(params,data);
params.sign = sign(params, config.key);
var data = xmlBuilder.buildObject(params);
debug('data to be sent to weixin: %s', data);
var option = {
method: 'POST',
uri: 'https://api.mch.weixin.qq.com/pay/orderquery',
body: data
}
var _this = this;
return request(option)
.then(function(res) {
return xmlToJson(res);
}).then(function(wx_resp) {
debug('data received from weixin: %j', wx_resp);
var verify_code = _this.verify(wx_resp);
if(!verify_code || verify_code.result != true){
return Promise.reject(verify_code.msg || "微信支付服务器错误");
}
return Promise.resolve(wx_resp);
});
}
Weixin.prototype.create = function(datas){
var config = this.config;
var now = moment(),
span = config.it_b_pay || "1d",
expire = moment().add(span.slice(0, -1), span[span.length - 1]);
var params = {
appid: config.appid,
mch_id: config.mch_id,
nonce_str: rand.alphaNum(10),
body: datas.description ,
detail: datas.detail || "",
out_trade_no: datas.trade_id,
fee_type : "CNY",
total_fee: Math.round(datas.amount * 100),
time_start: now.format('YYYYMMDDHHmmss'),
time_expire: expire.format('YYYYMMDDHHmmss'),
notify_url : datas.notify_url,
spbill_create_ip : datas.ip
};
switch(datas.type){
case "weixin_app" :
params.trade_type = "APP";
break;
case "weixin_mp" :
params.trade_type = "JSAPI";
if(!datas.openid){
return Promise.reject("缺少openid");
}
params.openid = datas.openid;
break;
case "weixin_native" :
params.trade_type = "NATIVE";
if(!datas.product_id){
return Promise.reject("缺少product_id");
}
params.product_id = datas.product_id
break;
case "weixin_web" :
params.trade_type = "MWEB";
if(!datas.openid){
return Promise.reject("缺少openid");
}
params.openid = datas.openid
if(!datas.scene_info){
return Promise.reject("缺少scene_info");
}
if(typeof datas.scene_info == "object"){
params.scene_info = JSON.stringify(datas.scene_info);
}else{
params.scene_info = datas.scene_info;
}
break;
}
params.sign = sign(params, config.key);
var data = xmlBuilder.buildObject(params);
debug('data to be sent to weixin: %s', data);
var option = {
method: 'POST',
uri: 'https://api.mch.weixin.qq.com/pay/unifiedorder',
body: data
}
var _this = this;
return request(option)
.then(function(res) {
return xmlToJson(res);
}).then(function(wx_resp) {
debug('data received from weixin: %j', wx_resp);
var verify_code = _this.verify(wx_resp);
if(!verify_code || verify_code.result != true){
return Promise.reject(verify_code.msg || "微信支付服务器错误");
}
var prepaydata ;
var timestamp = parseInt(new Date().getTime() / 1000);
switch (wx_resp.trade_type){
case 'APP' :
prepaydata = {
appid: wx_resp.appid,
partnerid: wx_resp.mch_id,
prepayid: wx_resp.prepay_id,
package: 'Sign=WXPay',
noncestr: rand.alphaNum(10),
timestamp: timestamp
};
break;
case "JSAPI" :
prepaydata = {
appId: wx_resp.appid,
timeStamp: timestamp,
nonceStr: rand.alphaNum(10),
package: 'prepay_id=' + wx_resp.prepay_id,
signType: 'MD5'
}
break;
case "NATIVE" :
prepaydata = {
appid: wx_resp.appid,
mch_id: wx_resp.mch_id,
nonce_str: rand.alphaNum(10),
prepay_id: wx_resp.prepay_id,
code_url : wx_resp.code_url
}
break;
default:
prepaydata = null;
break;
}
return Promise.resolve(prepaydata);
});
}
Weixin.prototype.refund = function(data){
var config = this.config;
var params = {
appid: config.appid,
mch_id: config.mch_id,
nonce_str: rand.alphaNum(10),
out_trade_no : data.trade_id,
out_refund_no : data.refund_id,
total_fee : data.amount * 100 ,
refund_fee : data.actual_fee * 100
};
params.sign = sign(params, config.key);
var data = xmlBuilder.buildObject(params);
debug('data to be sent to weixin: %s', data);
var options = {
url: "https://api.mch.weixin.qq.com/secapi/pay/refund",
body: data,
key: config.client_key,
cert: config.client_cert,
timeout: 3000,
};
var _this = this;
return request(option)
.then(function(res) {
return xmlToJson(res);
}).then(function(wx_resp) {
debug('data received from weixin: %j', wx_resp);
var verify_code = _this.verify(wx_resp);
if(!verify_code || verify_code.result != true){
return Promise.reject(verify_code.msg || "微信支付服务器错误");
}
return Promise.resolve(wx_resp);
});
}
Weixin.prototype.formatOut = function(err){
return err ? xmlBuilder.buildObject({
return_code: 'FAIL',
return_msg: err.toString()
}) : xmlBuilder.buildObject({
return_code: 'SUCCESS',
return_msg: 'OK'
}) ;
}
function xmlToJson (data) {
return new Promise(function(resolve, reject){
xmlParser.parseString(data, function (err, result) {
if (err) {
debug('xml parser wx_resp err : %j', err);
resolve(false);
}
var wx_resp = result.xml;
resolve(wx_resp);
});
});
}
module.exports = Weixin ;