partner-fm.js 9.6 KB
var xml2js = require('xml2js');
var request = require('request');
var http = require('http');
var redis = require('redis');
var resendDelay = require('sate24/resend-delay.js');
var LRU = require('lru-cache');

var aaa;
var _callbackReport;
var config;
var logger;
var redisClient;

var xmlBuilder = new xml2js.Builder();
var taskHistory = LRU({max: 500, maxAge: 1000 * 3600 * 2});

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 {
        console.log('Undefined options.logger, terminating....')
        process.exit(1);
    }

    createRedisClient(config.globals.redis_host, config.globals.redis_port);
    createServer();

    resendDelay.init({
        config: config,
        topupRequest: topupStatus,
        logger: logger
    });
}

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});
            topupStatus(task);
        } else {
            _topupRequest(task);
        }
    });
}

function _topupRequest(task) {

    var payload = composeTopupMessage(
        config.h2h_out.pin,
        task.remoteProduct,
        task.destination,
        task.requestId
    );

    var reqOpts = {
        url: config.h2h_out.partner,
        method: "POST",
        body: payload,
        headers: {
            'Content-Type': 'text/xml',
        }
    };

    logger.verbose('Requesting TOPUP to partner', {reqOpts: reqOpts, payload: payload});
    request(reqOpts, function (err, response, body) {
        if (err) {
            var msg = 'Error requesting TOPUP to partner: ' + err;
            logger.warn(msg, {task: task, err: err});
            callbackReport(task.requestId, '68', msg);
            return;
        }

        logger.verbose('Got a direct response from TOPUP', {response: body, task: task});
        topupResponseHandler(body, task.requestId, callbackReport);
    });
}

function topupStatus(task) {
    var payload = composeTopupStatusMessage(
        config.h2h_out.pin,
        task.requestId
    );

    var reqOpts = {
        url: config.h2h_out.partner,
        method: "POST",
        body: payload,
        headers: {
            'Content-Type': 'text/xml',
        }
    };

    logger.verbose('Requesting TOPUPSTATUS to partner', {reqOpts: reqOpts, payload: payload});
    request(reqOpts, function (err, response, body) {
        if (err) {
            var msg = 'Error requesting TOPUPSTATUS to partner: ' + err;
            logger.warn(msg, {task: task, err: err});
            callbackReport(task.requestId, '68', msg);
            return;
        }

        logger.verbose('Got a direct response from TOPUPSTATUS', {response: body, task: task});
        topupResponseHandler(body, task.requestId, callbackReport);
    });
}

function topupResponseHandler(xmlResponse, _requestId, cb) {
    var xmlParser = xml2js.parseString;
    xmlParser(xmlResponse, function(err, data) {
        var msg;
        var requestId;
        var rc = '68';

        if (_requestId) {
            requestId = _requestId;
        }

        if (err) {
            msg = 'Error parsing xml response: ' + err;

            if (logger) {
                logger.warn(msg, {err: err, response: xmlResponse, task: task});
            } else {
                console.log(msg);
            }
        } else {

            try {
                msg = data.fm.message
            }
            catch(e) {
                msg = 'Unknown message'
            }

            if (data.fm.status == '0') {

                rc = '00';
                msg = modifyMessageWithSn(msg);
                logger.verbose('Modify message on success message', {msg: msg});

            } else if (data.fm.status == '1') {
                rc = '68';
            } else if (data.fm.status == '2') {
                rc = '40';
            } else if (data.fm.status == '3') {
                rc = '40';
            } else {
                rc = '68';
            }

            if (data.fm.refTrxid) {
                requestId = data.fm.refTrxid;
            }

        }

        cb(requestId, rc, msg, xmlResponse)
    });
}

function callbackReport(requestId, responseCode, msg, rawResponse, dontResendDelay) {
    if (!requestId) {
        logger.warn('Undefined requestId, not sending callbackReport', {rc: responseCode, msg: msg, rawResponse: rawResponse});
        return;
    }

    if (responseCode != '68' || dontResendDelay) {
        resendDelay.cancel(requestId);
    } else {
        getTaskFromHistory(requestId, function(err, archivedTask) {
            if (archivedTask) {
                resendDelay.register(archivedTask);
            }
        });
    }

    _callbackReport(requestId, responseCode, msg, null, rawResponse);
}

function getSnFromMessage(msg) {
    try {
        var matches = msg.match(/SN:\s*(\d+)/);
        return matches[1];
    }
    catch(e) {
        return;
    }
}

function modifyMessageWithSn(msg) {
    var sn = getSnFromMessage(msg);

    if (logger) {
        logger.verbose('SN=' + sn);
    }

    if (sn) {
        msg = 'SN=' + sn + '; ' + msg;
    }
    return msg;
}

function composeTopupMessage(pin, product, destination, requestId) {
    var data = {fm: {
        command: 'TOPUP',
        pin: pin,
        product: product,
        msisdn: destination,
        refTrxid: requestId
    }};

    return xmlBuilder.buildObject(data);
}

function composeTopupStatusMessage(pin, requestId) {
    var data = {fm: {
        command: 'TOPUPSTATUS',
        pin: pin,
        refTrxid: requestId
    }}

    return xmlBuilder.buildObject(data);
}

function createServer() {
    var httpServer = http.createServer(function(request, response) {

        logger.info('Got request from partner');

        var body = "";
        req.on('data', function (chunk) {
            body += chunk;
        });

        req.on('end', function () {
            res.writeHead(200);
            res.end('OK');

            topupResponseHandler(body, null, callbackReport);

        });

    });

    httpServer.listen(config.h2h_out.listen_port, function() {
        logger.info('HTTP Reverse/Report server listen on port ' + config.h2h_out.listen_port);
    });
}

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 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.composeTopupMessage = composeTopupMessage;
exports.getSnFromMessage = getSnFromMessage;
exports.modifyMessageWithSn = modifyMessageWithSn;