xmlrpc-server.js 9.43 KB
var xmlrpc = require('xmlrpc');
var request = require('request');
var neoxmlinutil = require('./neoxmlinutil');
var http = require('http');
var https = require('https');
var fs = require('fs');
var xml2js = new require('xml2js');
var xml2jsParser = require('xml2js').parseString;
var xml2jsBuilder = new xml2js.Builder();
var url = require("url");
var strftime = require('strftime');

var options;
var config;
var logger;

var server;

function start(_options) {
    options = _options;

    if (options.config) {
        config = options.config;
    }
    else {
        config = require("./config.json");
    }

    if (options.logger) {
        logger = options.logger;
    }
    else {
        logger = console;
    }

    createResponseServer();

    createDiyHttpXmlRpcServer();
    //createXmlRpcServer()
    //createExpressXmlRpcServer();
}

function getXmlRpcParam(values) {
    try {

        var count = values.length
        var result = {};
        for (var i = 0; i < count; i++) {
            var value = values[i];

            var keys = Object.keys(value.value[0]);
            var firstKey = keys[0];
            result[value.name[0]] = value.value[0][firstKey][0];
        }

        return result;

    }
    catch(err) {
        return null;
    }
}

function createDiyHttpXmlRpcServer() {
    var serverOptions = {
        key: fs.readFileSync('./server.key'),
        cert: fs.readFileSync('./server.crt')
    }

    var httpServer = https.createServer(serverOptions, function(req, res) {

        logger.info("Incoming connection from " + req.connection.remoteAddress);

        var body = "";

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

        req.on('end', function () {

            xml2jsParser(body, function(err, message) {

                if (err) {
                    res.end('Unknown xml');
                    return;
                }

                var method;
                var _params;

                try {
                    method = message.methodCall.methodName[0];
                    _params = message.methodCall.params[0].param[0].value[0].struct[0].member;
                }
                catch(errSelectMethod) {
                    logger.warn('Failed to get method and _params');
                    res.end('Invalid XMLRPC message')
                    return;
                }

                params = getXmlRpcParam(_params);
                logger.verbose('Got XMLRPC request', {method: method, params: params});

                sendToMaster(params, req.connection.remoteAddress, function(forwardError) {
                    if (forwardError) {
                        immediateReply(params, res, '40', forwardError.toString());
                    } else {
                        immediateReply(params, res, '68');
                    }
                });

            })
        });

    });

    httpServer.listen(config.server_options.port, function() {
        logger.info('HTTP XMLRPC listen on port ' + config.server_options.port);
    });
}

function composeXmlRpcResponse(param) {

    var values = [];
    var keys = Object.keys(param);
    var keysCount = keys.length;

    for (var i = 0; i < keysCount; i++) {
        var key = keys[i];
        var value = {
            name: key,
            value: {
                string: param[key]
            }
        }
        values.push(value);
    }

    var data = {
        methodResponse: {
            params: {
                param: {
                    value: {
                        struct: {
                            member: values
                        }
                    }
                }
            }
        }
    }

    logger.info(JSON.stringify(data));

    return xml2jsBuilder.buildObject(data);
}

function immediateReply(param, requestResponse, responseCode, message) {

    if (!responseCode) {
        responseCode = '68';
    }

    if (!message) {
        message = "Sementara tidak dapat diproses.";
    }

    if (responseCode == '68') {
        message = 'ISI '
            + param.NOM.toUpperCase()
            + ' KE '
            + param.NOHP
            + ', Transaksi anda sedang diproses'
    }

    var responseData = {
        'RESPONSECODE': responseCode,
        'REQUESTID': param.REQUESTID,
        'MESSAGE': message,
        'TRANSACTIONID': '0',
    }


    var responseBody = composeXmlRpcResponse(responseData)
    logger.info("Sending immediateReply response", {responseData: responseData});

    requestResponse.writeHead(200, {'Content-Type': 'text/xml'});
    requestResponse.end(responseBody);
}

function composeMessage(params, remoteAddress) {
    try {
        var nom = params.NOM.replace(/\./g, '').trim();
        var destination = params.NOHP.replace(/\./g, '').trim();
        var pin = params.PIN.replace(/\./g, '').trim();
        var requestId = params.REQUESTID.replace(/\./g, '').trim();

        return 'MI.' + nom + '."' + destination + '".' + pin + '.' + requestId + '.NOTRUST."' + remoteAddress + '"';
    }
    catch(err) {
        return;
    }


}

function sendToMaster(param, remoteAddress, callback) {

    /*
    var smscidSuffix = '99999999999999' + String(Math.round(Math.random() * 99999999999999));
    var smscid = 'XML1' + smscidSuffix.slice(-13);
    */

    //var smscid = 'XML' + '12345' + strftime('%H%M%S%L');
    var smscid = config.smscid;// + Math.round(Math.random() * 9999999999999);

    var message = composeMessage(param, remoteAddress);
    if (!message) {
        logger.warn('Invalid message for sendToMaster', {param: param});
        return;
    }

    var requestOpts = {
		url: config.master_url,
		qs: {
			PhoneNumber: param.MSISDN,
			'Text': message,
			Res_Port: config.res_port,
			Smscid: smscid
		}
	};

    if (config.res_ip) {
        requestOpts.qs.ip_addr = config.res_ip;
    }

    logger.info('Forward request to master', {requestOpts: requestOpts});

    request(requestOpts, function(requestError, response, body) {

        if (requestError) {
            logger.warn('Error requesting to master: ' + requestError);
        } else {
            logger.info('Got response from master', {body: body, requestOpts: requestOpts});
        }

        callback(requestError);
    })

}

function sendReply(response) {
    /*
    var requestId = neoxmlinutil.getRequestIdFromResponseMessage(response.text);

    if (!requestId) {
        logger.warn('No request id found, skipping');
        return;
    }
    */

    var params = {
        REQUESTID: response.requestid,
        MSISDN: response.PhoneNumber,
        RESPONSECODE: response.resp_code,
        MESSAGE: response.text,
    }

    if (response.new_sn) {
        params.SN = response.new_sn.replace(/^SN=/, '');
    }

    if (response.new_ending_balance) {
        params.BALANCE = response.new_ending_balance;
    }

    if (response.new_price) {
        params.PRICE = response.new_price;
    }

    logger.info('sendReply', {params: params});

    neoxmlinutil.getReverseUrl(response.PhoneNumber, function(err, reverseUrls) {
        if (err) {
            logger.warn('Fail to get reverse urls, skipping');
            return;
        }

        if (reverseUrls.length <= 0) {
            logger.warn('No reverse urls found, skipping');
            return;
        }

        sendTopUpReport(reverseUrls, params, 0, 4);
    });
}

function sendTopUpReport(reverseUrls, params, urlIdx, retry) {
    if (retry === null || retry === undefined) {
        retry = 1;
    }

    if (urlIdx === null || urlIdx === undefined) {
        urlIdx = 0;
    }

    if (urlIdx >= reverseUrls.length) {
        logger.warn('No other reverse urls for partner available');

        if (retry > 0) {
            logger.warn('Retrying to send topUpReport to partner first url');
            setTimeout(
                sendTopUpReport,
                5000,
                reverseUrls,
                params,
                0,
                --retry
            )
            return;
        }

        logger.warn('topUpReport retry exceed');
        return;
    }

    var partnerUrl = url.parse(reverseUrls[urlIdx]);

    var clientOptions = {
        host: partnerUrl.hostname
        , port: partnerUrl.port
        , path: partnerUrl.pathname
    };
    logger.info('Preparing to send topUpReport', {clientOptions: clientOptions});

    var client;
    if (partnerUrl.protocol == 'https:') {
        client = xmlrpc.createSecureClient(clientOptions);
    } else {
        client = xmlrpc.createClient(clientOptions);
    }

    var methodName = 'topUpReport';
    logger.info('Requesting topUpReport', {params: params});

    client.methodCall(methodName, [ params ], function (topUpReportError, value) {

        if (topUpReportError) {
            if (topUpReportError.indexOf('Invalid XML-RPC message') < 0) {

                logger.warn('Error sending topUpReport retrying another url (if available): ' + topUpReportError, {error: topUpReportError});
                sendTopUpReport(reverseUrls, params, ++urlIdx, retry);
                return;

            }
        }

        logger.verbose("topUpReport ACK", {value: value});

    });
}

function createResponseServer() {
    var httpServer = http.createServer(function(req, res) {

        res.end();

        var parsed_url = url.parse(req.url, true, true);
        logger.info("Hit on response server", {qs: parsed_url.query});

        sendReply(parsed_url.query);

    });

    httpServer.listen(config.res_port, function() {
        logger.info('HTTP Response server listen on port ' + config.res_port);
    });
}

exports.start = start;