diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e20c3ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +config.ini +log.txt +logs/log* diff --git a/aaa.js b/aaa.js new file mode 100644 index 0000000..752bfe3 --- /dev/null +++ b/aaa.js @@ -0,0 +1,159 @@ +var request = require('request'); +var strftime = require('strftime'); + +var max_retry = 10; +var sleep_before_retry = 3000; + +var config; +var partner; + +var available_products = []; + +function unaliasResponseCode(response_code, config_responsecode_alias) { + if (config_responsecode_alias == undefined && config && config.h2h_out && config.h2h_out.responsecode_alias) { + config_responsecode_alias = config.h2h_out.responsecode_alias; + } + + if (!config_responsecode_alias) { + return response_code; + } + + items = config_responsecode_alias.split(','); + items_count = items.length; + + for (var i=0; i < items_count; i++) { + codes = items[i].split(':'); + + if (codes.length <= 0) { continue; } + + if (response_code == codes[0]) { + console.log('Change response code from ' + codes[0] + ' to ' + codes[1]); + return codes[1]; + } + } + + return response_code; +} + +function pullCity() { + var url = config.globals.aaa_host + '/pull_city'; + console.log('Pull cities from AAA - ' + url); + request(url, function (error, response, body) { + if (!error && response.statusCode == 200) { + //console.log('city=' + body); + } else { + console.log('Error in pull city'); + } + }); +} + +function pullProduct() { + var url = config.globals.aaa_host + '/pull_product?opr_name=' + config.globals.operators; + console.log('Pull products from AAA - ' + url); + + request(url, function (error, response, body) { + if (error || response.statusCode != 200) { + console.log('Error in pull products'); + return; + } + + var productsAndOperators = body.split(';'); + var productsCount = productsAndOperators.length; + + for (var i=0; i < productsCount; i++) { + var product = productsAndOperators[i].split(',', 1)[0]; + available_products.push(product); + } + //console.log(available_products); + }); +} + +function pull() { + var url = config.globals.aaa_host + + '/pull?city=ALL&nom=' + config.globals.products + + '&chip_info=' + config.globals.gateway_name; + + //console.log('AAA PULL - ' + url); + request(url, function (error, response, body) { + if (!error && response.statusCode == 200) { + if (body == 'NONE') { + return; + } + console.log(body); + + var result = body.split(';'); + if (result[0] != 'OK') { + return; + } + + var task = []; + task['requestId'] = result[1]; + task['timestamp'] = result[3]; + task['destination'] = result[4]; + task['product'] = result[7]; + + if (config.products[task['product']] !== undefined) { + task['remoteProduct'] = config.products[task['product']]; + } else { + task['remoteProduct'] = task['product']; + } + + partner.topupRequest(task); + + } else { + console.log('Error in pull task'); + return; + } + }); +} + +function pullLoop() { + if (!config.globals.pause) { + pull(); + } + + setTimeout(pullLoop, config.globals.interval); +} + +function callbackReport(requestId, responseCode, message, retry) { + if (retry === undefined) { + retry = max_retry; + } + + responseCode = unaliasResponseCode(responseCode); + + timestamp = strftime('%Y%m%d%H%M%S'); + var url = config.globals.aaa_host + + '/topup?trans_id=' + requestId + + '&trans_date' + timestamp + + '&trans_date=' + timestamp + + '&resp_code=' + responseCode + + '&ussd_msg=' + config.globals.gateway_name + + '$' + encodeURIComponent(message); + + console.log('Report to AAA - ' + url); + request(url, function (error, response, body) { + if (error || response.statusCode != 200) { + console.log('Error report to AAA'); + + if (retry) { + console.log('Retrying to report to AAA (' + retry + ')'); + callbackReport(requestId, responseCode, message, retry - 1); + } + } + }); +} + +function start(_config, _partner) { + config = _config; + partner = _partner; + + pullCity(); + pullProduct(); + + setTimeout(pullLoop, 10 * 1000); +} + +exports.start = start; +exports.callbackReport = callbackReport; +exports.unaliasResponseCode = unaliasResponseCode; diff --git a/config.sample.ini b/config.sample.ini new file mode 100644 index 0000000..4fad175 --- /dev/null +++ b/config.sample.ini @@ -0,0 +1,18 @@ +[globals] +operators=TEST,XL_ALTERNATIF +products=TST1 +gateway_name=NODEJS-DEV +aaa_host=http://172.23.0.12:4250 +interval=1000 +admin_port=17456 + +[h2h_out] +partner=https://172.23.0.12:6789 +userid=R97DEV +password=czcb +listen_port=13522 +check_interval=2000 + +[products] +TST1=TR1 + diff --git a/httpserver.js b/httpserver.js new file mode 100644 index 0000000..d90ecc6 --- /dev/null +++ b/httpserver.js @@ -0,0 +1,90 @@ +var http = require('http'); +var nsr = require('node-simple-router'); +var router = nsr(); + +var config; +var httpServer; + +function start(_config) { + if (_config != undefined) { + config = _config; + } + listenPort = config.globals.admin_port; + + router.get("/info", function(request, response) { + response.setHeader("Content-Type", "text/plain"); + response.write('CHIPINFO / GATEWAY NAME: ' + config.globals.gateway_name + "\n"); + response.write('PRODUCTS: ' + config.globals.products + "\n"); + response.write('AAA HOST: ' + config.globals.aaa_host + "\n"); + response.write('PARTNER: ' + config.h2h_out.partner + "\n"); + response.write('PAUSED: ' + config.globals.pause + "\n"); + response.write('UPTIME: ' + process.uptime() + "\n"); + response.write('REQUESTS COUNT: ' + config.globals.requests_count + "\n"); + response.write('ACTIVE REQUESTS COUNT: ' + config.globals.active_requests_count + "\n"); + response.write('MAX ACTIVE REQUESTS COUNT: ' + config.globals.max_active_requests_count + "\n"); + + response.end(); + }); + + router.get("/pause/:apikey", function(request, response) { + if (!config.globals.apikey) { + response.end('Undefined APIKEY on config'); + return; + } + + if (request.params.apikey != config.globals.apikey) { + response.end('Invalid APIKEY'); + return; + } + + config.globals.pause = 1; + response.end('Paused'); + }); + + router.get("/resume/:apikey", function(request, response) { + if (!config.globals.apikey) { + response.end('Undefined APIKEY on config'); + return; + } + + if (request.params.apikey != config.globals.apikey) { + response.end('Invalid APIKEY'); + return; + } + + delete config.globals.pause; + response.end('Resume'); + }); + + router.get("/reset-stats/:apikey", function(request, response) { + if (!config.globals.apikey) { + response.end('Undefined APIKEY on config'); + return; + } + + if (request.params.apikey != config.globals.apikey) { + response.end('Invalid APIKEY'); + return; + } + + config.globals.max_active_requests_count = 0; + + response.writeHead(307, { + 'Location': '/info' + }); + + response.end(); + }); + + httpServer = http.createServer(router).listen(listenPort); + console.log('HTTP server listens on port ' + listenPort); + + return httpServer; +} + +function setConfig(_config) { + config = _config; +} + +exports.start = start; +exports.setConfig = setConfig; diff --git a/index.js b/index.js new file mode 100644 index 0000000..70985ec --- /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('./httpserver.js'); +var httpServer = HttpServer.start(config); + +var aaa = require('./aaa.js'); +var xmlout = require('./xmlout.js'); + +xmlout.start(config, aaa.callbackReport); +aaa.start(config, xmlout); diff --git a/logs/empty b/logs/empty new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json new file mode 100644 index 0000000..1f5e943 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "sate24-to-sate24", + "version": "0.0.1", + "description": "ST24 to ST24 H2H OUT", + "main": "index.js", + "scripts": { + "test": "mocha" + }, + "repository": { + "type": "git", + "url": "git@gitlab.kodesumber.com:reload97/sate24-to-sate24.git" + }, + "keywords": [ + "st24", + "reload97", + "ppob", + "h2h", + "m2m", + "xmlrpc" + ], + "author": "Adhidarma Hadiwinoto <gua@adhisimon.org>", + "license": "BSD", + "dependencies": { + "mocha": "~2.2.5", + "request": "~2.57.0", + "strftime": "~0.9.2", + "iniparser": "~1.0.5", + "mathjs": "~1.7.0", + "xmlrpc": "~1.3.1" + } +} diff --git a/test.js b/test.js new file mode 100644 index 0000000..7510e8e --- /dev/null +++ b/test.js @@ -0,0 +1,33 @@ +var assert = require("assert"); + + +describe('aaa', function() { + var aaa = require('./aaa'); + + describe("#unaliasResponseCode()", function() { + it('should return 68', function() { + assert.equal('68', aaa.unaliasResponseCode('01', '01:68')); + }); + + it('should return 68', function() { + assert.equal('68', aaa.unaliasResponseCode('68', '01:68')); + }); + + it('should return 00', function() { + assert.equal('00', aaa.unaliasResponseCode('00', '01:68')); + }); + + it('should return 40', function() { + assert.equal('40', aaa.unaliasResponseCode('40', '')); + }); + + it('should return 40', function() { + assert.equal('40', aaa.unaliasResponseCode('40', '')); + }); + + it('should return 40', function() { + assert.equal('40', aaa.unaliasResponseCode('40')); + }); + + }); +}); diff --git a/xmlout.js b/xmlout.js new file mode 100644 index 0000000..4534183 --- /dev/null +++ b/xmlout.js @@ -0,0 +1,222 @@ +var xmlrpc = require('xmlrpc'); +var url = require('url'); +var math = require('mathjs'); + +var config; +var callbackReport; + +var max_retry = 2; +var sleep_before_retry = 2000; + +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + +function topupRequest(task, retry) { + if (config.globals.requests_count == undefined) { + config.globals.requests_count = 1; + } else { + config.globals.requests_count++; + } + + if (config.globals.active_requests_count == undefined) { + config.globals.active_requests_count = 1; + } else { + config.globals.active_requests_count++; + } + + if (config.globals.max_active_requests_count == undefined) { + config.globals.max_active_requests_count = config.globals.active_requests_count; + } else { + config.globals.max_active_requests_count = math.max(config.globals.max_active_requests_count, config.globals.active_requests_count); + } + + + if (retry === undefined) { + retry = max_retry; + } + + var partnerUrl = url.parse(config.h2h_out.partner); + var clientOptions = { + host: partnerUrl.hostname + , port: partnerUrl.port + , path: partnerUrl.pathname + }; + console.log('XMLRPC client options:'); + console.log(clientOptions); + + var client; + if (partnerUrl.protocol == 'https:') { + console.log('Use SSL'); + client = xmlrpc.createSecureClient(clientOptions); + } else { + console.log('Not using SSL'); + client = xmlrpc.createClient(clientOptions); + } + + var methodName = 'topUpRequest'; + console.log('methodName: ' + methodName); + + var params = { + MSISDN: config.h2h_out.userid, + REQUESTID: task['requestId'], + PIN: config.h2h_out.password, + NOHP: task['destination'], + NOM: task['remoteProduct'] + }; + console.log('PARAMS:'); + console.log(params); + + + client.methodCall(methodName, [ params ], function (error, value) { + + if (config.globals.active_requests_count == undefined) { + config.globals.active_requests_count = 0; + } else { + config.globals.active_requests_count--; + } + + // Results of the method response + if (error) { + console.log('XMLRPC Client Error (' + task['requestId'] + '): '); + console.log(error); + + if (retry) { + + console.log('Retrying topUpRequest (' + retry + ')'); + setTimeout(function() { + topupRequest(task, retry - 1); + }, sleep_before_retry); + + } else { + callbackReport(task['requestId'], '40', 'Gangguan koneksi ke suplier'); + } + return; + } + + console.log('Method response for \'' + methodName + '\': ') + console.log(value); + + if (value['RESPONSECODE'] == '00' && config.h2h_out.parse_sn == 'YES') { + value['MESSAGE'] = 'SN=' + parseSN(value['MESSAGE']) + '; ' + value['MESSAGE']; + //console.log('Message with SN: ' + value['MESSAGE']); + } + + callbackReport(value['REQUESTID'], value['RESPONSECODE'], value['MESSAGE']); + }); +} + +function createServer() { + + console.log('Creating XML-RPC server on port ' + config.h2h_out.listen_port); + var serverOptions = { + port: config.h2h_out.listen_port + }; + + var server = xmlrpc.createServer(serverOptions); + + server.on('NotFound', function (method, params) { + console.log(method + ' is not found on our XML-RPC server'); + console.log('params:'); + console.log(params); + }); + + server.on('topUpReport', function (err, params, callback) { + console.log('RECEIVE topUpReport, params:'); + console.log(params); + + var paramscount = params.length; + for (var i = 0; i < paramscount; i++) { + var value = params[i]; + + if (value['RESPONSECODE'] == '00' && config.h2h_out.parse_sn == 'YES') { + value['MESSAGE'] = 'SN=' + parseSN(value['MESSAGE']) + '; ' + value['MESSAGE']; + //console.log('Message with SN: ' + value['MESSAGE']); + } + + callbackReport(value['REQUESTID'], value['RESPONSECODE'], value['MESSAGE']); + } + + callback(null, 'ACK REPORT OK'); + }) + +} + +function checkStatus(task) { + var partnerUrl = url.parse(config.h2h_out.partner); + var clientOptions = { + host: partnerUrl.hostname + , port: partnerUrl.port + , path: partnerUrl.pathname + }; + console.log('XMLRPC client options:'); + console.log(clientOptions); + + var client; + if (partnerUrl.protocol == 'https:') { + console.log('Use SSL'); + client = xmlrpc.createSecureClient(clientOptions); + } else { + console.log('Not using SSL'); + client = xmlrpc.createClient(clientOptions); + } + + var methodName = 'topUpInquiry'; + console.log('methodName: ' + methodName); + + var params = { + MSISDN: config.h2h_out.userid, + REQUESTID: task['requestId'], + PIN: config.h2h_out.password, + NOHP: task['destination'] + }; + console.log('PARAMS:'); + console.log(params); + + client.methodCall(methodName, [ params ], function (error, value) { + // Results of the method response + if (error) { + console.log('Error: '); + console.log(error); + return; + } + console.log('Method response for \'' + methodName + '\': ') + console.log(value); + + callbackReport(value['REQUESTID'], value['RESPONSECODE'], value['MESSAGE']); + }); +} + +function start(_config, _callbackReport) { + config = _config; + callbackReport = _callbackReport + + createServer(); +} + +function parseSN(message) { + var sn_regex = new RegExp(config.h2h_out.sn_pattern); + var sn_match = message.match(sn_regex); + + //console.log('SN MATCH:'); + //console.log(sn_match); + + if (sn_match <= 0) { + console.log('SN Not found: ' + message); + return ''; + } + + var sn = sn_match[0]; + var sn_remove_patterns = config.h2h_out.sn_remove_patterns.split(config.h2h_out.sn_remove_patterns_separator); + //console.log('SN REMOVE PATTERNS:'); + //console.log (sn_remove_patterns); + + var count = sn_remove_patterns.length; + + for(var i = 0; i < count; i++) { + sn = sn.replace(sn_remove_patterns[i], ''); + } + + return sn.trim(); +} + +exports.start = start; +exports.topupRequest = topupRequest;