NodeJS整合银联网关支付(DEMO)
银联支付的测试开发做的很完善,可以下载各个语言的测试包,进行开发测试,但是并没有nodejs的,难点就是证书签名还有验签这两个步骤。
其实银联加密方式和支付宝微信不同的地方在于,使用了非对称加密,意思是为了在网络中传输安全,双方约定各自产生一个公钥还有私钥,私钥自己保存,公钥公开给对方(你要发送信息的人都知道)。当需要传输秘密的信息时候,用自己的私钥加密,发给对方,对方收到信息后,为了判定这个是否伪造(是不是确实从你这儿发送给他的),那么拿出你的公钥进行验证,发现是一样的,那么就可以确定这个确实是你发送的。这样做就可以保证信息的安全。
下面是code:
银联配置文件:config.js
//配置银联支付需要的数据-这都是银联测试商户信息,可以上https://merchant.unionpay.com/portal/login.jsp去申请测试商户 merId:'777290058136713',//商户id font_trans_url:'https://101.231.204.80:5000/gateway/api/frontTransReq.do',//网关跳转至银联平台支付页面地址 sigle_query_url:'https://101.231.204.80:5000/gateway/api/queryTrans.do',//单笔查询请求地址 sign_cert_dir:__dirname+'/certificates',//签名证书路径 certId:'40220995861346480087409489142384722381', sign_cert_pwd:'0000000',//签名证书密码 sign_cert_path:__dirname+'/certificates/700000000000001_acp.pfx',//签名用私钥证书 validate_cert_path:__dirname+'/certificates/verify_sign_acp.cer',//验签用银联公钥证书
.pfx结尾的都是用密码加密过后的私钥
.cer结尾的都是公钥
银联支付模块unionPay.js
varvalidator=require('validator'),
util=require('util'),
_=require('underscore'),
crypto=require('crypto'),
x509=require('x509'),
sha1=require('sha1'),
wopenssl=require('wopenssl'),
config=require('./config');//加载银联配置
//银联网关支付
varunionPay={
//创建预订单
/*
*参数parms:{out_trade_no:OUT_TRADE_NO,fee:FEE}
*out_trade_no商户订单号fee订单金额,单位分
*/
sdk_front_notice_url:'http://'+config.domain+'/unionPay/result',//银联网关支付前台通知地址
sdk_back_notic_url:'http://'+config.domain+'/unionPay/productPay',//银联网关支付后台通知地址
createOrder:
function(parms,callback){
varerrmsg;
vartimeStamp=parms.timeStamp;
if(parms.payType==0){
varback_notic_url='http://'+config.domain+'/unionPay/productPay';
}elseif(parms.payType==1){
varback_notic_url='http://'+config.domain+'/unionPay/rechargePay';
}else{
varback_notic_url=this.sdk_back_notic_url;
}
varformData={
'version':'5.0.0',//版本号
'encoding':'utf-8',//编码方式
'txnType':'01',//交易类型
'txnSubType':'01',//交易子类
'bizType':'000201',//业务类型
'frontUrl':this.sdk_front_notice_url,//前台通知地址
'signMethod':'01',//签名方法
'channelType':'08',//渠道类型,07-PC,08-手机
'accessType':'0',//接入类型
'currencyCode':'156',//交易币种,境内商户固定156
//TODO以下信息需要填写
'merId':config.merId,//商户代码,请改自己的测试商户号,此处默认取demo演示页面传递的参数
'orderId':parms.out_trade_no,//商户订单号,8-32位数字字母,不能含“-”或“_”,此处默认取demo演示页面传递的参数,可以自行定制规则
'txnTime':timeStamp,//订单发送时间timestamp,格式为YYYYMMDDhhmmss,取北京时间,此处默认取demo演示页面传递的参数
'txnAmt':parms.fee,//交易金额,单位分
'backUrl':back_notic_url,//后台通知地址
'certId':''//可不必填写,在SignKeyFromPfx中返回
};
varprivateKey;
varcertId;
varcert;
SignKeyFromPfx(function(err,result){
if(err){
errmsg='证书签名失败';
}else{
certId=result.certId;
privateKey=result.key;
formData.certId=certId;
if(formData.signature){
deleteformData.signature
}
//----签名开始----
//参数转变为签名串
varunionPay_parms=transForSign(formData);
//摘要
varunionPay_parms_sha1=sha1(unionPay_parms);
//签名
varsigner=crypto.createSign('RSA-SHA1');
signer.update(unionPay_parms_sha1);
varsignature_base64=signer.sign(privateKey,'base64');
//放入域中
formData.signature=signature_base64;
//加入表单请求银联的地址
formData.action_url=config.font_trans_url;
//console.log(formData);
if(errmsg){
callback(errmsg);
}else{
callback(null,formData);
}
}
});
},
//签名
sign:
function(parms,callback){
varerrmsg;
varformData=parms;
SignKeyFromPfx(function(err,result){
if(err){
errmsg='证书签名失败';
}else{
certId=result.certId;
privateKey=result.key;
if(formData.signature){
deleteformData.signature
}
//----签名开始----
//参数转变为签名串
varunionPay_parms=transForSign(formData);
//摘要
varunionPay_parms_sha1=sha1(unionPay_parms);
//签名
varsigner=crypto.createSign('RSA-SHA1');
signer.update(unionPay_parms_sha1);
varsignature_base64=signer.sign(privateKey,'base64');
//放入域中
formData.signature=signature_base64;
//console.log(formData);
if(errmsg){
callback(errmsg);
}else{
callback(null,formData);
}
}
});
},
//验签
validate:
function(parms,callback){
varvalidate_signature=parms.signature;
deleteparms.signature;
varformData=parms;
ValidateKeyFromCer(formData,validate_signature,function(err,result){
if(err||!validate_signature||!formData){
console.log('验签失败');
callback('验签失败');
}else{
varpublicKey=result.key;
if(formData.signature){
deleteformData.signature
}
//----验签开始----
varunionPay_parms=transForSign(formData);
varunionPay_parms_sha1=sha1(unionPay_parms);
//console.log('待验证签:'+validate_signature);
varverifier=crypto.createVerify('RSA-SHA1');
//console.log('验证签名publickey:\n'+publicKey);
//console.log('验证签名src_sign:'+unionPay_parms_sha1);
verifier.update(newBuffer(unionPay_parms_sha1,'utf-8'));
varis_success=verifier.verify(publicKey,validate_signature,'base64');
if(is_success){
callback(null,formData);
}else{
console.log('验签不相等');
callback('验签不相等');
}
}
});
}
};
//签名串算法--将参数排序,转成键值对格式字符串
functiontransForSign(params){
vararray=[]
for(variinparams){
array.push(''+i+'='+params[i])
}
varstringSignTemp=_.sortBy(array,function(str){
returnstr;
});
returnstringSignTemp.join('&');
};
//通过证书密码获得证书的rsa-privatekey值和证书Id
functionSignKeyFromPfx(callback){
if(config.certsData){
callback(null,config.certsData);
}else{
varcertPath=config.sign_cert_path;
varcertPwd=config.sign_cert_pwd;
varcertDir=config.sign_cert_dir;
varp12=wopenssl.pkcs12.extract(certPath,certPwd);
//console.log(p12.certificate);//p12.certificate和p12.rsa
varcerts=wopenssl.x509.parseCert(p12.certificate);
//因为不知道怎么将十六进制证书id:certs.serial变成十进制证书id,因为这是个很大的整形biglong
varcertsData={};
certsData.certId=config.certId;
certsData.key=p12.rsa;
certsData.ca=certs;
//存入config
config.certsData=certsData;
callback(null,certsData);//{key:String,certId:String,ca:Array}
}
};
//获得验签证书的rsa-publickey值
functionValidateKeyFromCer(formData,signature,callback){
if(config.validCertsData){
callback(null,config.validCertsData);
}else{
varvalidateCertPath=config.validate_cert_path;
varcerts=wopenssl.x509.parseCert(validateCertPath);
//console.log(certs);
varfs=require('fs');
varCERTIFICATE=fs.readFileSync(validateCertPath);
console.log(CERTIFICATE);
varpublicKey=CERTIFICATE.toString('ascii');
varvalidCertsData={};
validCertsData.key=publicKey;
validCertsData.cert=CERTIFICATE;
config.validCertsData=validCertsData;
if(publicKey){
callback(null,validCertsData);
}else{
msg='验签失败';
callback(msg);
}
}
};
//转化时间格式函数
functionformat(){
//时间格式化
varformat='yyyyMMddhhmmss';
date=newDate();
varo={
'M+':date.getMonth()+1,//month
'd+':date.getDate(),//day
'h+':date.getHours(),//hour
'm+':date.getMinutes(),//minute
's+':date.getSeconds(),//second
'q+':Math.floor((date.getMonth()+3)/3),//quarter
'S':date.getMilliseconds()//millisecond
};
if(/(y+)/.test(format))
format=format.replace(RegExp.$1,(date.getFullYear()+'').substr(4-RegExp.$1.length));
for(varkino)
if(newRegExp('('+k+')').test(format))
format=format.replace(RegExp.$1,RegExp.$1.length==1?o[k]:('00'+o[k]).substr((''+o[k]).length));
returnformat;
};
module.exports=unionPay;
其实最重要的是签名还有验签部分,对证书.pfx和.cer的处理,其中的createOrder方法只是方便使用返回的请求表单内容。
在测试完成后,生产环境的配置还是不太一样。当银联商户申请成功后,银联会发一份邮件,上面有商户号,你的私钥证书.pfx需要你自己根据他的邮件提示去下载的,附件内的“证书下载,安装”文件有详细的说明教程。还有就是配置中的请求地址https://101.231.204.80:5000需要换为https://gateway.95516.com,生产环境中除非是从商户提交审核的域名发起的请求,否则一律会报错亲爱的用户,您本次交易可能存在风险.
以上所述是小编给大家介绍的NodeJS整合银联网关支付DEMO,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!