Commit 8124d89a46a7a0e4fb006fee3e9bfd63019e6dc5

Authored by Adhidarma Hadiwinoto
1 parent 0173521371
Exists in master

ready to test

Showing 4 changed files with 509 additions and 1 deletions Side-by-side Diff

anti-same-day-dupe.js
... ... @@ -0,0 +1,150 @@
  1 +var redis = require('redis');
  2 +var LRU = require('lru-cache');
  3 +var moment = require('moment');
  4 +
  5 +var config;
  6 +var logger;
  7 +var redisClient;
  8 +
  9 +var taskCache = LRU({max: 100, maxAge: 1000 * 3600 * 2});
  10 +
  11 +function createRedisClient(host, port) {
  12 + try {
  13 + redisClient = redis.createClient(port, host);
  14 + logger.verbose(__filename + ': Redis client for task history created');
  15 + } catch(err) {
  16 + logger.warn(__filename + ": Error creating redis client to " + host + ':' + port);
  17 + process.exit(1);
  18 + }
  19 +}
  20 +
  21 +function init(options) {
  22 + if (!options) {
  23 + console.log('Undefined options, terminating....');
  24 + process.exit(1);
  25 + }
  26 +
  27 + if (options.config) {
  28 + config = options.config;
  29 + } else {
  30 + console.log('Undefined options.config, terminating....')
  31 + process.exit(1);
  32 + }
  33 +
  34 + if (options && options.logger) {
  35 + logger = options.logger;
  36 + } else {
  37 + console.log('Undefined options.logger, terminating....')
  38 + process.exit(1);
  39 + }
  40 +
  41 + createRedisClient(config.globals.redis_host, config.globals.redis_port);
  42 +}
  43 +
  44 +function getKey(task, chipInfo) {
  45 + var today = moment(task.timestamp, 'YYYYMMDDHHmmss').format('YYYYMMDD');
  46 + return chipInfo + '.antiSameDayDupe.trx.date:' + today + '.rProd:' + task.remoteProduct.toUpperCase() + '.dest:' + task.destination ;
  47 +}
  48 +
  49 +function register(task, cb) {
  50 + var key = getKey(task, config.globals.gateway_name);
  51 +
  52 + taskCache.set(key, task);
  53 + saveToRedis(task,cb);
  54 +}
  55 +
  56 +function saveToRedis(task, cb) {
  57 + var key = getKey(task, config.globals.gateway_name);
  58 + logger.verbose('Saving task', {key: key, task: task});
  59 +
  60 + redisClient.set(key, JSON.stringify(task), function() {
  61 + redisClient.expire(key, 3600*24);
  62 + if (cb) {
  63 + cb();
  64 + }
  65 + });
  66 +}
  67 +
  68 +function createDummyTask(remoteProduct, destination) {
  69 + return {
  70 + remoteProduct: remoteProduct,
  71 + destination: destination,
  72 + timestamp: moment().format('YYYYMMDD'),
  73 + }
  74 +}
  75 +
  76 +function get(remoteProduct, destination, cb) {
  77 + var dummyTask = createDummyTask(remoteProduct, destination);
  78 +
  79 + var key = getKey(dummyTask, config.globals.gateway_name);
  80 + var task = taskCache.get(key);
  81 +
  82 + if (task) {
  83 + cb(null, task);
  84 + }
  85 + else {
  86 + getFromRedis(remoteProduct, destination, cb);
  87 + }
  88 +}
  89 +
  90 +function getFromRedis(remoteProduct, destination, cb) {
  91 + var dummyTask = createDummyTask(remoteProduct, destination);
  92 +
  93 + var key = getKey(dummyTask, config.globals.gateway_name, moment().format('YYYYMMDD'));
  94 + redisClient.get(key, function(err, result) {
  95 + if (err) {
  96 + logger.warn('antiDupe.get: error getting task from redis', {key: key, params: dummyTask});
  97 +
  98 + cb(err, null);
  99 + return;
  100 + }
  101 +
  102 + var task = {};
  103 +
  104 + try {
  105 + task = JSON.parse(result);
  106 + }
  107 + catch(e) {
  108 + logger.warn('antiDupe.get: Can not parse result', {key: key, params: dummyTask, data: result});
  109 + err = "Can not parse task";
  110 + cb(err, null);
  111 + return;
  112 + }
  113 +
  114 + cb(err, task);
  115 + });
  116 +}
  117 +
  118 +function check(task, cbNoDupe, cbDupe, cbDupeWithSameReqId) {
  119 + if (Number(config.globals.no_same_day_dupe_check)) {
  120 + logger.verbose('Skipping same day dupe check because of config.globals.no_same_day_dupe_check');
  121 + cbNoDupe(task);
  122 + }
  123 +
  124 + get(task.remoteProduct, task.destination, function(err, archivedTask) {
  125 + if (err) {
  126 + logger.warn('Error on checking same day duplicate', {task: task});
  127 + cbNoDupe(task);
  128 + return;
  129 + }
  130 +
  131 + if (archivedTask && archivedTask.requestId) {
  132 + if (task.requestId == archivedTask.requestId) {
  133 + logger.verbose('Duplicate trx on same day with same requestId', {task: task});
  134 + cbDupeWithSameReqId(task);
  135 + return;
  136 + }
  137 +
  138 + logger.verbose('Duplicate trx on same day', {task: task, archivedTask: archivedTask});
  139 + cbDupe(task);
  140 + return;
  141 + }
  142 +
  143 + register(task, function() {
  144 + cbNoDupe(task);
  145 + });
  146 + });
  147 +}
  148 +
  149 +exports.init = init;
  150 +exports.check = check;
... ... @@ -22,8 +22,12 @@
22 22 "author": "Adhidarma Hadiwinoto <me@adhisimon.org>",
23 23 "license": "ISC",
24 24 "dependencies": {
  25 + "lru-cache": "^4.0.1",
  26 + "moment": "^2.14.1",
  27 + "redis": "^2.6.2",
25 28 "request": "^2.74.0",
26 29 "sate24": "git+http://gitlab.kodesumber.com/reload97/node-sate24.git",
27   - "sate24-expresso": "git+http://gitlab.kodesumber.com/reload97/sate24-expresso.git"
  30 + "sate24-expresso": "git+http://gitlab.kodesumber.com/reload97/sate24-expresso.git",
  31 + "xml2js": "^0.4.17"
28 32 }
29 33 }
... ... @@ -0,0 +1,223 @@
  1 +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
  2 +
  3 +var request = require('request');
  4 +var xml2js = require('xml2js');
  5 +
  6 +var resendDelay = require('sate24/resend-delay.js');
  7 +var taskHistory = require('./task-history.js');
  8 +var antiSameDayDupe = require('./anti-same-day-dupe.js');
  9 +
  10 +var config;
  11 +var aaa;
  12 +var _callbackReport;
  13 +var logger;
  14 +
  15 +var xmlBuilder = new xml2js.Builder({rootName: 'xml'});
  16 +
  17 +function start(options) {
  18 + if (!options) {
  19 + console.log('Undefined options, terminating....');
  20 + process.exit(1);
  21 + }
  22 +
  23 + if (options.config) {
  24 + config = options.config;
  25 + } else {
  26 + console.log('Undefined options.config, terminating....')
  27 + process.exit(1);
  28 + }
  29 +
  30 + if (options.aaa) {
  31 + aaa = options.aaa;
  32 + _callbackReport = options.aaa.callbackReportWithPushToMongoDb;
  33 + } else {
  34 + console.log('Undefined options.aaa, terminating....')
  35 + process.exit(1);
  36 + }
  37 +
  38 + if (options && options.logger) {
  39 + logger = options.logger;
  40 + } else {
  41 + console.log('Undefined options.logger, terminating....')
  42 + process.exit(1);
  43 + }
  44 +
  45 + resendDelay.init({
  46 + config: config,
  47 + topupRequest: _topupStatus,
  48 + logger: logger
  49 + });
  50 +
  51 + taskHistory.init(options);
  52 + antiSameDayDupe.init(options);
  53 +}
  54 +
  55 +function callbackReport(requestId, responseCode, msg) {
  56 + if (responseCode != '68') {
  57 + resendDelay.cancel(requestId);
  58 +
  59 + } else {
  60 + taskHistory.get(requestId, function(err, task) {
  61 + if (task) {
  62 + resendDelay.register(task);
  63 + }
  64 + });
  65 + }
  66 +
  67 + _callbackReport(requestId, responseCode, msg);
  68 +}
  69 +
  70 +function parseResponse(task, response) {
  71 + var xmlParser = xml2js.parseString;
  72 +
  73 + xmlParser(response, function(err, result) {
  74 + if (err) {
  75 + var msg = 'Error parsing response. ' + err;
  76 + logger.warn(msg, {task: task, response: response, err: err});
  77 + callbackReport(task.requestId, '68', msg);
  78 + return;
  79 + }
  80 +
  81 + logger.verbose('Response parsed', {result: result, task: task});
  82 +
  83 + var rc = '68';
  84 +
  85 + var partnerResponseCode;
  86 + try {
  87 + partnerResponseCode = result.hpay.responseCode[0];
  88 + } catch(e) {}
  89 +
  90 + var partnerMessage = '';
  91 + try {
  92 + partnerMessage = result.hpay.responseMessage[0];
  93 + } catch(e) {}
  94 +
  95 + if (partnerResponseCode == '00') {
  96 + rc = '00';
  97 + }
  98 + else if ((partnerResponseCode == '99') && (partnerMessage == 'Member Not Found')) {
  99 + rc = '40';
  100 + }
  101 + else if (partnerResponseCode == '99') {
  102 + rc = '68';
  103 + }
  104 + else {
  105 + rc = '40';
  106 + }
  107 +
  108 + var msg = '';
  109 + if (partnerResponseCode) {
  110 + msg = partnerResponseCode;
  111 + }
  112 +
  113 + if (partnerMessage) {
  114 + msg = msg + ' ' + partnerMessage;
  115 + }
  116 + msg = msg.trim();
  117 +
  118 + callbackReport(task.requestId, rc, msg);
  119 + });
  120 +}
  121 +
  122 +function requestToPartner(methodName, task) {
  123 + var payload = createPayload(methodName, task, config.h2h_out.userid, config.h2h_out.noid, config.h2h_out.password);
  124 + if (!payload) {
  125 + callbackReport(task.requestId, '40', 'ERROR: Undefined payload');
  126 + return;
  127 + }
  128 +
  129 + var requestOpts = {
  130 + url: config.h2h_out.partner,
  131 + method: "POST",
  132 + body: payload,
  133 + headers: {
  134 + 'Content-Type': 'text/xml',
  135 + }
  136 + }
  137 +
  138 + logger.verbose('Requesting to partner', {methodName: methodName, task: task, requestOpts: requestOpts});
  139 + request(requestOpts, function (err, response, responseBody) {
  140 + if (err) {
  141 + var rc = '68';
  142 + if (methodName === 'pay') {
  143 + rc = '40';
  144 + }
  145 +
  146 + var msg = 'Error requesting pay method. ' + err;
  147 + callbackReport(task.requestId, rc, msg);
  148 + return;
  149 + }
  150 +
  151 + logger.info('Got direct response from partner', {task: task, responseBody: responseBody});
  152 + parseResponse(task, responseBody);
  153 + });
  154 +}
  155 +
  156 +
  157 +function topupRequest(task) {
  158 + if (!aaa.isTodayTrx(task)) {
  159 + logger.warn('Maaf, transaksi beda hari tidak dapat dilakukan');
  160 + callbackReport(task.requestId, '68', 'Maaf, transaksi beda hari tidak dapat dilakukan');
  161 + resendDelay.cancel(task);
  162 + return;
  163 + }
  164 +
  165 + antiSameDayDupe.check(task, _topupRequest, onSameDayDupe, _topupStatus);
  166 +}
  167 +
  168 +function _topupRequest(task) {
  169 + taskHistory.put(task, function() {
  170 + requestToPartner('pay', task);;
  171 + });
  172 +}
  173 +
  174 +function _topupStatus(task) {
  175 + requestToPartner('checking', task);
  176 +}
  177 +
  178 +function onSameDayDupe(task) {
  179 + callbackReport(task.requestId, '55', 'Transaksi duplikat dalam satu hari yang sama');
  180 +}
  181 +
  182 +function extractProductDetail(remoteProduct) {
  183 + var product;
  184 +
  185 + try {
  186 + product = remoteProduct.split(',');
  187 + }
  188 + catch(e) {
  189 + logger.warn('extractProductDetail: exception on split');
  190 + return null;
  191 + }
  192 +
  193 + if (product.length !== 3) {
  194 + logger.warn('extractProductDetail: product.length <> 3');
  195 + return null;
  196 + }
  197 +
  198 + return {product: product[0], productType: product[1], nominal: product[2]};
  199 +}
  200 +
  201 +function createPayload(transactionType, task, user, noid, pin) {
  202 + var product = extractProductDetail(task.remoteProduct);
  203 + if (!product) {
  204 + logger.warn('createPayload: undefined product');
  205 + return;
  206 + }
  207 +
  208 + var payload = {
  209 + transactionType: transactionType,
  210 + product: product.product,
  211 + productType: product.productType,
  212 + idpel: task.destination,
  213 + pin: pin,
  214 + noid: noid,
  215 + user: user,
  216 + nominal: product.nominal
  217 + };
  218 +
  219 + return xmlBuilder.buildObject(payload);
  220 +}
  221 +
  222 +exports.start = start;
  223 +exports.topupRequest = topupRequest;
... ... @@ -0,0 +1,131 @@
  1 +var redis = require('redis');
  2 +var LRU = require('lru-cache');
  3 +
  4 +var config;
  5 +var logger;
  6 +var redisClient;
  7 +
  8 +var taskHistory = LRU({max: 100, maxAge: 1000 * 3600 * 2});
  9 +
  10 +function createRedisClient(host, port) {
  11 + try {
  12 + redisClient = redis.createClient(port, host);
  13 + logger.verbose(__filename + ': Redis client for task history created');
  14 + } catch(err) {
  15 + logger.warn(__filename + ": Error creating redis client to " + host + ':' + port);
  16 + process.exit(1);
  17 + }
  18 +}
  19 +
  20 +function init(options) {
  21 + if (!options) {
  22 + console.log('Undefined options, terminating....');
  23 + process.exit(1);
  24 + }
  25 +
  26 + if (options.config) {
  27 + config = options.config;
  28 + } else {
  29 + console.log('Undefined options.config, terminating....')
  30 + process.exit(1);
  31 + }
  32 +
  33 + if (options && options.logger) {
  34 + logger = options.logger;
  35 + } else {
  36 + console.log('Undefined options.logger, terminating....')
  37 + process.exit(1);
  38 + }
  39 +
  40 + createRedisClient(config.globals.redis_host, config.globals.redis_port);
  41 +}
  42 +
  43 +function getKey(task) {
  44 + var requestId;
  45 +
  46 + if (typeof task === 'string') {
  47 + requestId = task;
  48 + } else {
  49 + try {
  50 + requestId = task.requestId;
  51 + }
  52 + catch(e) {
  53 + return;
  54 + }
  55 + }
  56 +
  57 + return config.globals.gateway_name + '.smithsonian.hist.rid:' + requestId;
  58 +}
  59 +
  60 +function put(task, cb) {
  61 + var key = getKey(task, config.globals.gateway_name);
  62 + logger.verbose('Saving task to history LRU', {key: key, task: task});
  63 +
  64 + try {
  65 + taskHistory.set(key, JSON.parse(JSON.stringify(task)));
  66 + } catch (e) { }
  67 +
  68 + putToRedis(task, cb);
  69 +}
  70 +
  71 +function putToRedis(task, cb) {
  72 + if (!redisClient) {
  73 + logger.verbose('Not saving to redis because of undefined redisClient')
  74 + if (cb) { cb(); }
  75 + return;
  76 + }
  77 +
  78 + var key = getKey(task, config.globals.gateway_name);
  79 + logger.verbose('Saving task to redis', {key: key, task: task});
  80 +
  81 + redisClient.set(key, JSON.stringify(task), function() {
  82 + redisClient.expire(key, 3600*24*30);
  83 + if (cb) {
  84 + cb();
  85 + }
  86 + });
  87 +}
  88 +
  89 +function get(task, cb) {
  90 + logger.verbose('Getting task from history', {task: task});
  91 + var key = getKey(task, config.globals.gateway_name);
  92 + var archive = taskHistory.get(key);
  93 +
  94 + if (archive) {
  95 + if (cb) { cb(null, archive); }
  96 + }
  97 + else {
  98 + getFromRedis(task, cb);
  99 + }
  100 +}
  101 +
  102 +function getFromRedis(task, cb) {
  103 + if (!redisClient) {
  104 + if (cb) { cb(null, null); }
  105 + return;
  106 + }
  107 +
  108 + var key = getKey(task, config.globals.gateway_name);
  109 + redisClient.get(key, function(err, result) {
  110 + if (err) {
  111 + logger.warn('Error retrieving task from redis', {err: err});
  112 + cb(err, null);
  113 + return;
  114 + }
  115 +
  116 + var task;
  117 + try {
  118 + task = JSON.parse(result);
  119 + }
  120 + catch(e) {
  121 + logger.warn('Exception on parsing redis result as a json', {err: e});
  122 + cb(e, null);
  123 + }
  124 +
  125 + cb(null, task);
  126 + })
  127 +}
  128 +
  129 +exports.init = init;
  130 +exports.get = get;
  131 +exports.put = put;