partner-cjk.js 8.64 KB
var winston = require('winston');
var crypto = require('crypto');
var xml = require('xml');
var url = require('url');
var http = require('http');
var xml2js = require('xml2js').parseString;
var mongoClient = require('mongodb').MongoClient;
var strftime = require('strftime');
var moment = require('moment');

var config;
var callbackReport;
var aaa;
var logger;
var options;
var mongodb;

function initMongoClient() {
    if (!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});
    }
}

var maxRetry = 10;
var sleepBeforeRetry = 30;

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

    initMongoClient();
}

function calculateSignature(params) {
    var passwordHash = crypto.createHash('sha1').update(params.password).digest().toString('hex');
    var plain = params.trxtype + params.prdcode + params.value + params.msisdn + params.trxid + params.uid + passwordHash;
    var signature = crypto.createHash('sha256').update(plain).digest().toString('hex');

    try {
        logger.verbose('Signature calculated', {plain: plain, signature: signature});
    }
    catch(err) {}

    return signature;
}

function createXmlPayload(params) {
    var payload = "<?xml version=\"1.0\" ?>\n" + xml({
        ciwaru: [
            {trxtype: params.trxtype},
            {prdcode: params.prdcode},
            {value: params.value},
            {msisdn: params.msisdn},
            {trxid: params.trxid},
            {uid: params.uid},
            {hash: calculateSignature(params)}
        ]
    });

    try { logger.verbose("Payload: " + payload); }
    catch(errLog) {}

    return payload;
}

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 {
        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 getSNFromMessage(message) {
    try {
        var sn_match = message.match(/SN: (\w+)/);
        return sn_match[1].trim();
    }
    catch(err) {
        if (logger) {
            logger.verbose('Exception on getting sn from message', {err: err});
        }
        return '';
    }
}

function hasSuccessKeywords(message) {
    var keywords = ['SUKSES', 'Finish'];

    var count = keywords.length;
    for (var i=0; i < count; i++) {
        if (message.indexOf(keywords[i]) >= 0) {
            return true;
        }
    }
    return false;
}

function supplierRcToST24Rc(rc) {
    var rcs = {
        '0001': '40',
        '0012': '40', // hash data tidak sesuai
        '0015': '40', // saldo tidak cukup
        '0019': '13', // produk tidak tersedia
    }

    if (rcs[rc]) {
        return rcs[rc];
    } else {
        return;
    }
}

function getSNFromResponseObject(respObj) {
    try {
        return respObj.ciwaru.sn[0].trim();
    }
    catch(err) {
        return;
    }
}

function topupResponseHandler(body, task) {

    //logger.info('Got reply from partner', {body: body});

    xml2js(body, function(err, result) {
        var ts =  strftime('%Y-%m-%d %H:%M:%S', new Date());

        if (err) {
            logger.warn('Got invalid XML from partner', {err: err, body: body, task: task});
            callbackReport(task.requestId, '68', body);

            pushResponseToMongoDb(task, {ts:  ts, supplier: config.globals.gateway_name, raw: body}, '68');
            return;
        }
        logger.info('XML message from partner', {result: result});

        var rc = '68';
        var message = result.ciwaru.msg[0];

        if (message.toUpperCase().indexOf('PENDING') >= 0) {
            rc = '68';
        }
        else if (hasSuccessKeywords(message)) {

            var sn = getSNFromResponseObject(result);

            if (!sn) {
                sn = getSNFromMessage(message);
            }

            message = 'SN=' + sn + '; ' + message;
            rc = '00';

        }
        else if (message.indexOf('Nomor Pelanggan Salah') >= 0)  {

            rc = '14';
        }
        else if (message.indexOf('Kuota Produk Untuk Hari Ini Habis') >= 0) {

            rc = '13';

        } else {
            rc  = supplierRcToST24Rc(result.ciwaru.rc[0]);
            if (!rc) {
                rc = '68';
            }
        }

        pushResponseToMongoDb(task, {ts:  ts, supplier: config.globals.gateway_name, raw: body, parsed: result}, rc);

        if ((task.retry == maxRetry) || (rc != '68')) {
            callbackReport(task.requestId, rc, message);
        } else {
            logger.info('Not reporting to AAA for duplicate 68', {task: task});
        }

        if (rc == '68') {
            topupRequestRetry(task);
        }
    });
}

function topupRequestRetry(task) {
    task.retry--;

    if (task.retry > 0) {
        logger.info('Retrying in ' +  sleepBeforeRetry + 's');
        setTimeout(topupRequest, sleepBeforeRetry * 1000, task, task.retry);
    }
    else {
        logger.warn('Maximum retry for pending status exceeded', {task: task});
    }
}

function topupRequest(task, retry) {

    if (retry === undefined) {

        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);

        retry = maxRetry;
    }

    if (!task.retry) {
        task.retry = retry;
    }

    var remoteProduct = task.remoteProduct.split(',');

    var params = {
        trxtype: '01',
        prdcode: remoteProduct[0],
        value: remoteProduct[1],
        msisdn: task.destination,
        trxid: task.requestId,
        uid: config.h2h_out.userid,
        password: config.h2h_out.password,
    };

    var postBody = createXmlPayload(params);

    var partnerUrl = url.parse(config.h2h_out.partner);
    var postRequest = {
        host: partnerUrl.hostname,
        path: partnerUrl.path,
        port: partnerUrl.port,
        method: "POST",
        headers: {
            'Content-Type': 'text/xml',
            'Content-Length': Buffer.byteLength(postBody)
        }
    };

    logger.info('POST to partner', {postRequest: postRequest});
    var req = http.request(postRequest, function( res ) {

        logger.verbose('Status code: ' + res.statusCode );
        var buffer = "";

        res.on( "data", function( data ) {
            buffer = buffer + data;
        });

        res.on( "end", function( data ) {
            topupResponseHandler(buffer, task);
        });

    });

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

        topupRequestRetry(task);
    });

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

exports.start = start;
exports.topupRequest = topupRequest;
exports.calculateSignature = calculateSignature;
exports.createXmlPayload = createXmlPayload;
exports.getSNFromMessage = getSNFromMessage;