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 相关文章推荐
14款NodeJS Web框架推荐
Jul 11 NodeJs
NodeJS学习笔记之Http模块
Jan 13 NodeJs
使用nodejs开发cli项目实例
Jun 03 NodeJs
NodeJS实现阿里大鱼短信通知发送
Jan 17 NodeJs
Nodejs中 npm常用命令详解
Jul 04 NodeJs
nodejs中向HTTP响应传送进程的输出
Mar 19 NodeJs
详解NODEJS的http实现
Jan 04 NodeJs
Nodejs 发布自己的npm包并制作成命令行工具的实例讲解
May 15 NodeJs
nodejs微信开发之授权登录+获取用户信息
Mar 17 NodeJs
NodeJs 实现简单WebSocket即时通讯的示例代码
Aug 05 NodeJs
nodejs使用node-xlsx生成excel的方法示例
Aug 22 NodeJs
nodejs中内置模块fs,path常见的用法说明
Nov 07 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_MySQL教程-第一天
2007/03/18 PHP
php实现过滤表单提交中html标签的方法
2014/10/17 PHP
PHP使用php-resque库配合Redis实现MQ消息队列的教程
2016/06/29 PHP
PHP实现页面静态化深入讲解
2021/03/04 PHP
3分钟写出来的Jquery版checkbox全选反选功能
2013/10/23 Javascript
Jquery实现简单的轮播效果(代码管用)
2016/03/14 Javascript
基于javascript实现样式清新图片轮播特效
2016/03/30 Javascript
jQuery实现简洁的轮播图效果实例
2016/09/07 Javascript
各式各样的导航条效果css3结合jquery代码实现
2016/09/17 Javascript
jQuery拖拽通过八个点改变div大小
2020/11/29 Javascript
每周一练 之 数据结构与算法(Stack)
2019/04/16 Javascript
使用vuex存储用户信息到localStorage的实例
2019/11/11 Javascript
详解template标签用法(含vue中的用法总结)
2021/01/12 Vue.js
Python字符串切片操作知识详解
2016/03/28 Python
python3获取当前文件的上一级目录实例
2018/04/26 Python
python 统计数组中元素出现次数并进行排序的实例
2018/07/02 Python
python初学者,用python实现基本的学生管理系统(python3)代码实例
2019/04/10 Python
PyQt5基本控件使用详解:单选按钮、复选框、下拉框
2019/08/05 Python
matplotlib绘制多个子图(subplot)的方法
2019/12/03 Python
python DataFrame转dict字典过程详解
2019/12/26 Python
浅析python函数式编程
2020/09/26 Python
美国第一香水网站:Perfume.com
2017/01/23 全球购物
Hello Molly美国:女性时尚在线
2019/08/26 全球购物
T3官网:头发造型工具
2019/12/26 全球购物
Yahoo-PHP面试题3
2012/01/14 面试题
2013年入党人员的自我鉴定
2013/10/25 职场文书
劳动竞赛活动总结
2014/05/05 职场文书
信用卡工作证明模板
2014/09/14 职场文书
党的群众路线教育实践活动党员个人剖析材料
2014/10/08 职场文书
高校教师个人工作总结2014
2014/12/17 职场文书
单位实习鉴定评语
2015/01/04 职场文书
课外活动实习计划
2015/01/19 职场文书
2015年技术员工作总结
2015/04/10 职场文书
《正比例》教学反思
2016/02/23 职场文书
民事调解协议书
2016/03/21 职场文书
浅谈Java实现分布式事务的三种方案
2021/06/11 Java/Android