var xmlrpc = require('xmlrpc'); var url = require('url'); var math = require('mathjs'); var winston = require('winston'); var redis = require('redis'); var resendDelay = require('sate24/resend-delay.js'); var LRU = require('lru-cache'); var aaa; var logger; var config; var _callbackReport; var redisClient; var taskHistory = LRU({max: 500, maxAge: 1000 * 3600 * 2}); process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; function callbackReport(requestId, responseCode, message, dontResendDelay, raw) { var responseToSave = { parsed: { MESSAGE: message, } }; if (raw) { responseToSave.raw = raw; } if (responseCode != '68' || dontResendDelay) { resendDelay.cancel(requestId); } else { getTaskFromHistory(requestId, function(err, archivedTask) { if (archivedTask) { logger.verbose('DEBUG', {archivedTask: archivedTask}); resendDelay.register(archivedTask); } }); } _callbackReport(requestId, responseCode, message, null, responseToSave); } function createRedisClient(host, port) { if (!host && !port) { logger.info('Not creating redis client because unspecified host or port'); return; } try { redisClient = redis.createClient(port, host); } catch(err) { logger.warn("Error creating redis client to " + host + ':' + port); } } function topupRequest(task) { aaa.insertTaskToMongoDb(task); getTaskFromHistory(task, function(err, archivedTask) { putTaskToHistory(task); if (archivedTask) { logger.info('Task has been executed before, going to checkStatus', {task: task, archivedTask: archivedTask}); checkStatus(task); } else { _topupRequest(task); } }); } function _topupRequest(task, pendingOnConnectError) { if (!aaa.isTodayTrx(task)) { callbackReport(task.requestId, '68', 'Terdeteksi transaksi beda hari, batal kirim ke supplier. Silahkan cek webreport', true); return; } var partnerUrl = url.parse(config.h2h_out.partner); var clientOptions = { host: partnerUrl.hostname, port: partnerUrl.port, path: partnerUrl.pathname }; var client; if (partnerUrl.protocol == 'https:') { client = xmlrpc.createSecureClient(clientOptions); } else { client = xmlrpc.createClient(clientOptions); } var params = { MSISDN: config.h2h_out.userid, REQUESTID: task['requestId'], PIN: config.h2h_out.password, NOHP: task['destination'], NOM: task['remoteProduct'] }; var methodName = 'topUpRequest'; logger.info('Preparing XMLRPC request', {methodname: methodName, params: params, partnerUrl: partnerUrl.href}); client.methodCall(methodName, [ params ], function (error, value) { // Results of the method response if (error && !pendingOnConnectError && (error.code == 'ECONNREFUSED' || error.code == 'EHOSTUNREACH')) { logger.warn('XMLRPC Client Error on connecting', {requestId: task['requestId'], err: error}); callbackReport(task['requestId'], '91', 'Gangguan koneksi ke suplier: ' + error); return; } else if (error) { logger.warn('XMLRPC Client Error', {requestId: task['requestId'], err: error}); callbackReport(task['requestId'], '68', 'XMLRPC Client Error: ' + error); return; } logger.info('Got XMLRPC response from partner for', {response_method: methodName, response_message: value}); if (value['RESPONSECODE'] == '94') { logger.info('Change RC 94 to 68'); value['RESPONSECODE'] = '68'; } if (value['RESPONSECODE'] == '00' && value['SN'] && value['SN'].trim()) { value['MESSAGE'] = 'SN=' + value['SN'].trim() + '; ' + value['MESSAGE']; } else if (value['RESPONSECODE'] == '00' && config.h2h_out.parse_sn == 'YES') { value['MESSAGE'] = 'SN=' + parseSN(value['MESSAGE']) + '; ' + value['MESSAGE']; } callbackReport(value['REQUESTID'], value['RESPONSECODE'], value['MESSAGE']); }); } function createServer() { logger.info('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) { logger.warn('Unknown method recevied on XMLRPC server', {xmlrpc_method: method, xmlrpc_params: params}); }); server.on('topUpReport', function (err, params, callback) { logger.info('Got XMLRPC topUpReport request from partner', {xmlrpc_method: 'topUpReport', xmlrpc_params: params}); var paramscount = params.length; for (var i = 0; i < paramscount; i++) { var value = params[i]; if (value['RESPONSECODE'] == '94') { logger.info('Change RC 94 to 68'); value['RESPONSECODE'] = '68'; } if (value['RESPONSECODE'] == '00' && config.h2h_out.parse_sn == 'YES') { value['MESSAGE'] = 'SN=' + parseSN(value['MESSAGE']) + '; ' + value['MESSAGE']; } callbackReport(value['REQUESTID'], value['RESPONSECODE'], value['MESSAGE']); } callback(null, 'ACK REPORT OK'); }) } function getBalanceFromMessage(message, balance_regex) { if (!balance_regex) { if (config && config.globals && config.globals.balance_regex) { balance_regex = config.globals.balance_regex; } } if (!balance_regex) { return; } try { var re = new RegExp(balance_regex); var matches = message.match(re); var result = matches[1]; result = result.replace(/\./g, ''); result = result.replace(/,/g, ''); return Number(result); } catch(err) { return; } } function updateBalance(message) { var balance = getBalanceFromMessage(message); if (balance) { logger.info('Balance: ' + balance); aaa.updateBalance(balance); } } function checkStatus(task) { if (Number(config.globals.topup_request_on_check_status)) { _topupRequest(task, true); return; } var partnerUrl = url.parse(config.h2h_out.partner); var clientOptions = { host: partnerUrl.hostname , port: partnerUrl.port , path: partnerUrl.pathname }; logger.info('XMLRPC client options:'); logger.info(clientOptions); var client; if (partnerUrl.protocol == 'https:') { client = xmlrpc.createSecureClient(clientOptions); } else { client = xmlrpc.createClient(clientOptions); } var methodName = 'topUpInquiry'; var params = { REQUESTID: task['requestId'], MSISDN: config.h2h_out.userid, PIN: config.h2h_out.password, NOHP: task['destination'] }; logger.info('Requesting topUpInquiry', {params: params}); client.methodCall(methodName, [ params ], function (error, value) { // Results of the method response if (error) { logger.warn('Error requesting topUpInquiry: ', {err: error, params: params}); callbackReport(task.requestId, '68', 'Error requesting topUpInquiry: ' + error); return; } logger.info('Method response for \'' + methodName, {response: value}); callbackReport(task.requestId, value['RESPONSECODE'], value['MESSAGE']); }); } function start(options) { if (!options) { console.log('Undefined options, terminating....'); process.exit(1); } if (options.config) { config = options.config; } else { console.log('Undefined options.config, terminating....') process.exit(1); } if (options.aaa) { aaa = options.aaa; _callbackReport = options.aaa.callbackReportWithPushToMongoDb; } else { console.log('Undefined options.aaa, terminating....') process.exit(1); } if (options && options.logger) { logger = options.logger; } else { logger = new winston.Logger({ transports: [ new (winston.transports.Console)() ] }); } createRedisClient(config.globals.redis_host, config.globals.redis_port); createServer(); var resendDelayOptions = { config: config, topupRequest: checkStatus, logger: logger }; if (Number(config.globals.topup_request_on_resend_delay)) { resendDelayOptions.topupRequest = topupRequest; } resendDelay.init(resendDelayOptions); } function parseSN(message, _config) { if (!_config) { _config = config; } var sn_regex = new RegExp(_config.h2h_out.sn_pattern); var sn_match = message.match(sn_regex); if (sn_match <= 0) { logger.info('SN Not found: ' + message); return ''; } var match_index = 0; if (_config.h2h_out.sn_match_index) { match_index = Number(_config.h2h_out.sn_match_index); } var sn = sn_match[match_index]; if (_config.h2h_out.sn_remove_whitespace) { sn = sn.replace(/\s/g, ''); } var sn_remove_patterns = []; if (_config.h2h_out.sn_remove_patterns && _config.h2h_out.sn_remove_patterns_separator) { sn_remove_patterns = _config.h2h_out.sn_remove_patterns.split(_config.h2h_out.sn_remove_patterns_separator); } var count = sn_remove_patterns.length; for(var i = 0; i < count; i++) { //sn = sn.replace(sn_remove_patterns[i], ''); var re = new RegExp(sn_remove_patterns[i], 'g'); sn = sn.replace(re, ''); } //sn = paddingSN(sn, _config); return sn.trim(); } function getTaskKey(task, chipInfo) { var requestId; if (typeof task === 'string') { requestId = task; } else { try { requestId = task.requestId; } catch(e) { logger.warn('Something wrong', {task: task}); console.trace('Cekidot'); process.exit(1); } } if (!chipInfo && config && config.globals && config.globals.gateway_name) { chipInfo = config.globals.gateway_name; } return chipInfo + '.hitachi.rid:' + requestId; } function putTaskToHistory(task, cb) { if (Number(config.globals.no_dupe_check)) { if (cb) { cb(); } return; } var key = getTaskKey(task, config.globals.gateway_name); logger.verbose('Saving task to history LRU', {key: key, task: task}); try { taskHistory.set(key, JSON.parse(JSON.stringify(task))); } catch (e) { } putTaskToRedis(task, cb); } function putTaskToRedis(task, cb) { if (!redisClient) { logger.verbose('Not saving to redis because of undefined redisClient') if (cb) { cb(); } return; } var key = getTaskKey(task, config.globals.gateway_name); logger.verbose('Saving task to redis', {key: key, task: task}); redisClient.set(key, JSON.stringify(task), function() { redisClient.expire(key, 3600*24*30); if (cb) { cb(); } }); } function getTaskFromHistory(task, cb) { logger.verbose('Getting task from history', {task: task}); var key = getTaskKey(task, config.globals.gateway_name); var archive = taskHistory.get(key); if (archive) { if (cb) { cb(null, archive); } } else { getTaskFromRedis(task, cb); } } function getTaskFromRedis(task, cb) { if (!redisClient) { if (cb) { cb(null, null); } return; } var key = getTaskKey(task, config.globals.gateway_name); redisClient.get(key, function(err, result) { if (err) { logger.warn('Error retrieving task from redis', {err: err}); cb(err, null); return; } var task; try { task = JSON.parse(result); } catch(e) { logger.warn('Exception on parsing redis result as a json', {err: e}); } cb(null, task); }) } exports.start = start; exports.topupRequest = topupRequest; exports.getBalanceFromMessage = getBalanceFromMessage; exports.checkStatus = checkStatus;