NodeJS整合银联网关支付(DEMO)


Posted in NodeJs onNovember 09, 2016

银联支付的测试开发做的很完善,可以下载各个语言的测试包,进行开发测试,但是并没有 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

var validator = require('validator'),
util = require('util'),
_ = require('underscore'),
crypto = require('crypto'),
x509 = require('x509'),
sha1 = require('sha1'),
wopenssl = require('wopenssl'),
config = require('./config'); //加载银联配置
//银联网关支付
var unionPay = {
//创建预订单
/*
* 参数 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) {
var errmsg;
var timeStamp = parms.timeStamp;
if(parms.payType==0) {
var back_notic_url = 'http://'+ config.domain +'/unionPay/productPay';
} else if(parms.payType==1) {
var back_notic_url = 'http://'+ config.domain +'/unionPay/rechargePay';
} else {
var back_notic_url = this.sdk_back_notic_url;
}
var formData = {
'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中返回
};
var privateKey;
var certId;
var cert;
SignKeyFromPfx(function(err , result){
if (err) {
errmsg = '证书签名失败';
} else { 
certId = result.certId;
privateKey = result.key;
formData.certId = certId;
if (formData.signature) {
delete formData.signature
}
//----签名开始----
//参数转变为签名串
var unionPay_parms = transForSign(formData);
//摘要
var unionPay_parms_sha1 = sha1(unionPay_parms);
//签名
var signer = crypto.createSign('RSA-SHA1');
signer.update(unionPay_parms_sha1);
var signature_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) {
var errmsg;
var formData = parms;
SignKeyFromPfx(function(err , result){
if (err) {
errmsg = '证书签名失败';
} else { 
certId = result.certId;
privateKey = result.key;
if (formData.signature) {
delete formData.signature
}
//----签名开始----
//参数转变为签名串
var unionPay_parms = transForSign(formData);
//摘要
var unionPay_parms_sha1 = sha1(unionPay_parms);
//签名
var signer = crypto.createSign('RSA-SHA1');
signer.update(unionPay_parms_sha1);
var signature_base64 = signer.sign(privateKey, 'base64');
//放入域中
formData.signature = signature_base64;
//console.log(formData);
if (errmsg) {
callback(errmsg);
} else {
callback(null,formData);
}
}
});
},
//验签
validate:
function(parms,callback) {
var validate_signature = parms.signature;
delete parms.signature;
var formData = parms;
ValidateKeyFromCer(formData,validate_signature,function(err , result){
if (err || !validate_signature || !formData) {
console.log('验签失败');
callback('验签失败');
} else { 
var publicKey = result.key;
if (formData.signature) {
delete formData.signature
}
//----验签开始----
var unionPay_parms = transForSign(formData);
var unionPay_parms_sha1 = sha1(unionPay_parms);
//console.log('待验证签:' + validate_signature);
var verifier = crypto.createVerify('RSA-SHA1');
//console.log('验证签名public key:\n' + publicKey);
//console.log('验证签名src_sign:' + unionPay_parms_sha1);
verifier.update(new Buffer(unionPay_parms_sha1, 'utf-8'));
var is_success = verifier.verify(publicKey, validate_signature, 'base64');
if (is_success) {
callback(null,formData);
} else {
console.log('验签不相等');
callback('验签不相等');
}
}
});
}
};
// 签名串算法--将参数排序,转成键值对格式字符串
function transForSign(params){
var array = []
for (var i in params) {
array.push('' + i + '=' + params[i])
}
var stringSignTemp = _.sortBy(array, function (str) {
return str;
});
return stringSignTemp.join('&');
};
//通过证书密码获得证书的rsa-privatekey值和证书Id
function SignKeyFromPfx(callback){
if (config.certsData) {
callback(null, config.certsData);
} else {
var certPath = config.sign_cert_path;
var certPwd = config.sign_cert_pwd;
var certDir = config.sign_cert_dir;
var p12 = wopenssl.pkcs12.extract(certPath, certPwd);
//console.log(p12.certificate); //p12.certificate和p12.rsa
var certs = wopenssl.x509.parseCert(p12.certificate);
//因为不知道怎么将十六进制证书id:certs.serial变成十进制证书id,因为这是个很大的整形biglong
var certsData = {};
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值
function ValidateKeyFromCer(formData, signature, callback){
if (config.validCertsData) {
callback(null, config.validCertsData);
} else {
var validateCertPath = config.validate_cert_path;
var certs = wopenssl.x509.parseCert(validateCertPath);
//console.log(certs);
var fs = require('fs');
var CERTIFICATE = fs.readFileSync(validateCertPath);
console.log(CERTIFICATE);
var publicKey = CERTIFICATE.toString('ascii');
var validCertsData = {};
validCertsData.key = publicKey;
validCertsData.cert = CERTIFICATE;
config.validCertsData = validCertsData;
if (publicKey) {
callback(null,validCertsData);
} else {
msg = '验签失败';
callback(msg);
}
}
};
//转化时间格式函数
function format(){
//时间格式化
var format = 'yyyyMMddhhmmss';
date = new Date();
var o = {
'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 (var k in o)
if (new RegExp('(' + k + ')').test(format))
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length));
return format;
};
module.exports = unionPay;

其实最重要的是签名还有验签部分,对证书 .pfx 和 .cer 的处理,其中的 createOrder 方法只是方便使用返回的请求表单内容。

在测试完成后,生产环境的配置还是不太一样。当银联商户申请成功后,银联会发一份邮件,上面有商户号,你的私钥证书 .pfx 需要你自己根据他的邮件提示去下载的,附件内的 “证书下载,安装” 文件有详细的说明教程。还有就是配置中的请求地址 https://101.231.204.80:5000需要换为 https://gateway.95516.com ,生产环境中除非是从商户提交审核的域名发起的请求,否则一律会报错 亲爱的用户,您本次交易可能存在风险.

以上所述是小编给大家介绍的NodeJS整合银联网关支付DEMO,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

NodeJs 相关文章推荐
NodeJS的模块写法入门(实例代码)
Mar 07 NodeJs
nodejs win7下安装方法
May 24 NodeJs
nodejs简单实现中英文翻译
May 04 NodeJs
NodeJS的Promise的用法解析
May 05 NodeJs
详解nodejs 文本操作模块-fs模块(二)
Dec 22 NodeJs
NodeJS实现视频转码的示例代码
Nov 18 NodeJs
nodejs实现简单的gulp打包
Dec 21 NodeJs
nodejs 生成和导出 word的实例代码
Jul 31 NodeJs
nodejs require js文件入口,在package.json中指定默认入口main方法
Oct 10 NodeJs
NodeJS搭建HTTP服务器的实现步骤
Oct 12 NodeJs
PHPStorm中如何对nodejs项目进行单元测试详解
Feb 28 NodeJs
如何让Nodejs支持H5 History模式(connect-history-api-fallback源码分析)
May 30 NodeJs
nodejs的HTML分析利器node-jquery用法浅析
Nov 08 #NodeJs
Jquery通过ajax请求NodeJS返回json数据实例
Nov 08 #NodeJs
NodeJS和BootStrap分页效果的实现代码
Nov 07 #NodeJs
NodeJS使用formidable实现文件上传
Oct 27 #NodeJs
nodejs简单实现操作arduino
Sep 25 #NodeJs
NodeJs读取JSON文件格式化时的注意事项
Sep 25 #NodeJs
nodejs微信公众号支付开发
Sep 19 #NodeJs
You might like
php+ajax实时输入自动搜索匹配的方法
2014/12/26 PHP
Laravel 5框架学习之向视图传送数据
2015/04/08 PHP
浅谈Laravel中的一个后期静态绑定
2017/08/11 PHP
php创建类并调用的实例方法
2019/09/25 PHP
javascript编程起步(第六课)
2007/02/27 Javascript
JavaScript监测ActiveX控件是否已经安装过的代码
2008/09/02 Javascript
javascript 常用代码技巧大收集
2009/02/25 Javascript
javascript CSS画图之基础篇
2009/07/29 Javascript
在html页面上拖放移动标签
2010/01/08 Javascript
js获取select默认选中的Option并不是当前选中值
2014/05/07 Javascript
把文本中的URL地址转换为可点击链接的JavaScript、PHP自定义函数
2014/07/29 Javascript
jQuery团购倒计时特效实现方法
2015/05/07 Javascript
jQuery数据检索中根据关键字快速定位GridView指定行的实现方法
2016/06/08 Javascript
jQuery实现checkbox列表的全选、反选功能
2016/11/24 Javascript
前端开发之CSS原理详解
2017/03/11 Javascript
vue.js之vue-cli脚手架的搭建详解
2017/05/05 Javascript
angular中ui calendar的一些使用心得(推荐)
2017/11/03 Javascript
对vue2.0中.vue文件页面跳转之.$router.push的用法详解
2018/08/24 Javascript
jquery获取img的src值实例介绍
2019/01/16 jQuery
layui实现checkbox的目录树tree的例子
2019/09/12 Javascript
微信小程序点击生成朋友圈分享图(遇到的坑)
2020/06/17 Javascript
javascript实现贪吃蛇小游戏
2020/07/28 Javascript
[00:14]护身甲盾
2019/03/06 DOTA
Python写的一个简单DNS服务器实例
2014/06/04 Python
简单的Python的curses库使用教程
2015/04/11 Python
Python实现列表转换成字典数据结构的方法
2016/03/11 Python
python 网络编程详解及简单实例
2017/04/25 Python
基于Linux系统中python matplotlib画图的中文显示问题的解决方法
2017/06/15 Python
pandas读取csv文件,分隔符参数sep的实例
2018/12/12 Python
Django Rest framework频率原理与限制
2019/07/26 Python
关于pytorch中全连接神经网络搭建两种模式详解
2020/01/14 Python
Pytorch数据拼接与拆分操作实现图解
2020/04/30 Python
信息系统专业个人求职信范文
2013/12/07 职场文书
编辑求职信样本
2013/12/16 职场文书
英文感谢信范文
2015/01/21 职场文书
php微信小程序解包过程实例详解
2021/03/31 PHP