partner-masterpulsa-voucher.js 10.4 KB
var winston = require('winston');
var request = require('request');
var strftime = require('strftime');
var crypto = require('crypto');
var redis = require('redis');

var config;
var callbackReport;
var aaa;
var logger;
var options;
var redisClient;

var adviceDelay = 30000;

function callbackReportWrapper(requestId, rc, message) {
    try {
        message = message.replace(/:/g, ' ').replace(/#/g, ' ');
        message = message.replace(/\s+/g, ' ');
    }
    catch(err) {
        if (logger) {
            logger.warn('Exception on callbackReportWrapper: ' + err);
        }
    }

    if (rc != '00' && rc != '68') {
        try {
            var key = dupcheckKey(config.globals.gateway_name, task);
            redisClient.del(key);
        }
        catch(delErr) {
        }
    }

    callbackReport(requestId, rc, message);
}

function createRedisClient() {
    redisClient = redis.createClient(config.globals.redis_port, config.globals.redis_host);
}

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 {
        logger = new winston.Logger({
            transports: [
              new (winston.transports.Console)()
            ]
        });
    }

    createRedisClient();
}

function getRedisKey(task) {
    return config.globals.gateway_name + '.tid:' + task.requestId;
}

function dupcheckKey(gatewayName, task) {
    return 'DUPCHECK.gw:' + gatewayName + '.prod:' + task.remoteProduct + '.dest:' + task.destination + '.date:' + strftime('%Y%m%d', new Date);
}

function topupRequest(task, retry) {
    aaa.insertTaskToMongoDb(task);

    var key = dupcheckKey(config.globals.gateway_name, task);

    redisClient.get(key, function(err, data) {
        if (err) {
            callbackReportWrapper(task.requestId, '40', 'Gagal cek anti transaksi duplikat (redis error)');
            return;
        }

        if (!data) {
            logger.verbose('Belum ada trx dengan tujuan dan denom yang sama pada hari ini. Lanjutkan.');

            redisClient.set(key, JSON.stringify(task));
            redisClient.expire(key, 3600 * 24 * 2);

            voucherPay(task, retry);

        } else {

            try {
                var taskOnRedis = JSON.parse(data);
                if (task.requestId == taskOnRedis.requestId) {
                    logger.verbose('Sudah ada trx dengan tujuan dan denom yg sama, requestId jg sama. Lanjutkan dengan advice.')
                    advice(task, retry);
                } else {
                    logger.verbose('Sudah ada trx dengan tujuan dan denom yg sama, requestId tidak sama. Batalkan.')
                    callbackReportWrapper(task.requestId, '55', 'Transaksi duplikat')
                }
            }
            catch(errJSONParse) {
                callbackReportWrapper(task.requestId, '68', "error parsing json");
            }
        }
    });
}

function calculateSignature(cid, secret, dt) {
    return crypto.createHash('sha256').update(cid + dt + secret).digest().toString('hex');
}

function parsePaymentResponse(message) {
    var data = message.split('#');
    var retval = {};

    if (data[0] == 'ERROR') {
        retval = {
            status: data[0],
            rc: data[1],
            rcmessage: data[2],
        }

    } else {

        var i = 0;
        retval = {
            status: data[i++],
            rc: data[i++],
            rcmessage: data[i++],
            resptext: data[i++],
            dt: data[i++],
            refnum: data[i++],
            voucherid: data[i++],
            nominal: data[i++]
        }

        try {
            retval.sn = data[9];
        }
        catch(err) {
            retval.sn = null;
        }

        if (!retval.sn) {
            retval.sn = rehashRefnum(retval.refnum);
        }
    }

    retval.raw = message;

    return retval;
}

function rehashRefnum(refnum) {
    var hashed = refnum.replace(/^0+/, '');

    try {
        hashed = hashed.replace(/A/g, '1').replace(/B/g, '2').replace(/C/g, '3').replace(/D/g, '4').replace(/E/g, '5').replace(/F/g, '6');
        hashed = hashed.substring(0, 20);
    }
    catch(err) {
        logger.warn('Gagal rehashRefnum: ' + err);
        hashed = refnum;
    }
    return hashed;
}

function reportPaymentSuccess(task, response) {
    var message = 'SN=' + response.sn + '; ' + response.raw;

    logger.info('Report payment success to ST24', {task: task, response: response});

    callbackReportWrapper(task.requestId, '00', message);
}

function reportPaymentError(task, response) {
    var errorCode = getErrorCode(response.rcmessage);
    var st24rc = getST24ResponseCode(errorCode);

    if (st24rc == '68') {
        logger.info('Got pending response, requesting advice in ' + adviceDelay + 'ms', {task: task, response: response});
        setTimeout(advice, adviceDelay, task);
    }

    logger.info('Report payment error/pending to ST24', {supplier_rc: errorCode, st24_rc: st24rc, task: task, response: response});

    callbackReportWrapper(
        task.requestId,
        getST24ResponseCode(errorCode),
        response.raw
    );
}

function getST24ResponseCode(supplierResponseCode) {
    var st24rc = '40';

    if (supplierResponseCode.length == 1) {
        supplierResponseCode = '0' + supplierResponseCode;
    }

    if (supplierResponseCode == '05') {
        st24rc = '40';
    }
    else if (supplierResponseCode == '63') {
        // kata mp sih harusnya suspect tapi selama ini gagal hasilnya,
        // contoh: [63] ERROR Tidak ada Pembayaran ke:089664568903
        st24rc = '40';
    }
    else if (['00', '13', '14', '47', '68'].indexOf(supplierResponseCode) >= 0) {
        st24rc = supplierResponseCode;
    }
    else if (['15', '56'].indexOf(supplierResponseCode) >= 0) {
        // nomor tidak valid
        st24rc = '14';
    }
    else if (['18', '68'].indexOf(supplierResponseCode) >= 0) {
        st24rc = '68';
    }
    else if (supplierResponseCode == '67') {
        st24rc = '91'
    }
    else if (supplierResponseCode == '46') {
        st24rc = '40'

        if (aaa && config && config.globals && config.globals.pause_on_not_enough_balance
            && (config.globals.pause_on_not_enough_balance == '1')) {

            logger.warn('Not enough balance detected. Going to pause the system.');
            aaa.pause();
        }
    }

    return st24rc;
}

function getErrorCode(rcmessage) {
    try {
        var errorCode = rcmessage.match(/\[(\d+)\]/);
        return errorCode[1];
    }
    catch(err) {
        logger.warn('Empty RCMESSAGE, returning 68 as RC for safety');
        return '68';
    }
}

function generateDt(taskTimestamp) {
    if (!taskTimestamp) {
        return strftime('%Y%m%d', new Date());
    }

    return taskTimestamp.slice(0, 8);
}

function generateRequestOptions(userid, password, partnerUrl, task) {
    var dt = generateDt(task.timestamp);
    var sign = calculateSignature(userid, password, dt);

    var requestOptions = {
        method: 'GET',
        url: partnerUrl,
        qs: {
            modul: '',
            command: '',
            tujuan: task['destination'],
            voucherid: task['remoteProduct'],
            cid: userid,
            dt: dt,
            hc: sign,
            trxid: task['requestId'],
        }
    }

    return requestOptions;
}

function advice(task, retry) {

    if (retry === null || retry === undefined) {
        retry = 10;
    }

    var requestOptions = generateRequestOptions(config.h2h_out.userid, config.h2h_out.password, config.h2h_out.partner, task);

    requestOptions.qs.modul = 'ISI';
    requestOptions.qs.command = 'ADV';

    logger.info('Requesting advice to supplier', {requestOptions: requestOptions});
    request(requestOptions, function(requestError, requestResponse, requestResponseBody) {
        if (requestError || requestResponse.statusCode != 200) {
            logger.warn('Advice error', {error: requestError, http_response: requestResponse.statusCode});

            if (retry > 0) {
                logger.warn('Going to retry advice in ' + adviceDelay + 'ms', {task: task, retry: retry});
                setTimeout(advice, adviceDelay, task, --retry);
            }

            return;
        }

        var paymentResponse = parsePaymentResponse(requestResponseBody);
        logger.info('Got advice payment response', {paymentResponse: paymentResponse});

        if (paymentResponse.status == 'SUCCESS') {
            reportPaymentSuccess(task, paymentResponse);
        }
        else {
            reportPaymentError(task, paymentResponse);
        }
    });

}

function voucherPay(task) {
    var requestOptions = generateRequestOptions(config.h2h_out.userid, config.h2h_out.password, config.h2h_out.partner, task);

    requestOptions.qs.modul = 'ISI';
    requestOptions.qs.command = 'PAY';

    logger.info('Requesting auto payment to supplier', {requestOptions: requestOptions});
    request(requestOptions, function(requestError, requestResponse, requestResponseBody) {
        if (requestError) {
            logger.warn('Request error', {error: requestError});

            setTimeout(advice, adviceDelay, task);
            return;
        }

        if (requestResponse.statusCode != 200) {
            logger.warn('HTTP response status code is not 200', {http_response: requestResponse.statusCode});

            setTimeout(advice, adviceDelay, task);
            return;
        }

        logger.info('Supplier response: ' + requestResponseBody);

        var paymentResponse = parsePaymentResponse(requestResponseBody);
        logger.info('Got payment response', {paymentResponse: paymentResponse});

        if (paymentResponse.status == 'SUCCESS') {
            reportPaymentSuccess(task, paymentResponse);
        }
        else {
            reportPaymentError(task, paymentResponse);
        }
    });
}

exports.start = start;
exports.topupRequest = topupRequest;
exports.calculateSignature = calculateSignature;
exports.parsePaymentResponse = parsePaymentResponse;
exports.getErrorCode = getErrorCode;
exports.getST24ResponseCode = getST24ResponseCode;
exports.generateDt = generateDt;