diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/index.js b/index.js new file mode 100644 index 0000000..dd057e2 --- /dev/null +++ b/index.js @@ -0,0 +1,13 @@ +var iniparser = require('iniparser'); +var config = iniparser.parseSync('./config.ini'); + +var aaaHost = config.globals.aaa_host; + +HttpServer = require('sate24/httpserver.js'); +var httpServer = HttpServer.start(config); + +var aaa = require('sate24/aaa.js'); +var partner = require('./partner-datacell.js'); + +partner.start(config, aaa.callbackReport); +aaa.start(config, partner); diff --git a/package.json b/package.json new file mode 100644 index 0000000..73c1b6a --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "sate24-to-datacell", + "version": "0.0.1", + "description": "ST24 to Datacell", + "main": "index.js", + "scripts": { + "test": "mocha" + }, + "repository": { + "type": "git", + "url": "git@gitlab.kodesumber.com:reload97/sate24-to-datacell.git" + }, + "keywords": [ + "datacell", + "ppob", + "st24" + ], + "author": "Adhidarma Hadiwinoto <gua@adhisimon.org>", + "license": "BSD", + "dependencies": { + "sate24": "git+http://git@gitlab.kodesumber.com/reload97/node-sate24.git", + "iniparser": "~1.0.5", + "base64-xor": "~0.10.0", + "request": "~2.60.0", + "mathjs": "~1.7.1", + "xml": "~1.0.0", + "xml2js": "~0.4.9", + "strftime": "~0.9.2" + } +} diff --git a/partner-datacell.js b/partner-datacell.js new file mode 100644 index 0000000..9c389f0 --- /dev/null +++ b/partner-datacell.js @@ -0,0 +1,328 @@ +var http = require('http'); +var url = require('url'); +var math = require('mathjs'); +var xml = require('xml'); +var xml2js = require('xml2js').parseString; +var strftime = require('strftime'); +var xor = require('base64-xor'); +var request = require('request'); + +var config; +var callbackReport; + +var max_retry = 2; +var sleep_before_retry = 2000; + +var trx_balances = {}; +var trx_prices = {}; + +function calculateSignature(userid, password, msisdn, timestamp) { + var a = msisdn.substr(msisdn.length - 4) + timestamp; + var b = userid.substr(0, 4) + password; + + return xor.encode(a,b); +} + +function calculateBalanceSignature(userid, password, timestamp) { + var a = '0000' + timestamp; + var b = userid.substr(0, 4) + password; + + return xor.encode(a,b); +} + + +function createPayload(task) { + var timestamp = strftime('%H%M%S'); + + var payload = { + datacell: [ + { perintah: 'charge'}, + {oprcode: task['remoteProduct']}, + {userid: config.h2h_out.userid}, + {time: timestamp}, + {msisdn: task['destination']}, + {ref_trxid: task['requestId']}, + {sgn: calculateSignature(config.h2h_out.userid, config.h2h_out.password, task['destination'], timestamp)} + ] + }; + + console.log(payload); + return "<?xml version=\"1.0\" ?>\n" + xml(payload); +} + +function topupRequest(task, retry) { + //balanceCheck(); + + var payload_xml = createPayload(task); + //console.log(payload_xml); + + var postRequest = { + host: "202.152.62.2", + path: "/RELOAD97.php", + port: 7713, + method: "POST", + headers: { + 'Content-Type': 'text/xml', + 'Content-Length': Buffer.byteLength(payload_xml) + } + }; + + var buffer = ""; + var req = http.request( postRequest, function( res ) { + + console.log('Status code: ' + res.statusCode ); + var buffer = ""; + res.on( "data", function( data ) { buffer = buffer + data; } ); + res.on( "end", function( data ) { + topupResponseHandler(buffer); + }); + + }); + + req.on('error', function(e) { + console.log('problem with request: ' + e.message); + callbackReport(task['requestId'], '40', e.message); + }); + + req.write( payload_xml ); + req.end(); +} + +function topupResponseHandler(body, request_id) { + xml2js(body, function (err, result) { + if (err) { + console.log(body); + callbackReport(request_id, '40', buffer); + return; + } + + console.log(result); + + request_id = result.datacell.ref_trxid[0].trim(); + + var response_code = '68'; + + var message = ''; + try { + if (result.datacell.message && result.datacell.message.length > 0) { + message = result.datacell.message[0].trim(); + } else if (result.datacell.msg && result.datacell.msg.length > 0) { + message = result.datacell.msg[0].trim(); + } + } + catch(err) { + message = 'exception saat parsing message'; + } + + if (result.datacell.resultcode && result.datacell.resultcode[0] == '999') { + response_code = '40'; + } + + if (message.indexOf('Nomor tujuan salah') >= 0) { + response_code = '14'; + } else if (message.indexOf('*GAGAL, transaksi yang sama sudah ada dalam 10 menit') >= 0) { + response_code = '55'; + } else if (message.indexOf('saldo sdh dikembalikan') >= 0) { + response_code = '40' + } else if (message.indexOf('Trx dpt diulang') >= 0) { + response_code = '40' + } else if (message.indexOf('SUKSES SN Operator:') >= 0) { + response_code = '00'; + + var sn = parseSN(message); + console.log ('SN Operator: ' + sn); + + /* + if (!sn) { + + console.log('Missing real operator SN, using SN from suplier'); + try { + sn = result.datacell.trxid[0].trim(); + } + catch(err) { + sn = ''; + } + } + */ + + if (sn) { + message = 'SN=' + sn + '; ' + message; + } else { + message = 'SN belum didapat. ' + message; + response_code = '68'; + } + } + + + var price = priceFromMessage(message); + if (price != null) { + console.log('Harga: ' + price); + trx_prices[request_id] = price; + setTimeout(deleteTrxPrice, 3 * 24 * 3600 * 1000, request_id); + } else if (response_code == '00' && trx_prices[request_id] !== undefined) { + price = trx_prices[request_id]; + console.log('Harga: ' + price); + message = message + ' -- Harga: ' + price; + } + + var balance = balanceFromMessage(message); + if (balance != null) { + console.log('Saldo: ' + balance); + trx_balances[request_id] = balance; + setTimeout(deleteTrxBalance, 3 * 24 * 3600 * 1000, request_id); + } else if (response_code == '00' && trx_balances[request_id] !== undefined) { + balance = trx_balances[request_id]; + console.log('Saldo: ' + balance); + message = message + ' -- Saldo: ' + balance; + } + + callbackReport(request_id, response_code, message); + }); +} + +function deleteTrxPrice(request_id) { + delete trx_prices[request_id]; +} + +function deleteTrxBalance(request_id) { + delete trx_balances[request_id]; +} + +function parseSN(message) { + var results = message.match(/SN Operator: .+ SN Kami/); + if (!results || results.length <= 0) { + return ''; + } + + var result = results[0]; + result = result.replace('SN Operator:', ''); + result = result.replace('SN Kami', ''); + result = result.trim(); + + if (result == '00') { + result = ''; + } + + return result; +} + +function createServer() { + + var httpServer = http.createServer(function(req, res) { + var parsed_url = url.parse(req.url, true, true); + + console.log('Got request from partner ("' + req.url + '")'); + + var body = ""; + req.on('data', function (chunk) { + body += chunk; + }); + + req.on('end', function () { + res.writeHead(200); + res.end('OK'); + + //console.log(body); + + if (parsed_url.pathname == '/sn') { + console.log('Reverse report -- SN'); + topupResponseHandler(body); + + } else if (parsed_url.pathname = '/refund') { + console.log('Reverse report -- REFUND'); + callbackReport(parsed_url.query.ref_trxid, '40', parsed_url.query.message); + + } else { + console.log('Reverse report -- UNKNOWN'); + console.log('Ignore unknown request on reverse url'); + } + }); + }); + + httpServer.listen(config.h2h_out.listen_port, function() { + console.log('HTTP Reverse/Report server listen on port ' + config.h2h_out.listen_port); + }); +} + +function balanceCheck() { + var timestamp = strftime('%H%M%S'); + + var payload = { + datacell: [ + {perintah: 'saldo'}, + {userid: config.h2h_out.userid}, + {time: timestamp}, + {sgn: calculateBalanceSignature(config.h2h_out.userid, config.h2h_out.password, timestamp)} + ] + }; + + var postRequest = { + host: "202.152.62.2", + path: "/RELOAD97.php", + port: 7713, + method: "POST", + headers: { + 'Content-Type': 'text/xml', + 'Content-Length': Buffer.byteLength(payload_xml) + } + }; + + var buffer = ""; + var req = http.request( postRequest, function( res ) { + + console.log('Status code: ' + res.statusCode ); + var buffer = ""; + res.on( "data", function( data ) { buffer = buffer + data; } ); + res.on( "end", function( data ) { + console.log('CHECK BALANCE RESULT:'); + console.log(buffer); + }); + + }); + + req.on('error', function(e) { + console.log('problem with request: ' + e.message); + }); + + req.write( payload_xml ); + req.end(); + +} + +function balanceFromMessage(message) { + var matches = message.match(/Saldo: Rp (\d+)/); + + if (!matches) { + return null; + } + if (matches.length < 2) { + return null; + } + + return matches[1]; +} + +function priceFromMessage(message) { + var matches = message.match(/Harga: (\d+)/); + + if (!matches) { + return null; + } + if (matches.length < 2) { + return null; + } + + return matches[1]; +} + +function start(_config, _callbackReport) { + config = _config; + callbackReport = _callbackReport + + createServer(); +} + +exports.start = start; +exports.topupRequest = topupRequest; +exports.balanceFromMessage = balanceFromMessage; +exports.priceFromMessage = priceFromMessage; diff --git a/test.js b/test.js new file mode 100644 index 0000000..00ee8f9 --- /dev/null +++ b/test.js @@ -0,0 +1,49 @@ +var assert = require("assert"); + +describe('partner-datacell', function() { + var partner = require('./partner-datacell'); + + describe('#balanceFromMessage', function() { + var message; + + it('should return 8306874', function() { + assert.equal(8306874, partner.balanceFromMessage('IR25 No: 085697273881 sudah diterima dan sdg diproses. SN Kami :243588112. Harga: 24750. Saldo: Rp 8306874.')); + }); + + it('should return 8351574', function() { + assert.equal(8351574, partner.balanceFromMessage('TEL20 No: 085372113774 sudah diterima dan sdg diproses. SN Kami :243586975. Harga: 19950. Saldo: Rp 8351574.')); + }); + + it('should return 0', function() { + assert.equal(0, partner.balanceFromMessage('TEL20 No: 085372113774 sudah diterima dan sdg diproses. SN Kami :243586975. Harga: 19950. Saldo: Rp 0.')); + }); + + it('should return null', function() { + assert.equal(null, partner.balanceFromMessage('XL25 No: 08174945541 SUKSES SN Operator: 970729963933 SN Kami: 243591297.')); + }); + }); + + describe('#priceFromMessage', function() { + var message; + + it('should return 24750', function() { + assert.equal(24750, partner.priceFromMessage('IR25 No: 085697273881 sudah diterima dan sdg diproses. SN Kami :243588112. Harga: 24750. Saldo: Rp 8306874.')); + }); + + it('should return 19950', function() { + assert.equal(19950, partner.priceFromMessage('TEL20 No: 085372113774 sudah diterima dan sdg diproses. SN Kami :243586975. Harga: 19950. Saldo: Rp 8351574.')); + }); + + it('should return 0', function() { + assert.equal(0, partner.priceFromMessage('TEL20 No: 085372113774 sudah diterima dan sdg diproses. SN Kami :243586975. Harga: 0. Saldo: Rp 8351574.')); + }); + + it('should return null', function() { + assert.equal(null, partner.priceFromMessage('XL25 No: 08174945541 SUKSES SN Operator: 970729963933 SN Kami: 243591297.')); + }); + }); + + + + +});