var winston = require('winston'); var soap = require('soap'); var crypto = require('crypto'); var strftime = require('strftime'); var fs = require("fs"); var http = require("http"); var url = require("url"); var mongoClient = require('mongodb').MongoClient; var moment = require('moment'); process.chdir(__dirname); var max_retry = 10; var sleep_before_retry = 5000; var config; var matrix; var callbackReport; var aaa; var logger; var options; var mongodb; function start(_config, _callbackReport, options) { config = _config; callbackReport = _callbackReport if (options && options.aaa) { aaa = options.aaa; } if (options && options.logger) { logger = options.logger; } else { logger = new winston.Logger({ transports: [ new (winston.transports.Console)() ] }); } if (options && options.matrix) { matrix = options.matrix; } initMongoClient(); } function callbackReportWrapper(requestId, responseCode, message, dontIncrement) { callbackReport(requestId, responseCode, message); if (dontIncrement) { return; } try { aaa.incrementStrikeStatus(responseCode); } catch(err) { logger.warn("Gagal aaa.incrementStrikeStatus: " + err); } } function initMongoClient() { if (!config || !config.mongodb || !config.mongodb.url) { return; } try { var url = config.mongodb.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 insertTaskToMongoDb(task) { if (!isMongoReady()) { return; } task.supplier = config.globals.gateway_name; task.rc = '68'; try { mongodb.collection(config.mongodb.collection).insertOne(task); } catch(err) { //logger.warn('Exception when inserting document to mongodb', {err: err, task: task}); } } function pushResponseToMongoDb(task, response, rc) { if (!isMongoReady()) { return; } try { if (!response.ts) { response.ts = strftime('%Y-%m-%d %H:%M:%S', new Date()); } mongodb.collection(config.mongodb.collection).updateOne( {requestId: task.requestId}, { $set: { lastResponse: response, supplier: config.globals.gateway_name, rc: rc }, $push: { responses: response } }, function(err, result) { if (err) { logger.warn('Error when pushing response to mongodb', {err: err, task: task, response: response}); return; } } ); } catch(err) { logger.warn('Exception when pushing response to mongodb', {err: err, task: task, response: response}); } } function isMongoReady() { if (!config.mongodb) { return; } if (!config.mongodb.collection) { return; } if (!mongodb) { return; } return true; } function topupRequest(task, retry) { try { if (config && config.globals && config.globals.reject_on_pending_count && matrix && matrix.strikeStatus && matrix.strikeStatus.pending) { var pendingCount = matrix.strikeStatus.pending; var pendingLimitCount = parseInt(config.globals.reject_on_pending_count); if (pendingLimitCount <= matrix.strikeStatus.pending) { logger.warn( 'Reject trx karena pending terlalu banyak', {pendingCount: pendingCount, pendingLimitCount: pendingLimitCount} ); callbackReport(task.requestId, '13', 'Reject trx karena pending terlalu banyak'); return; } } } catch(err) { logger.warn("Exception saat periksa pendingLimit: " + err); } task.ts = moment(task.timestamp, 'YYYYMMDDHHmmss').format('YYYY-MM-DD HH:mm:ss'); task.ts_date = moment(task.timestamp, 'YYYYMMDDHHmmss').format('YYYY-MM-DD'); insertTaskToMongoDb(task); saldoCheck(billpayment, task); } function saldoCheck(callback, task) { var params = { userName: config.h2h_out.userid, productCode: '00000' , terminal: 'H2HIPN10', transactionType: '61', reff: Math.ceil( Math.random() * 99999999 ), timeStamp: strftime('%Y-%m-%d %H:%M:%S', new Date()) } params.signature = createSignatureForSaldoCheck(params, config.h2h_out.password); soap.createClient(config.h2h_out.partner, function(err, soapClient) { if (err) { var errorMessage = 'Error creating soap client for saldoCheck: ' + err; logger.warn(errorMessage, {err: err}); callbackReportWrapper(task.requestId, '40', errorMessage); pushResponseToMongoDb(task, {supplier: config.globals.gateway_name, raw: errorMessage}, '40'); return; } logger.info('Requesting to service', {url: config.h2h_out.partner, params: params}); soapClient.apih2h.apih2hPort.saldoCheck({ inputSaldo: params }, function(err, result) { logger.verbose( 'Got saldoCheck response', { lastEndpoint: soapClient.lastEndpoint, lastRequest: soapClient.lastRequest, lastMessage: soapClient.lastMessage, lastResponse: soapClient.lastResponse, } ); if (err) { var errorMessage = 'Error requesting saldoCheck: ' + err; logger.warn(errorMessage, {err: err}); callbackReportWrapper(task.requestId, '40', errorMessage); pushResponseToMongoDb(task, {supplier: config.globals.gateway_name, raw: errorMessage}, '40'); } var balance; logger.verbose('saldoCheck result', {result: result}); try { balance = result.outputParameter.bit61.$value; } catch(e) { balance = 'UNKNOWN'; } if (task) { callback(task, balance); } }); }); } function billpayment(task, balance) { var terminalSuffix = Math.ceil( Math.random() * 20 ) + 10; var remoteProduct = task.remoteProduct.split(','); var params = { userName: config.h2h_out.userid, productCode: remoteProduct[0] , terminal: 'H2HIPN' + terminalSuffix, transactionType: '50', billNumber: createBillNumber(task.destination), amount: remoteProduct[1], bit61: createBillNumber(task.destination), reff: task.requestId, timeStamp: strftime('%Y-%m-%d %H:%M:%S', new Date()) } var signature = createSignature(params, config.h2h_out.password); params.signature = signature; soap.createClient(config.h2h_out.partner, function(err, soapClient) { var _params = { userName: params.userName, signature: params.signature, productCode: params.productCode, terminal: params.terminal, transactionType: params.transactionType, billNumber: params.billNumber, amount: params.amount, bit61: params.bit61, reff: params.reff, timeStamp: params.timeStamp } logger.info('Requesting to service', {url: config.h2h_out.partner, params: _params}); soapClient.apih2h.apih2hPort.billpayment({ inputCheck: _params }, function(err, result) { logger.verbose( 'Got response', { lastEndpoint: soapClient.lastEndpoint, lastRequest: soapClient.lastRequest, lastMessage: soapClient.lastMessage, lastResponse: soapClient.lastResponse, lastElapsedTime: soapClient.lastElapsedTime, } ); if (err) { var errorMessage = 'Error requesting service: ' + err; logger.warn(errorMessage, {err: err}); callbackReportWrapper(task.requestId, '68', errorMessage); pushResponseToMongoDb(task, {supplier: config.globals.gateway_name, raw: soapClient.lastResponse}, '68'); return; } topupResponseHandler(task, result, balance, soapClient.lastResponse); }, , {timeout: 120000, time: true}); }); } function topupResponseHandler(task, response, balance, rawResponse) { var st24rc = '68'; var st24message = response.outputParameter.resultDesc.$value; var resultCode = parseInt(response.outputParameter.resultCode.$value); var bit39 = parseInt(response.outputParameter.bit39.$value); var sn = ''; if ( resultCode == 1 ) { // product disabled st24rc = '13'; } else if ( resultCode == 2 ) { // prodcode disable st24rc = '13'; } else if ( resultCode == 3 ) { // duplicate reff st24rc = '55'; } else if ( resultCode == 4 ) { // not enough balance st24rc = '40'; } else if ( resultCode == 5 ) { // username blocked st24rc = '40'; } else if ( resultCode == 6 ) { // not exists username st24rc = '40'; } else if ( resultCode == 11 ) { // invalid request st24rc = '40' } else if ( resultCode == 12 ) { // no route to host st24rc = '40' } else if ( resultCode == 13 ) { // invalid signature st24rc = '40' } else if ( bit39 == 6 ) { st24rc = '40'; st24message = 'Error Transaksi ditolak karena terjadi error di H2H dengan response code diluar daftar ini. Silahkan hubungi H2H'; } else if ( bit39 == 12 ) { st24rc = '40'; st24message = 'Invalid Transaction Transaksi ditolak karena flow transaksi tidak valid'; } else if ( bit39 == 13 ) { st24rc = '13'; st24message = 'Invalid voucher nominal'; } else if ( bit39 == 14 ) { st24rc = '14'; st24message = 'MSISDN tidak ditemukan'; } else if ( bit39 == 30 ) { st24rc = '40'; st24message = 'Format Error'; } else if ( bit39 == 31 ) { st24rc = '40'; st24message = 'Kode bank tidak terdaftar'; } else if ( bit39 == 63 ) { st24rc = '40'; st24message = 'Reversal denied'; } else if ( bit39 == 68 ) { st24rc = '68'; st24message = 'Transaksi sedang dalam proses'; } else if ( bit39 == 69 ) { st24rc = '68'; st24message = 'Respon Ok lebih dari 24 detik'; } else if ( bit39 == 70 ) { st24rc = '13'; st24message = 'Voucher out of stock'; } else if ( bit39 == 79 ) { st24rc = '14'; st24message = 'Phone number is blocked by Telkomsel'; } else if ( bit39 == 81 ) { st24rc = '14'; st24message = 'Phone number is expired'; } else if ( bit39 == 89 ) { st24rc = '40'; st24message = 'Link to billing provider is down'; } else if ( bit39 == 91 ) { st24rc = '40'; st24message = 'Database problem'; } else if ( bit39 == 92 ) { st24rc = '40'; st24message = 'Unable to route transaction'; } else if ( bit39 == 94 ) { st24rc = '40'; st24message = 'Duplicate reversal request'; } else if ( resultCode == 0 && bit39 == 0) { try { sn = response.outputParameter.bit61.$value.substring(43); } catch(e) { sn = ''; } st24message = 'SN=' + sn + '; ' + st24message; st24rc = '00'; } var message = response.outputParameter.resultCode.$value + " " + response.outputParameter.resultDesc.$value + "; BIT39: " + response.outputParameter.bit39.$value ; if (response.outputParameter.resultDesc.$value != st24message) { var message = message + " " + st24message; } message = message + ' -- Prev Balance: ' + balance; var parsedResponse = { productCode: response.outputParameter.productCode.$value, terminal: response.outputParameter.terminal.$value, transactionType: response.outputParameter.transactionType.$value, billNumber: response.outputParameter.billNumber.$value, amount: response.outputParameter.amount.$value, bit61: response.outputParameter.bit61.$value, reff: response.outputParameter.reff.$value, timeStamp: response.outputParameter.timeStamp.$value, resultCode: response.outputParameter.resultCode.$value, resultDesc: response.outputParameter.resultDesc.$value, bit39: response.outputParameter.bit39.$value, prevBalance: balance, sn: sn, st24message: st24message, } var combinedMessage = ''; Object.keys(parsedResponse).forEach(function(key,index) { combinedMessage += key + ': ' + parsedResponse[key] + '; ' }); combinedMessage = combinedMessage.trim(); parsedResponse.MESSAGE = combinedMessage; logger.info('Got result: ' + message, {response: response}); callbackReportWrapper(task.requestId, st24rc, st24message + ' -- Prev Balance: ' + balance); pushResponseToMongoDb(task, {supplier: config.globals.gateway_name, raw: rawResponse, parsed: parsedResponse}, st24rc); } function createSignature(params, password) { var passwordHash = crypto.createHash('sha256').update(password).digest().toString('hex'); var plain = params.userName + passwordHash + params.productCode + params.terminal + params.transactionType + params.billNumber + params.amount + params.reff + params.timeStamp; return crypto.createHash('sha1').update(plain).digest().toString('hex'); } function createSignatureForSaldoCheck(params, password) { var passwordHash = crypto.createHash('sha256').update(password).digest().toString('hex'); var plain = params.userName + passwordHash + params.productCode + params.terminal + params.transactionType + params.reff + params.timeStamp; return crypto.createHash('sha1').update(plain).digest().toString('hex'); } function createBillNumber(destination) { return ("0000000000000" + destination).slice(-13); } exports.start = start; exports.topupRequest = topupRequest; exports.createSignature = createSignature; exports.createBillNumber = createBillNumber;