diff --git a/package.json b/package.json index 57c4623..8b64ca1 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,11 @@ "author": "Adhidarma Hadiwinoto <gua@adhisimon.org>", "license": "ISC", "dependencies": { + "redis": "^2.6.0-2", + "request": "^2.72.0", "sate24": "git+http://gitlab.kodesumber.com/reload97/node-sate24.git", "sate24-expresso": "git+http://gitlab.kodesumber.com/reload97/sate24-expresso.git", + "strftime": "^0.9.2", "winston": "^2.2.0" } } diff --git a/partner-masterpulsa-voucher.js b/partner-masterpulsa-voucher.js new file mode 100644 index 0000000..d8eab93 --- /dev/null +++ b/partner-masterpulsa-voucher.js @@ -0,0 +1,284 @@ +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 = 10000; + +function createRedisClient() { + redisClient = redis.createClient(config.globals.redis_port, config.globals.redis_host); +} + +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)() + ] + }); + } + + createRedisClient(); +} + +function getRedisKey(task) { + return config.globals.gateway_name + '.tid:' + task.requestId; +} + +function putTaskToRedis(task) { + var redisKey = getRedisKey(task); + var taskInJSON = JSON.stringify(task); + + redisClient.set(redisKey, taskInJSON); + redisClient.expire(redisKey, 3600*24*7); +} + +function topupRequest(task, retry) { + var redisKey = getRedisKey(task); + + redisClient.get(redisKey, function(err, result) { + if (err || !result) { + + //logger.info('Redis error or not found', {redisKey: redisKey, error: err, result: result}); + putTaskToRedis(task); + payVoucher(task); + + } else { + advice(task); + } + }); +} + +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 = { + raw: message + }; + + 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++] + } + } + + return retval; +} + +function reportPaymentSuccess(task, response) { + var message = 'SN=' + response.refnum + '; ' + response.raw; + + logger.info('Report payment success to ST24', {task: task, response: response}); + + callbackReport(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}); + + callbackReport( + task.requestId, + getST24ResponseCode(errorCode), + response.raw + ); +} + +function getST24ResponseCode(supplierResponseCode) { + var st24rc = '40'; + + if (supplierResponseCode.length == 1) { + supplierResponseCode = '0' + supplierResponseCode; + } + + if (['00', '13', '14', '47', '68'].indexOf(supplierResponseCode) >= 0) { + st24rc = supplierResponseCode; + } + else if (supplierResponseCode == '15') { + st24rc = '14'; + } + else if (['05', '18', '63', '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: request_error, 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.generateSN = generateSN; +exports.generateDt = generateDt;