Welcome

首页 / 脚本样式 / JavaScript / NodeJS整合银联网关支付(DEMO)

银联支付的测试开发做的很完善,可以下载各个语言的测试包,进行开发测试,但是并没有 nodejs 的,难点就是证书签名还有验签这两个步骤。
其实银联加密方式和支付宝微信不同的地方在于,使用了非对称加密,意思是为了在网络中传输安全,双方约定各自产生一个公钥还有私钥,私钥自己保存,公钥公开给对方(你要发送信息的人都知道)。当需要传输秘密的信息时候,用自己的私钥加密,发给对方,对方收到信息后,为了判定这个是否伪造(是不是确实从你这儿发送给他的),那么拿出你的公钥进行验证,发现是一样的,那么就可以确定这个确实是你发送的。这样做就可以保证信息的安全。
下面是 code :
银联配置文件:config.js
//配置银联支付需要的数据 - 这都是银联测试商户信息,可以上 https://merchant.unionpay.com/portal/login.jsp 去申请测试商户merId: "777290058136713", //商户idfont_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:
" + 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值和证书Idfunction 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.rsavar certs = wopenssl.x509.parseCert(p12.certificate);//因为不知道怎么将十六进制证书id:certs.serial变成十进制证书id,因为这是个很大的整形biglongvar certsData = {};certsData.certId = config.certId;certsData.key = p12.rsa;certsData.ca = certs;//存入configconfig.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,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!