sender.js 6.01 KB
const MODULE_NAME = 'CORE-CALLBACK.SENDER';

const axios = require('axios');
const config = require('komodo-sdk/config');
const validUrl = require('valid-url');
const logger = require('tektrans-logger');

const dumper = require('./dumper/sender');
const matrix = require('../matrix');

const webhookSender = require('../webhook-sender');

const HTTP_TIMEOUT = Number(
    config.callback_sender && config.callback_sender.http_timeout_ms,
) || 30 * 1000;

const SLEEP_BEFORE_RETRY_MS = Number(
    config.callback_sender && config.callback_sender.sleep_before_retry_ms,
) || 10 * 1000;

const MAX_RETRY = Number(
    config.callback_sender && config.callback_sender.max_retry,
) || 10;

logger.verbose(`${MODULE_NAME} 848B9104: Initialized`, {
    HTTP_TIMEOUT,
    SLEEP_BEFORE_RETRY_MS,
    MAX_RETRY,
});

const axiosHeaders = {
    'Content-Type': 'application/json',
    'User-Agent': 'KOMODO-HTTPGETX callback sender',
};

const sleep = require('../sleep');
const urlConcatQs = require('../url-concat-qs');

const sender = async (data, xid, retry) => {
    const params = {
        httpgetx_xid: xid,
        command: data.command || 'BUY',

        request_id: data.request_id && data.request_id.toString(),
        transaction_id: data.transaction_id && data.transaction_id.toString(),
        transaction_date: data.transaction_date,

        store_name: data.store_name,
        terminal_name: data.terminal_name,

        product_name: data.product_name,
        destination: data.destination,

        rc: data.rc,
        sn: data.sn || undefined,
        amount: Number(data.amount) || undefined,
        ending_balance: Number(data.ending_balance) || undefined,

        message: data.message,

        bill_count: Number(data.bill_count) || undefined,
        bill_amount: Number(data.bill_amount) || undefined,
        fee_per_bill: Number(data.fee) || undefined,
        fee_total: Number(data.fee_total) || undefined,

        bill_detail: data.bill_detail || undefined,
        struk: data.struk || undefined,
    };

    if (data.command === 'INQUIRY' && data.amount_to_charge) {
        params.amount_to_charge = data.amount_to_charge;
    }

    const isPostpaid = ['INQUIRY', 'PAY'].indexOf(data.command) >= 0;
    const isHttpPost = isPostpaid;

    try {
        const webhookType = 'KOMODO-CENTER-HTTPGETX.CORE-CALLBACK';
        webhookSender(xid, webhookType, params);
    } catch (e) {
        logger.warn(`${MODULE_NAME} 1E2BF2CD: Exception calling webhookSender`, {
            xid,
        });
    }

    if (!data.reverse_url || !validUrl.isWebUri(data.reverse_url)) {
        logger.verbose(`${MODULE_NAME} C4FF18FB: Ignoring invalid reverse url`, {
            xid,
            reverseUrl: data && data.reverse_url,
        });

        return;
    }

    const endpointUrl = isHttpPost ? data.reverse_url : urlConcatQs(data.reverse_url, params);

    logger.info(`${MODULE_NAME} 8B6A4CEC: Sending to PARTNER`, {
        xid,
        retry: retry || 0,
        isPostpaid,
        isHttpPost,
        endpointUrl,
    });

    let responseToDump;
    let errorResponseToDump;

    try {
        const response = isHttpPost
            ? await axios.post(data.reverse_url, params, {
                timeout: HTTP_TIMEOUT,
                headers: axiosHeaders,
            })
            : await axios.get(data.reverse_url, {
                params,
                timeout: HTTP_TIMEOUT,
                headers: axiosHeaders,
            });

        responseToDump = response;

        matrix.callback_sender.sent += 1;
        matrix.callback_sender.active_count += 1;
        matrix.callback_sender.active_sending[xid] = {
            ts: new Date(),
            trxId: data.trx_id,
            reverseUrl: data.reverse_url,
        };

        if (isPostpaid) {
            matrix.callback_sender.sent_using_post += 1;
        } else {
            matrix.callback_sender.sent_using_get += 1;
        }

        logger.info(`${MODULE_NAME} 3641FBD7: Has been sent to PARTNER successfully`, {
            xid,
            retry,
            httpStatus: response.status,
            responseBody: response && response.data,
        });
    } catch (e) {
        matrix.callback_sender.sent_failed += 1;
        matrix.callback_sender.last_error = {
            xid,
            ts: new Date(),
            eCode: e.code,
            eMessage: e.message,
            trxId: data.trx_id,
            reverseUrl: data.reverse_url,
            httpStatus: e.response && e.response.status,
            responseBody: e.response && e.response.data,
        };

        responseToDump = e.response && e.response.data;
        errorResponseToDump = e;

        logger.warn(`${MODULE_NAME} A1EC9E70: Failed on sending to PARTNER`, {
            xid,
            retry,
            maxRetry: MAX_RETRY,
            errCode: e.code,
            errMessage: e.message,
            reverseUrl: data.reverse_url,
            endpointUrl,
            httpStatus: e.response && e.response.status,
            responseBody: e.response && e.response.data,
        });

        if (e.response && e.response.status) {
            logger.verbose(`${MODULE_NAME} 10AE785C: Skip retry on http status presence`, {
                xid,
                httpStatus: e.response && e.response.status,
            });
            return;
        }

        if ((retry || 0) < MAX_RETRY) {
            await sleep(SLEEP_BEFORE_RETRY_MS);

            logger.verbose(`${MODULE_NAME} D8958695: Going to retry sending CORE-CALLBACK TO PARTNER`, {
                xid,
                retried: retry,
                sleepTime: SLEEP_BEFORE_RETRY_MS,
            });

            sender(data, xid, (retry || 0) + 1);
        }
    } finally {
        matrix.callback_sender.active_count -= 1;
        if (matrix.callback_sender.active_sending[xid]) {
            delete matrix.callback_sender.active_sending[xid];
        }

        dumper(
            xid,
            isHttpPost ? 'POST' : 'GET',
            endpointUrl,
            params,
            responseToDump,
            errorResponseToDump,
        );
    }
};

module.exports = sender;