var fs = require('fs'); var https = require('https'); var http = require('http'); var url = require('url'); var request = require('request'); var xml2js = require('xml2js').parseString; var strftime = require('strftime'); var math = require('mathjs'); var winston = require('winston'); //var cekstatus = require('./cekstatus.js'); var mongoClient = require('mongodb').MongoClient; var LRU = require('lru-cache'); var reverseParser = require('./reverse-parser'); var config; var httpServer; var aaa; var logger; var callbackReport; var mongodb; var tasks = LRU(10000); http.globalAgent.maxSockets = Infinity; process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; var sleep_before_retry = 30000; var pendingResultCode = ['0005', '0012', '0068', '0090', '0063', '0018', '0096']; var logTag = __filename.split('/').reverse()[0]; function initMongoClient() { if (!config.mongodbstruk || !config.mongodbstruk.url) { return; } try { var url = config.mongodbstruk.url; mongoClient.connect(url, function(err, db) { if (err) { logger.warn('Failed to connect to mongodb', {err: err}); return; } mongodb = db; logger.info('MongoDB connected'); }); } catch(err) { logger.warn('Exception when connecting to mongodb', {err: err, url: url}); } } function prepareResultData(result) { var task; var data = {}; data.gateway = config.globals.gateway_name; try { data.requestId = result.reffid[0].trim(); var key = config.globals.gateway_name + '.rid:' + data.requestId; task = tasks.get(key); } catch(err) { data.requestId = null; } try { data.status = result.ResultCode[0].trim(); } catch(err) { data.status = '68' } try { data.rcmessage = result.ErrorMsg[0].trim(); } catch(err) { data.rcmessage = ''; } try { data.resptext = ''; } catch(err) { data.resptext = ''; } try { var ts = moment(task.timestamp, 'YYYYMMDDHHmmss').format('YYYY-MM-DD HH:mm:ss') data.dt = ts; } catch(err) { logger.warn('Exception when getting timestamp data, using current timestamp', {err: err, result: result, task: task}); data.dt = strftime('%Y-%m-%d %H:%M:%S', new Date()); } try { data.namapel = result.nama_pel[0].trim(); } catch(err) { data.namapel = 'UNKNOWN'; } try { data.msn = result.nsm[0].trim(); } catch(err) { data.msn = 'UNKNOWN'; } try { data.idpel = result.idpel[0].trim(); } catch(err) { data.idpel = 'UNKNOWN'; } try { data.tarifdaya = result.tarif[0].trim(); } catch(err) { data.tarifdaya = 'UNKNOWN'; } try { data.admin = parseInt(result.adminfee[0].trim()); } catch(err) { data.admin = 0; } try { data.adm = parseInt(result.admin_fee[0].trim()); } catch(err) { data.adm = 0; } try { data.hargapelanggan = parseInt(result.amount_trx[0].trim()) - data.adm; } catch(err) { data.hargapelanggan = 0; } try { data.jumlahkwh = result.jml_daya[0].trim(); } catch(err) { data.jumlahkwh = 0; } try { data.token = result.token[0].trim(); } catch(err) { data.token = 0; } try { data.ppn = result.ppn_fee[0].trim(); } catch(err) { data.ppn_fee = 0; } try { data.ppj = result.ppj_fee[0].trim(); } catch(err) { data.ppj = 0; } try { data.angsuran = result.angsuran_fee[0].trim(); } catch(err) { data.angsuran = 0; } try { data.meterai = result.materai_fee[0].trim(); } catch(err) { data.materai_fee = 0; } return data; } function saveTokenToMongoDb(result) { if (!mongodb) { return; } if (!config.mongodbstruk) { return; } if (!config.mongodbstruk.collection) { return; } var data = prepareResultData(result); try { mongodb.collection(config.mongodbstruk.collection).insertOne(data); } catch(err) { logger.warn('Error when inserting data to mongodb', {err: err, data: data}); } } function putTaskToCache(task) { var key = config.globals.gateway_name + '.rid:' + task.requestId; try { tasks.set(key, task); } catch(err) { logger.warn('Error writing to task to cache', {err: err, key: key, task: task}); } } function topupRequest(task) { aaa.insertTaskToMongoDb(task); putTaskToCache(task); var ts = strftime('%Y%m%d%H%M%S', new Date()); var data = config.h2h_out.userid + '|' + config.h2h_out.password + '|' + task['remoteProduct'] + '|' + task['destination'] + '|0'; var options = { url: config.h2h_out.partner, qs: { ts: ts, data: data, reffid: task['requestId'] } }; logger.info('Creating http request', {options: options}); request(options, function (error, response, body) { var responseCode = '40'; var responseMessage = 'Gateway Error'; if (error) { logger.warn('HTTP REQUEST ERROR', error); callbackReport(task['requestId'], '89', 'HTTP REQUEST ERROR (' + error + ')'); } else if (response.statusCode != 200) { var error_message = 'GATEWAY ERROR (HTTP RESPONSE CODE: ' + response.statusCode + ')'; logger.warn(error_message); callbackReport(task['requestId'], '91', error_message); } else { logger.info('DIRECT RESPONSE', {requestId: task.requestId, body: body}); xml2js(body, function (err, result) { if (err) { logger.warn('Error parsing xml response', {requestId: task.requestId, err: err, body: body}); callbackReport(task['requestId'], '40', body); } else { var directResponse = result; logger.verbose('DIRECT RESPONSE from partner parsed', {directResponse: directResponse}); saveTokenToMongoDb(directResponse.Result); try { var result_price; try { result_price = directResponse.Result.Price[0].trim(); } catch(err) { result_price = 0; } var result_error_message; try { result_error_message = directResponse.Result.ErrorMsg[0].trim(); } catch(err) { result_error_message = ''; } var resultCode = directResponse.Result.ResultCode[0].trim(); responseMessage = 'ResultCode: ' + resultCode + ' | ErrorMsg: ' + result_error_message + ' | DateTime: ' + directResponse.Result.DateTime[0].trim() + ' | nsm: ' + directResponse.Result.nsm[0].trim() + ' | idpel: ' + directResponse.Result.idpel[0].trim() + ' | reffid: ' + directResponse.Result.reffid[0].trim() + ' | TransID: ' + directResponse.Result.TransID[0].trim() + ' | reff_switching: ' + directResponse.Result.reff_switching[0].trim() + ' | amount_trx: ' + directResponse.Result.amount_trx[0].trim() + ' | token: ' + directResponse.Result.token[0].trim() + ' | PrevBalance: ' + directResponse.Result.PrevBalance[0].trim() + ' | Price: ' + result_price + ' | EndBalance: ' + directResponse.Result.EndBalance[0].trim() ; logger.info('Response message: ' + responseMessage); if ( (resultCode == '0099') && (result_error_message.search(/METER .* YANG ANDA MASUKAN SALAH/) >= 0) ) { callbackReport(task.requestId, '14', responseMessage); return; } else if ( (resultCode == '0099') && (result_error_message.search(/IDPEL .* YANG ANDA MASUKAN SALAH/) >= 0) ) { callbackReport(task.requestId, '14', responseMessage); return; } else if ( (resultCode == '0099') && (result_error_message.search(/KONSUMEN .* DIBLOKIR HUBUNGI PLN/) >= 0) ) { callbackReport(task.requestId, '77', responseMessage); return; } else if ( (resultCode == '0099') && (result_error_message.search(/TOTAL KWH MELEBIHI BATAS MAKSIMUM/) >= 0) ) { callbackReport(task.requestId, '47', responseMessage); return; } else if ( (resultCode == '0099') && (result_error_message.search(/INQUIRY TIMEOUT, SILAHKAN DICOBA KEMBALI/) >= 0) ) { callbackReport(task.requestId, '91', responseMessage); return; } if (aaa) { // update balance aaa.updateBalance(directResponse.Result.EndBalance[0]); } if (resultCode == '0000') { var nama_pelanggan = directResponse.Result.nama_pel[0].trim(); nama_pelanggan = nama_pelanggan.replace(/-\/-/g, '-'); var sn = directResponse.Result.token[0].trim() + '/' + nama_pelanggan + '/' + directResponse.Result.tarif[0].trim() + 'VA/' + directResponse.Result.jml_daya[0].trim(); sn = sn.replace(/\s/g, '-'); responseMessage = 'SN=' + sn + '; ' + responseMessage; logger.info('New response message: ' + responseMessage); } if (pendingResultCode.indexOf(resultCode) != -1) { callbackReport(task['requestId'], '68', responseMessage); /* logger.info('Got pending status, requesting advice from webreport in ' + sleep_before_retry + 'ms'); setTimeout(function () { cekstatus.advice({trxid: directResponse.Result.TransID[0].trim()}, callbackFromWebReport); }, sleep_before_retry); */ return; } responseCode = resultCode.replace(/^00/, ""); if (result_error_message == 'Inq - APLICATION SERVER RESPONSE TIMEOUT') { responseCode = '91'; } } catch(err) { responseCode = '40'; responseMessage = 'Invalid response from gateway'; } } callbackReport(task['requestId'], responseCode, responseMessage); }); } //callbackReport(task['requestId'], responseCode, responseMessage); }); } function callbackFromWebReport(status) { if (!status) { logger.warn('Advice from webreport return empty status'); return; } logger.info('Got advice result from webreport', {status: status}); var responseCode = '68'; var result_price = 0; try { result_price = directResponse.Result.Price[0].trim(); } catch(err) {} var errorMsg = ''; try { errorMsg = status.response.errormsg[0]; } catch(err) {} var responseMessage = ''; try { responseMessage = 'Hasil advice dari webreport ' + 'ResultCode: ' + status.response.resultcode[0] } catch(err) { logger.warn('Error parsing ResultCode from webreport advice.', {err: err}); } try { responseMessage = responseMessage + ' | ErrorMsg: ' + errorMsg } catch(err) { logger.warn('Error parsing ErrorMsg from webreport advice.', {err: err}); } try { responseMessage = responseMessage + ' | TrxDate: ' + status.trxDate; } catch(err) { logger.warn('Error parsing TrxDate from webreport advice.', {err: err}); } try { responseMessage = responseMessage + ' | UpdateDate: ' + status.updateDate; } catch(err) { logger.warn('Error parsing UpdateDate from webreport advice.', {err: err}); } try { responseMessage = responseMessage + ' | nsm: ' + status.response.nsm[0]; } catch(err) { logger.warn('Error parsing nsm from webreport advice.', {err: err}); } try { responseMessage = responseMessage + ' | idpel: ' + status.response.idpel[0]; } catch(err) { logger.warn('Error parsing idpel from webreport advice.', {err: err}); } try { responseMessage = responseMessage + ' | reffid: ' + status.response.reffid[0].trim(); } catch(err) { logger.warn('Error parsing reffid from webreport advice.', {err: err}); } try { responseMessage = responseMessage + ' | TransID: ' + status.response.transid[0].trim(); } catch(err) { logger.warn('Error parsing TransID from webreport advice.', {err: err}); } try { responseMessage = responseMessage + ' | reff_switching: ' + status.response.reff_switching[0]; } catch(err) { logger.warn('Error parsing reff_switching from webreport advice.', {err: err}); } try { responseMessage = responseMessage + ' | amount_trx: ' + status.response.amount_trx[0]; } catch(err) { logger.warn('Error parsing amount_trx from webreport advice.', {err: err}); } try { responseMessage = responseMessage + ' | token: ' + status.response.token[0]; } catch(err) { logger.warn('Error parsing token from webreport advice.', {err: err}); } try { responseMessage = responseMessage + ' | PrevBalance: ' + status.response.prevbalance[0]; } catch(err) { logger.warn('Error parsing PrevBalance from webreport advice.', {err: err}); } try { responseMessage = responseMessage + ' | Price: ' + status.amount; } catch(err) { logger.warn('Error parsing Price from webreport advice.', {err: err}); } try { responseMessage = responseMessage + ' | EndBalance: ' + status.response.endbalance[0]; } catch(err) { logger.warn('Error parsing EndBalance from webreport advice.', {err: err}); } if ((status.status == 'S') && (status.response.resultcode[0] == '0000')) { responseCode = '00'; var nama_pelanggan = status.response.nama_pel[0] ; nama_pelanggan = nama_pelanggan.replace(/-\/-/g, '-'); var sn = status.response.token[0] + '/' + nama_pelanggan + '/' + status.response.tarif[0] + 'VA/' + status.response.jml_daya[0]; responseMessage = 'SN=' + sn + '; ' + responseMessage; } else if ((status.status == 'P') || (status.status == 'W')) { return callbackReport(status.response.reffid[0].trim(), '68', responseMessage); /* logger.info('Got pending status, requesting advice from webreport in ' + sleep_before_retry + 'ms'); setTimeout(function () { cekstatus.advice({trxid: status.trxId,}, callbackFromWebReport); }, sleep_before_retry); */ } else { responseCode = status.response.resultcode[0].replace(/^00/, ""); if (['00', '05', '12', '68', '90', '63', '18', '96'].indexOf(responseCode) >= 0) { responseCode = '40'; } } return callbackReport(status.response.reffid[0].trim(), responseCode, responseMessage); } function createHttpReportServer() { var httpServer = http.createServer(function(request, response) { var qs = url.parse(request.url, true).query; var path = url.parse(request.url).pathname; logger.info('Got reverse report from partner', {path: path, qs: qs}); response.end('OK'); var requestId = qs.reffid; var resultCode = qs.rescode; if (requestId && resultCode && resultCode != '0') { } if (requestId && resultCode) { if (resultCode == '0') { var responseData = reverseParser.parseMessage(qs.msg); var sn = [ qs.token, responseData.namapel, responseData.tarifdaya, responseData.jumlahkwh ].join('/'); return callbackReport(requestId, '00', 'SN=' + sn + ';' + 'Got reverse report: ' + qs.msg); } else if (pendingResultCode.indexOf(resultCode) >= 0) { return callbackReport(requestId, '68', 'Got reverse report: ' + qs.msg); } else { return callbackReport(requestId, '40', 'Got reverse report: ' + qs.msg); } } /* var trxid; try { trxid = qs.transid; } catch(err) { } if (trxid) { logger.info('Requesting advice from webreport', {trxid: trxid}) cekstatus.advice({trxid: trxid}, callbackFromWebReport); } */ }); httpServer.listen(config.h2h_out.listen_port, function() { logger.info('HTTP Reverse/Report server listen on port ' + config.h2h_out.listen_port); }); } 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)() ] }); } cekstatus.setLogger(logger); createHttpReportServer(); initMongoClient(); } exports.start = start; exports.topupRequest = topupRequest;