Commit 5544ecfdf51a9b95c3e0418d5c71fdd97e4f08ec

Authored by Adhidarma Hadiwinoto
1 parent 6c417c09c4
Exists in master

partner-im

Showing 4 changed files with 446 additions and 147 deletions Side-by-side Diff

... ... @@ -5,11 +5,10 @@ var password;
5 5  
6 6 var callbacks;
7 7  
8   -
9   -function onLoginSuccessful(data) {
  8 +function onOnline(data) {
10 9 logger.info('XMPP login successful', {data: data});
11   - if (callbacks.onLoginSuccessful) {
12   - callbacks.onLoginSuccessful();
  10 + if (callbacks.onOnline) {
  11 + callbacks.onOnline();
13 12 }
14 13 }
15 14  
... ... @@ -20,6 +19,11 @@ function onPM(sender, msg) {
20 19 }
21 20 }
22 21  
  22 +function onError(err) {
  23 + logger.warn('XMPP error, terminating in 3 secs', {err: err});
  24 + setTimeout(process.exit, 3000, 1);
  25 +}
  26 +
23 27 function onSubscribe(sender) {
24 28 xmpp.acceptSubscription(sender);
25 29 }
... ... @@ -30,7 +34,7 @@ function init(_username, _password, _logger, _callbacks) {
30 34 logger = _logger;
31 35 callbacks = _callbacks;
32 36  
33   - xmpp.on('online', onLoginSuccessful);
  37 + xmpp.on('online', onOnline);
34 38 xmpp.on('chat', onPM);
35 39 xmpp.on('error', onError);
36 40 xmpp.on('subscribe', onSubscribe)
1 1 var fs = require('fs');
2 2 var ini = require('ini');
3 3 var expresso = require('sate24-expresso');
4   -var partner = require('./partner-xmpp');
5 4 var config = ini.parse(fs.readFileSync(__dirname + '/config.ini', 'utf-8'));
6 5  
7 6 process.chdir(__dirname);
... ... @@ -9,7 +8,9 @@ process.chdir(__dirname);
9 8 var logger = require('sate24/logger.js').start();
10 9 var HttpServer = require('sate24/httpserver.js');
11 10 var aaa = require('sate24/aaa.js');
12   -var partner = require('./partner-ym.js');
  11 +var partner = require('./partner-im.js');
  12 +var imAdaptor = require('./adaptor-xmpp');
  13 +
13 14  
14 15 var matrix = aaa.prepareMatrix();
15 16  
... ... @@ -19,6 +20,7 @@ var options = {
19 20 'config': config,
20 21 'matrix': matrix,
21 22 'partner': partner,
  23 + 'imAdaptor': imAdaptor
22 24 }
23 25  
24 26 var httpServer = HttpServer.start(config, options);
... ... @@ -0,0 +1,433 @@
  1 +var redis = require('redis');
  2 +var moment = require('moment');
  3 +var LRU = require('lru-cache');
  4 +
  5 +var config;
  6 +var logger;
  7 +var aaa;
  8 +var callbackReport;
  9 +var partner;
  10 +var imConfig;
  11 +var redisClient;
  12 +
  13 +var resendHandlers = LRU({max: 2000, maxAge: 1000 * 3600 * 36});
  14 +
  15 +function dumpStats() {
  16 + if (config.globals.auto_resend_delay_secs) {
  17 + logger.verbose('DUMP STATS', {
  18 + 'resendHandlers:length': resendHandlers.length,
  19 + 'resendHandlers:itemCount': resendHandlers.itemCount,
  20 + });
  21 + }
  22 +}
  23 +
  24 +function dumpStatsPeriodic() {
  25 + var dumpStatsInterval = 1000 * 120;
  26 + if (config.globals.dump_stats_interval_secs) {
  27 + dumpStatsInterval = config.globals.dump_stats_interval_secs * 1000;
  28 + }
  29 + logger.verbose('Dump stats every ' + dumpStatsInterval / 1000 + ' secs (override it with config.globals.dump_stats_interval_secs)');
  30 + setInterval(dumpStats, dumpStatsInterval);
  31 +}
  32 +
  33 +function init(options) {
  34 + if (options && options.config) {
  35 + config = options.config;
  36 + } else {
  37 + console.log(__filename + ': Undefined options.config. Terminating.');
  38 + process.exit(230);
  39 + }
  40 +
  41 + if (options && options.logger) {
  42 + logger = options.logger;
  43 + } else {
  44 + console.log(__filename + ': Undefined options.logger. Terminating.');
  45 + process.exit(231);
  46 + }
  47 +
  48 + if (options && options.partner) {
  49 + partner = options.partner;
  50 + } else {
  51 + logger.warn(__filename + ': Undefined options.partner. Terminating.');
  52 + process.exit(232);
  53 + }
  54 +
  55 + if (options && options.aaa) {
  56 + aaa = options.aaa;
  57 + } else {
  58 + logger.warn(__filename + ': Undefined options.aaa. Terminating.');
  59 + process.exit(233);
  60 + }
  61 +
  62 + if (options && options.aaa && options.aaa.callbackReport) {
  63 + callbackReport = options.aaa.callbackReport;
  64 + } else {
  65 + logger.warn(__filename + ': Unknown options.aaa.callbackReport')
  66 + process.exit(234);
  67 + }
  68 +
  69 + if (options && options.imAdaptor) {
  70 + imAdaptor = options.imAdaptor;
  71 + } else {
  72 + logger.warn(__filename + ': Unknown options.imAdaptor')
  73 + process.exit(235);
  74 + }
  75 +
  76 + var imAdaptorcallbacks = {
  77 + onOnline: onOnline,
  78 + onPM: onPM,
  79 + }
  80 +
  81 + imAdaptor.init(config.h2h_out.im_username, config.h2h_out.im_password, logger, imAdaptorcallbacks);
  82 +
  83 + createRedisClient(config.globals.redis_host, config.globals.redis_port);
  84 + readImConfig();
  85 +
  86 + dumpStatsPeriodic();
  87 +}
  88 +
  89 +function createRedisClient(host, port) {
  90 + try {
  91 + redisClient = redis.createClient(port, host);
  92 + } catch(err) {
  93 + logger.warn("Error creating redis client to " + host + ':' + port);
  94 + }
  95 +}
  96 +
  97 +function readImConfig(filename) {
  98 +
  99 + if (!filename) {
  100 + filename = process.cwd() + '/config.im.json';
  101 + }
  102 +
  103 + try {
  104 + imConfig = require(filename);
  105 + }
  106 + catch(e) {
  107 + imConfig = {};
  108 + }
  109 +
  110 + logger.verbose('IM Config', {imConfig: imConfig});
  111 + return imConfig;
  112 +}
  113 +
  114 +function getPatternFromMessage(message, pattern, patternMatchIndex) {
  115 + var re = new RegExp(pattern);
  116 + var matches = message.match(re);
  117 +
  118 + if (!matches) {
  119 + return null;
  120 + }
  121 +
  122 + if (patternMatchIndex < matches.length) {
  123 + return matches[patternMatchIndex];
  124 + } else {
  125 + return null;
  126 + }
  127 +}
  128 +
  129 +function getPatternsFromMessage(message, patterns) {
  130 + var patternCount = patterns.length;
  131 + for (var i = 0; i < patternCount; i++) {
  132 +
  133 + var pattern = patterns[i];
  134 +
  135 + var result = getPatternFromMessage(message, pattern.pattern, pattern.matchIndex);
  136 + if (result) {
  137 + return result;
  138 + }
  139 + }
  140 +}
  141 +
  142 +function getTaskKey(task, chipInfo, today) {
  143 + if (!chipInfo && config && config.globals && config.globals.gateway_name) {
  144 + chipInfo = config.globals.gateway_name;
  145 + }
  146 +
  147 + if (task.timestamp && !today) {
  148 + today = moment(task.timestamp, 'YYYYMMDDHHmmss').format('YYYYMMDD');
  149 + }
  150 +
  151 + return chipInfo + '.trx.date:' + today + '.rProduct:' + task.remoteProduct.toUpperCase() + '.dest:' + task.destination ;
  152 +}
  153 +
  154 +function saveTask(task, cb) {
  155 + var key = getTaskKey(task, config.globals.gateway_name);
  156 + logger.verbose('Saving task', {key: key, task: task});
  157 +
  158 + redisClient.set(key, JSON.stringify(task), function() {
  159 + redisClient.expire(key, 3600*24);
  160 + if (cb) {
  161 + cb();
  162 + }
  163 + });
  164 +}
  165 +
  166 +function getTask(remoteProduct, destination, cb) {
  167 + var dummyTask = {
  168 + remoteProduct: remoteProduct,
  169 + destination: destination,
  170 + }
  171 +
  172 + var key = getTaskKey(dummyTask, config.globals.gateway_name, moment().format('YYYYMMDD'));
  173 + redisClient.get(key, function(err, result) {
  174 + if (err) {
  175 + logger.verbose('getTask: task not found', {key: key, params: dummyTask});
  176 +
  177 + cb(err, null);
  178 + return;
  179 + }
  180 +
  181 + var task = {};
  182 +
  183 + try {
  184 + task = JSON.parse(result);
  185 + }
  186 + catch(e) {
  187 + logger.warn('getTask: Can not parse result', {key: key, params: dummyTask, data: result});
  188 + err = "Can not parse result"
  189 + }
  190 + cb(err, task);
  191 + });
  192 +}
  193 +
  194 +function deleteTask(remoteProduct, destination) {
  195 + var dummyTask = {
  196 + remoteProduct: remoteProduct,
  197 + destination: destination,
  198 + }
  199 +
  200 + var key = getTaskKey(dummyTask, config.globals.gateway_name, moment().format('YYYYMMDD'));
  201 +
  202 + try {
  203 + redisClient.del(key);
  204 + }
  205 + catch(e) {};
  206 +}
  207 +
  208 +function createMessage(pattern, keywords) {
  209 + var msg = pattern;
  210 +
  211 + for (var key in keywords) {
  212 + msg = msg.replace('[' + key + ']', keywords[key]);
  213 + }
  214 + return msg;
  215 +}
  216 +
  217 +function getRemoteProductFromMessage(msg) {
  218 + return getPatternsFromMessage(msg, imConfig.product_patterns);
  219 +}
  220 +
  221 +function getDestinationFromMessage(msg) {
  222 + return getPatternsFromMessage(msg, imConfig.destination_patterns);
  223 +}
  224 +
  225 +function getSnFromMessage(msg) {
  226 + return getPatternsFromMessage(msg, imConfig.sn_patterns);
  227 +}
  228 +
  229 +function getRcFromMessage(msg) {
  230 + var rcs = imConfig.response_codes;
  231 + var rcsCount = rcs.length;
  232 +
  233 + for (var i = 0; i < rcsCount; i++) {
  234 +
  235 + var item = rcs[i];
  236 + var re = new RegExp(item.pattern);
  237 + if (msg.search(re) != -1) {
  238 + return item.rc;
  239 + }
  240 +
  241 + }
  242 + return '68';
  243 +}
  244 +
  245 +function isAllowedFrom(sender) {
  246 + if (!config || !config.h2h_out || !config.h2h_out.allowed_response_from) {
  247 + return true;
  248 + }
  249 +
  250 + whitelist = config.h2h_out.allowed_response_from.split(',');
  251 + whitelistCount = whitelist.length;
  252 +
  253 + for(var i=0; i<whitelistCount; i++) {
  254 + if (sender == whitelist[i]) {
  255 + return true;
  256 + }
  257 + }
  258 +
  259 + return false;
  260 +}
  261 +
  262 +function checkForSameDayDuplicate(task, cbNoDupe, cbDupe, cbDupeWithSameReqId) {
  263 + getTask(task.remoteProduct, task.destination, function(err, archivedTask) {
  264 + if (err) {
  265 + logger.warn('Error on checking same day duplicate', {task: task});
  266 + cbNoDupe(task);
  267 + return;
  268 + }
  269 +
  270 + if (archivedTask && archivedTask.requestId) {
  271 + if (cbDupeWithSameReqId && task.requestId == archivedTask.requestId) {
  272 + logger.verbose('Duplicate trx on same day with same requestId', {task: task});
  273 + cbDupeWithSameReqId(task, archivedTask);
  274 + return;
  275 + }
  276 +
  277 + logger.verbose('Duplicate trx on same day', {task: task, archivedTask: archivedTask});
  278 + cbDupe(task, archivedTask);
  279 + return;
  280 + }
  281 +
  282 + cbNoDupe(task);
  283 + });
  284 +}
  285 +
  286 +function registerResendDelay(task) {
  287 + if (!task.requestId) {
  288 + logger.warn('Invalid task on resendDelay')
  289 + return;
  290 + }
  291 +
  292 + if (!config || !config.globals || !Number(config.globals.auto_resend_delay_secs)) {
  293 + return;
  294 + }
  295 +
  296 + if (!partner || !partner.topupRequest) {
  297 + logger.warn('Skip request resend delay because partner.topupRequest is not exists');
  298 + return;
  299 + }
  300 +
  301 + var retry = 10;
  302 + var oldHandler = resendHandlers.get(task.requestId);
  303 + if (oldHandler) {
  304 + retry = oldHandler.retry - 1;
  305 +
  306 + try {
  307 + cancelResendDelay(task);
  308 + }
  309 + catch(e) {}
  310 + }
  311 +
  312 + if (retry <= 0) {
  313 + logger.verbose('Resend delay retry exceeded', {task: task});
  314 + cancelResendDelay(task);
  315 + return;
  316 + }
  317 +
  318 + logger.verbose('Registering resend delay task request', {task: task, delay: config.globals.auto_resend_delay_secs, retry: retry});
  319 + var handlerData = {
  320 + handler: setTimeout(partner.topupRequest, config.globals.auto_resend_delay_secs * 1000, task),
  321 + task: task,
  322 + retry: retry
  323 + }
  324 +
  325 + resendHandlers.set(task.requestId, handlerData);
  326 +}
  327 +
  328 +function cancelResendDelay(task) {
  329 + if (!task || !task.requestId) {
  330 + logger.warn('Invalid task on cancelResendDelay');
  331 + return;
  332 + }
  333 +
  334 + var oldHandler = resendHandlers.get(task.requestId);
  335 + if (!oldHandler) {
  336 + return;
  337 + }
  338 +
  339 + logger.verbose('Canceling resend delay', {task: task});
  340 +
  341 + try {
  342 + if (oldHandler.handler) {
  343 + clearTimeout(oldHandler.handler);
  344 + }
  345 + }
  346 + catch(e) {};
  347 +
  348 + try {
  349 + resendHandlers.del(task.requestId);
  350 + }
  351 + catch(e) {};
  352 +}
  353 +
  354 +function onOnline() {
  355 + logger.info('Login successful, resuming aaa communication');
  356 + try {
  357 + aaa.resume();
  358 + }
  359 + catch(e) {
  360 + logger.warn('Exception on resuming aaa module: ' + e);
  361 + }
  362 +}
  363 +
  364 +function onPM(from, msg) {
  365 +
  366 + if (!isAllowedFrom(from)) {
  367 + logger.info('Ignoring message from unknown sender', {from: from, msg: msg});
  368 + return;
  369 + }
  370 +
  371 + var remoteProduct = getRemoteProductFromMessage(msg);
  372 + var destination = getDestinationFromMessage(msg);
  373 +
  374 + logger.verbose("Extract remoteProduct and destination from message", {remoteProduct: remoteProduct, destination: destination, msg: msg});
  375 +
  376 + if (!remoteProduct || !destination) {
  377 + logger.info('Missing remote product or destination', {remoteProduct: remoteProduct, destination: destination, msg: msg});
  378 + return;
  379 + }
  380 +
  381 + logger.info('Got report from partner', {remoteProduct: remoteProduct, destination: destination, msg: msg});
  382 + getTask(remoteProduct, destination, function(err, task) {
  383 + if (err) {
  384 + logger.warn('Error getting relevant task');
  385 + return;
  386 + }
  387 +
  388 + if (!task) {
  389 + logger.warn('Something wrong, undefined task without error')
  390 + return;
  391 + }
  392 +
  393 + logger.verbose('Got relevant task', {task: task, msg: msg});
  394 +
  395 + var rc = getRcFromMessage(msg);
  396 + if (rc == '00') {
  397 + var sn = getSnFromMessage(msg);
  398 + if (sn) {
  399 + msg = 'SN=' + sn + ';' + msg;
  400 + }
  401 + }
  402 +
  403 + if (['00', '55', '68'].indexOf(rc) == -1) {
  404 + deleteTask(remoteProduct, destination);
  405 + }
  406 +
  407 + if (rc != '68') {
  408 + cancelResendDelay(task);
  409 + }
  410 +
  411 + callbackReport(task.requestId, rc, msg);
  412 + });
  413 +}
  414 +
  415 +exports.init = init;
  416 +exports.start = init;
  417 +exports.getPatternFromMessage = getPatternFromMessage;
  418 +exports.getPatternsFromMessage = getPatternsFromMessage;
  419 +exports.saveTask = saveTask;
  420 +exports.getTask = getTask;
  421 +exports.readImConfig = readImConfig;
  422 +exports.createMessage = createMessage;
  423 +exports.getRemoteProductFromMessage = getRemoteProductFromMessage;
  424 +exports.getDestinationFromMessage = getDestinationFromMessage;
  425 +exports.getRcFromMessage = getRcFromMessage;
  426 +exports.getSnFromMessage = getSnFromMessage;
  427 +exports.isAllowedFrom = isAllowedFrom;
  428 +exports.checkForSameDayDuplicate = checkForSameDayDuplicate;
  429 +exports.deleteTask = deleteTask;
  430 +exports.registerResendDelay = registerResendDelay;
  431 +exports.cancelResendDelay = cancelResendDelay;
  432 +exports.onOnline = onOnline;
  433 +exports.onPM = onPM;
partner-xmpp.js
... ... @@ -1,140 +0,0 @@
1   -var im = require('sate24/im.js')
2   -var imAdaptor = require('./adaptor-xmpp');
3   -
4   -var config;
5   -var aaa;
6   -var logger;
7   -var callbackReport;
8   -
9   -function onLoginSuccessful() {
10   - logger.info('Login successful, resuming aaa communication');
11   - aaa.resume();
12   -}
13   -
14   -function onPM(from, msg) {
15   -
16   - if (!im.isAllowedFrom(from)) {
17   - logger.info('Ignoring message from unknown sender', {from: from, msg: msg});
18   - return;
19   - }
20   -
21   - var remoteProduct = im.getRemoteProductFromMessage(msg);
22   - var destination = im.getDestinationFromMessage(msg);
23   -
24   - if (!remoteProduct && !destination) {
25   - logger.warn('Missing remote product or destination', {remoteProduct: remoteProduct, destination: destination, msg: msg});
26   - return;
27   - }
28   -
29   - logger.info('Got report from partner', {remoteProduct: remoteProduct, destination: destination, msg: msg});
30   - im.getTask(remoteProduct, destination, function(err, task) {
31   - if (err) {
32   - logger.warn('Error getting relevant task');
33   - return;
34   - }
35   -
36   - if (!task) {
37   - logger.warn('Something wrong, undefined task without error')
38   - return;
39   - }
40   -
41   - logger.verbose('Got relevant task', {task: task, msg: msg});
42   - var rc = im.getRcFromMessage(msg);
43   - if (rc == '00') {
44   - var sn = im.getSnFromMessage(msg);
45   - if (sn) {
46   - msg = 'SN=' + sn + ';' + msg;
47   - }
48   - }
49   -
50   - if (['00', '55', '68'].indexOf(rc) == -1) {
51   - im.deleteTask(remoteProduct, destination);
52   - }
53   -
54   - if (rc != '68') {
55   - im.cancelResendDelay(task);
56   - }
57   -
58   - callbackReport(task.requestId, rc, msg);
59   - });
60   -}
61   -
62   -function start(options) {
63   - if (options && options.config) {
64   - config = options.config;
65   - } else {
66   - console.log('Unknown options.config');
67   - process.exit('1');
68   - }
69   -
70   - if (options && options.aaa) {
71   - aaa = options.aaa;
72   - }
73   -
74   - if (options && options.aaa && options.aaa.callbackReport) {
75   - callbackReport = options.aaa.callbackReport;
76   - } else {
77   - console.log('Unknown options.aaa.callbackReport')
78   - process.exit(2);
79   - }
80   -
81   - if (options && options.logger) {
82   - logger = options.logger;
83   - } else {
84   - logger = new winston.Logger({
85   - transports: [
86   - new (winston.transports.Console)()
87   - ]
88   - });
89   - }
90   -
91   - var callbacks = {
92   - onLoginSuccessful: onLoginSuccessful,
93   - onPM: onPM,
94   - }
95   -
96   - im.init(options);
97   - imAdaptor.init(config.h2h_out.ym_id, config.h2h_out.ym_password, logger, callbacks);
98   -}
99   -
100   -function onSameDayDupe(task, archivedTask) {
101   - if (task.requestId == archivedTask.requestId) {
102   - logger.info('Mengulang trx untuk advice', {task: task});
103   - _topupRequest(task);
104   - } else {
105   - logger.info('Terdeteksi trx sama dalam satu hari yang sama', {task: task});
106   - callbackReport(task.requestId, '55', 'Terdeteksi trx sama dalam satu hari yang sama');
107   - }
108   -}
109   -
110   -function _topupRequest(task) {
111   - var pattern = config.h2h_out.request_pattern;
112   -
113   - var keywords = {
114   - remoteProduct: task.remoteProduct,
115   - destination: task.destination,
116   - pin: config.h2h_out.pin
117   - }
118   -
119   - im.saveTask(task, function() {
120   - im.registerResendDelay(task);
121   -
122   - var msg = im.createMessage(pattern, keywords);
123   - imAdaptor.sendMessage(config.h2h_out.partner, msg);
124   - });
125   -
126   -}
127   -
128   -function topupRequest(task) {
129   - if (!aaa.isTodayTrx(task)) {
130   - logger.warn('Maaf, transaksi beda hari tidak dapat dilakukan');
131   - callbackReport(task.requestId, '68', 'Maaf, transaksi beda hari tidak dapat dilakukan');
132   - im.cancelResendDelay(task);
133   - return;
134   - }
135   -
136   - im.checkForSameDayDuplicate(task, _topupRequest, onSameDayDupe, _topupRequest);
137   -}
138   -
139   -exports.start = start;
140   -exports.topupRequest = topupRequest;