partner-datacell.js 9.85 KB
var http = require('http');
var url = require('url');
var math = require('mathjs');
var xml = require('xml');
var xml2js = require('xml2js').parseString;
var strftime = require('strftime');
var xor = require('base64-xor');
var request = require('request');

var config;
var logger;
var aaa;
var callbackReport;

var max_retry = 2;
var sleep_before_retry = 2000;

var trx_balances = {};
var trx_prices = {};

function calculateSignature(userid, password, msisdn, timestamp) {
    var a = msisdn.substr(msisdn.length - 4) + timestamp;
    var b = userid.substr(0, 4) + password;

    return xor.encode(a,b);
}

function calculateBalanceSignature(userid, password, timestamp) {
    var a = '0000' + timestamp;
    var b = userid.substr(0, 4) + password;

    return xor.encode(a,b);
}


function createPayload(task) {
    var timestamp = strftime('%H%M%S');

    var payload = {
        datacell: [
            {perintah: 'charge'},
            {oprcode: task['remoteProduct']},
            {userid: config.h2h_out.userid},
            {time: timestamp},
            {msisdn: task['destination']},
            {ref_trxid: task['requestId']},
            {sgn: calculateSignature(config.h2h_out.userid, config.h2h_out.password, task['destination'], timestamp)}
        ]
    };

    logger.info('Creating payload', {payload: payload});
    return "<?xml version=\"1.0\" ?>\n" + xml(payload);
}

function topupRequest(task, retry) {
    //balanceCheck();

    var payload_xml = createPayload(task);
    //console.log(payload_xml);

    var partnerUrl = url.parse(config.h2h_out.partner);

    var postRequest = {
        host: partnerUrl.hostname,
        path: partnerUrl.pathname,
        port: partnerUrl.port,
        method: "POST",
        headers: {
            'Content-Type': 'text/xml',
            'Content-Length': Buffer.byteLength(payload_xml)
        }
    };

    logger.verbose('Requesting to partner', {postRequest: postRequest, payload: payload_xml});
    var buffer = "";
    var req = http.request( postRequest, function( res )    {

        logger.info('Status code: ' + res.statusCode );
        var buffer = "";
        res.on( "data", function( data ) { buffer = buffer + data; } );
        res.on( "end", function( data ) {
                topupResponseHandler(buffer, task.requestId);
        });

    });

    req.on('error', function(e) {
        logger.warn('problem with request: ' + e.message);
        callbackReport(task['requestId'], '40', e.message);
    });

    req.write( payload_xml );
    req.end();
}

function topupResponseHandler(body, request_id) {
    logger.info('topupResponseHandler', {body: body, request_id: request_id});

    if (!body) {
        logger.info('Partner send empty response', {request_id: request_id});
        if (request_id) {
            callbackReport(request_id, '68', 'partner send empty response');
        }
        return;
    }

    xml2js(body, function (err, result) {
        if (err) {
            logger.warn('topupResponseHandler', {body: body});
            callbackReport(request_id, '40', buffer);
            return;
        }

        logger.info('topupResponseHandler', {result: result});

        if (!request_id) {
            try {
                request_id = result.datacell.ref_trxid[0].trim();
            }
            catch(errRequestId) {
                return;
            }
        }


        var response_code = '68';

        var message = '';
        try {
            if (result.datacell.message && result.datacell.message.length > 0) {
                message = result.datacell.message[0].trim();
            } else if (result.datacell.msg && result.datacell.msg.length > 0) {
                message = result.datacell.msg[0].trim();
            }
        }
        catch(err) {
            message = 'exception saat parsing message';
        }

        if (result.datacell.resultcode && result.datacell.resultcode[0] == '999') {
            response_code = '40';
        }

        if (message.indexOf('Nomor tujuan salah') >= 0) {
            response_code = '14';
        } else if (message.indexOf('*GAGAL, transaksi yang sama sudah ada dalam 10 menit') >= 0) {
            response_code = '55';
        } else if (message.indexOf('saldo sdh dikembalikan') >= 0) {
            response_code = '40'
        } else if (message.indexOf('Trx dpt diulang') >= 0) {
            response_code = '40'
        } else if (message.indexOf('SUKSES SN Operator:') >= 0) {
            response_code = '00';

            var sn = parseSN(message);
            logger.info('SN Operator: ' + sn);

            /*
            if (!sn) {

                console.log('Missing real operator SN, using SN from suplier');
                try {
                    sn = result.datacell.trxid[0].trim();
                }
                catch(err) {
                    sn = '';
                }
            }
            */

            if (sn) {
                message = 'SN=' + sn + '; ' + message;
            } else {
                message = 'SN belum didapat. ' + message;
                response_code = '68';
            }
        }


        var price = priceFromMessage(message);
        if (price != null) {
            logger.info('Harga: ' + price);
            trx_prices[request_id] = price;
            setTimeout(deleteTrxPrice, 3 * 24 * 3600 * 1000, request_id);
        } else if (response_code == '00' && trx_prices[request_id] !==  undefined) {
            price = trx_prices[request_id];
            logger.info('Harga: ' + price);
            message = message + ' -- Harga: ' + price;
        }

        var balance = balanceFromMessage(message);
        if (balance != null) {
            logger.info('Saldo: ' + balance);
            trx_balances[request_id] = balance;
            setTimeout(deleteTrxBalance, 3 * 24 * 3600 * 1000, request_id);
        } else if (response_code == '00' && trx_balances[request_id] !==  undefined) {
            balance = trx_balances[request_id];
            logger.info('Saldo: ' + balance);
            message = message + ' -- Saldo: ' + balance;
        }

        callbackReport(request_id, response_code, message);
    });
}

function deleteTrxPrice(request_id) {
    delete trx_prices[request_id];
}

function deleteTrxBalance(request_id) {
    delete trx_balances[request_id];
}

function parseSN(message) {
    var results = message.match(/SN Operator: .+ SN Kami/);
    if (!results || results.length <= 0) {
        return '';
    }

    var result = results[0];
    result = result.replace('SN Operator:', '');
    result = result.replace('SN Kami', '');
    result = result.trim();

    if (result == '00') {
        result = '';
    }

    return result;
}

function createServer() {

    var httpServer = http.createServer(function(req, res) {
        var parsed_url = url.parse(req.url, true, true);

        logger.info('Got request from partner ("' + req.url + '")');

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

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

            //console.log(body);

            if (parsed_url.pathname == '/sn') {
                logger.info('Reverse report -- SN');
                topupResponseHandler(body);

            } else if (parsed_url.pathname = '/refund') {
                logger.info('Reverse report -- REFUND');
                callbackReport(parsed_url.query.ref_trxid, '40', parsed_url.query.message);

            } else {
                logger.info('Reverse report -- UNKNOWN');
                logger.info('Ignore unknown request on reverse url');
            }
        });
    });

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

function balanceCheck() {
    var timestamp = strftime('%H%M%S');

    var payload = {
        datacell: [
            {perintah: 'saldo'},
            {userid: config.h2h_out.userid},
            {time: timestamp},
            {sgn: calculateBalanceSignature(config.h2h_out.userid, config.h2h_out.password, timestamp)}
        ]
    };

    var partnerUrl = url.parse(config.h2h_out.partner);

    var postRequest = {
        host: partnerUrl.hostname,
        path: partnerUrl.pathname,
        port: partnerUrl.port,
        method: "POST",
        headers: {
            'Content-Type': 'text/xml',
            'Content-Length': Buffer.byteLength(payload_xml)
        }
    };

    var buffer = "";
    var req = http.request( postRequest, function( res )    {

        logger.info('Status code: ' + res.statusCode );
        var buffer = "";
        res.on( "data", function( data ) { buffer = buffer + data; } );
        res.on( "end", function( data ) {
            logger.info('CHECK BALANCE RESULT', {buffer: buffer});
        });

    });

    req.on('error', function(e) {
        logger.warn('problem with request: ' + e.message);
    });

    req.write( payload_xml );
    req.end();

}

function balanceFromMessage(message) {
    var matches = message.match(/Saldo: Rp (\d+)/);

    if (!matches) {
        return null;
    }
    if (matches.length < 2) {
        return null;
    }

    return matches[1];
}

function priceFromMessage(message) {
    var matches = message.match(/Harga: (\d+)/);

    if (!matches) {
        return null;
    }
    if (matches.length < 2) {
        return null;
    }

    return matches[1];
}

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)()
            ]
        });
    }
}

exports.start = start;
exports.topupRequest = topupRequest;
exports.balanceFromMessage = balanceFromMessage;
exports.priceFromMessage = priceFromMessage;