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(一)--- Node.js简介及安装开发环境
May 20 NodeJs
nodejs文件操作模块FS(File System)常用函数简明总结
Jun 05 NodeJs
轻松创建nodejs服务器(5):事件处理程序
Dec 18 NodeJs
nodejs实现获取某宝商品分类
May 28 NodeJs
使用Angular和Nodejs、socket.io搭建聊天室及多人聊天室
Aug 21 NodeJs
Nodejs进阶:基于express+multer的文件上传实例
Nov 21 NodeJs
详解nodejs微信公众号开发——5.素材管理接口
Apr 11 NodeJs
Nodejs实现多房间简易聊天室功能
Jun 20 NodeJs
nodejs密码加密中生成随机数的实例代码
Jul 17 NodeJs
Mac 安装 nodejs方法(图文详细步骤)
Oct 30 NodeJs
Nodejs处理异常操作示例
Dec 25 NodeJs
Nodejs中怎么实现函数的串行执行
Mar 02 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可逆加密/解密函数分享
2012/09/25 PHP
脚本吧 - 幻宇工作室用到js,超强推荐base.js
2006/12/23 Javascript
jQuery使用手册之一
2007/03/24 Javascript
js利用Array.splice实现Array的insert/remove
2009/01/13 Javascript
用js小类库获取浏览器的高度和宽度信息
2012/01/15 Javascript
JQuery实现简单的图片滑动切换特效
2015/11/22 Javascript
基于jquery实现页面滚动到底自动加载数据的功能
2015/12/19 Javascript
轻松搞定jQuery.noConflict()
2016/02/15 Javascript
解决jquery无法找到其他父级子集问题的方法
2016/05/10 Javascript
JS随机数产生代码分享
2018/02/24 Javascript
使用pkg打包Node.js应用的方法步骤
2018/10/19 Javascript
jQuery的ztree仿windows文件新建和拖拽功能的实现代码
2018/12/05 jQuery
JavaScript中concat复制数组方法浅析
2019/01/20 Javascript
NProgress显示顶部进度条效果及使用详解
2019/09/21 Javascript
深入理解webpack process.env.NODE_ENV配置
2020/02/23 Javascript
Vue使用CDN引用项目组件,减少项目体积的步骤
2020/10/30 Javascript
Python使用django框架实现多人在线匿名聊天的小程序
2017/11/29 Python
Python爬取数据并写入MySQL数据库的实例
2018/06/21 Python
利用pandas进行大文件计数处理的方法
2018/07/25 Python
详解python tkinter教程-事件绑定
2019/03/28 Python
使用Python制作简单的小程序IP查看器功能
2019/04/16 Python
Python 字典一个键对应多个值的方法
2020/09/29 Python
英国袜子店:Sock Shop
2017/01/11 全球购物
MIXIT官网:俄罗斯最大的化妆品公司之一
2020/01/25 全球购物
Java中的基本数据类型所占存储空间大小固定的吗
2012/02/15 面试题
行政人事经理职位说明书
2014/03/05 职场文书
优秀员工演讲稿
2014/05/19 职场文书
债务追讨授权委托书范本
2014/10/16 职场文书
2014年话务员工作总结
2014/11/19 职场文书
集结号观后感
2015/06/08 职场文书
员工手册董事长致辞
2015/07/29 职场文书
幼儿园园长新年寄语
2015/08/17 职场文书
乡镇团代会开幕词
2016/03/04 职场文书
车辆挂靠协议书
2016/03/23 职场文书
php双向队列实例讲解
2021/11/17 PHP
python pandas 解析(读取、写入)CSV 文件的操作方法
2022/12/24 Python