NodeJS整合銀聯(lián)網(wǎng)關(guān)支付(DEMO)
銀聯(lián)支付的測試開發(fā)做的很完善,可以下載各個語言的測試包,進(jìn)行開發(fā)測試,但是并沒有 nodejs 的,難點(diǎn)就是證書簽名還有驗簽這兩個步驟。
其實銀聯(lián)加密方式和支付寶微信不同的地方在于,使用了非對稱加密,意思是為了在網(wǎng)絡(luò)中傳輸安全,雙方約定各自產(chǎn)生一個公鑰還有私鑰,私鑰自己保存,公鑰公開給對方(你要發(fā)送信息的人都知道)。當(dāng)需要傳輸秘密的信息時候,用自己的私鑰加密,發(fā)給對方,對方收到信息后,為了判定這個是否偽造(是不是確實從你這兒發(fā)送給他的),那么拿出你的公鑰進(jìn)行驗證,發(fā)現(xiàn)是一樣的,那么就可以確定這個確實是你發(fā)送的。這樣做就可以保證信息的安全。
下面是 code :
銀聯(lián)配置文件:config.js
//配置銀聯(lián)支付需要的數(shù)據(jù) - 這都是銀聯(lián)測試商戶信息,可以上 https://merchant.unionpay.com/portal/login.jsp 去申請測試商戶 merId: '777290058136713', //商戶id font_trans_url: 'https://101.231.204.80:5000/gateway/api/frontTransReq.do', //網(wǎng)關(guān)跳轉(zhuǎn)至銀聯(lián)平臺支付頁面地址 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', //驗簽用銀聯(lián)公鑰證書
.pfx 結(jié)尾的都是用密碼加密過后的私鑰
.cer 結(jié)尾的都是公鑰
銀聯(lián)支付模塊 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'); //加載銀聯(lián)配置 //銀聯(lián)網(wǎng)關(guān)支付 var unionPay = { //創(chuàng)建預(yù)訂單 /* * 參數(shù) parms: {out_trade_no: OUT_TRADE_NO, fee: FEE} * out_trade_no 商戶訂單號 fee 訂單金額,單位分 */ sdk_front_notice_url: 'http://'+ config.domain +'/unionPay/result', //銀聯(lián)網(wǎng)關(guān)支付前臺通知地址 sdk_back_notic_url: 'http://'+ config.domain +'/unionPay/productPay', //銀聯(lián)網(wǎng)關(guān)支付后臺通知地址 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', //業(yè)務(wù)類型 'frontUrl' : this.sdk_front_notice_url, //前臺通知地址 'signMethod' : '01', //簽名方法 'channelType' : '08', //渠道類型,07-PC,08-手機(jī) 'accessType' : '0', //接入類型 'currencyCode' : '156', //交易幣種,境內(nèi)商戶固定156 //TODO 以下信息需要填寫 'merId' : config.merId, //商戶代碼,請改自己的測試商戶號,此處默認(rèn)取demo演示頁面?zhèn)鬟f的參數(shù) 'orderId' : parms.out_trade_no, //商戶訂單號,8-32位數(shù)字字母,不能含“-”或“_”,此處默認(rèn)取demo演示頁面?zhèn)鬟f的參數(shù),可以自行定制規(guī)則 'txnTime' : timeStamp, //訂單發(fā)送時間timestamp,格式為YYYYMMDDhhmmss,取北京時間,此處默認(rèn)取demo演示頁面?zhèn)鬟f的參數(shù) '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 } //----簽名開始---- //參數(shù)轉(zhuǎn)變?yōu)楹灻? 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; //加入表單請求銀聯(lián)的地址 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 } //----簽名開始---- //參數(shù)轉(zhuǎn)變?yōu)楹灻? 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('驗簽不相等'); } } }); } }; // 簽名串算法--將參數(shù)排序,轉(zhuǎn)成鍵值對格式字符串 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); //因為不知道怎么將十六進(jìn)制證書id:certs.serial變成十進(jìn)制證書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); } } }; //轉(zhuǎn)化時間格式函數(shù) 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 方法只是方便使用返回的請求表單內(nèi)容。
在測試完成后,生產(chǎn)環(huán)境的配置還是不太一樣。當(dāng)銀聯(lián)商戶申請成功后,銀聯(lián)會發(fā)一份郵件,上面有商戶號,你的私鑰證書 .pfx 需要你自己根據(jù)他的郵件提示去下載的,附件內(nèi)的 “證書下載,安裝” 文件有詳細(xì)的說明教程。還有就是配置中的請求地址 https://101.231.204.80:5000需要換為 https://gateway.95516.com ,生產(chǎn)環(huán)境中除非是從商戶提交審核的域名發(fā)起的請求,否則一律會報錯 親愛的用戶,您本次交易可能存在風(fēng)險.
以上所述是小編給大家介紹的NodeJS整合銀聯(lián)網(wǎng)關(guān)支付DEMO,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
koa+mongoose實現(xiàn)簡單增刪改查接口的示例代碼
這篇文章主要介紹了koa+mongoose實現(xiàn)簡單增刪改查接口的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-05-05Node.js開發(fā)教程之基于OnceIO框架實現(xiàn)文件上傳和驗證功能
這篇文章主要介紹了Node.js開發(fā)教程之基于OnceIO框架實現(xiàn)文件上傳和驗證的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-11-11node.js中fs文件系統(tǒng)模塊的使用方法實例詳解
這篇文章主要介紹了node.js中fs文件系統(tǒng)模塊的使用方法,結(jié)合實例形式詳細(xì)分析了node.js fs文件系統(tǒng)模塊各種常見方法的基本使用技巧與相關(guān)操作注意事項,需要的朋友可以參考下2020-02-02