Commit f83de7efb4d85f47b3e1c77ed45b0e0595ed0cd0

Authored by Adhidarma Hadiwinoto
1 parent 4c6bd69790
Exists in master

lru-cache taskHistory

Showing 2 changed files with 4 additions and 1 deletions Inline Diff

1 { 1 {
2 "name": "sate24-to-fm-bp", 2 "name": "sate24-to-fm-bp",
3 "version": "1.0.0", 3 "version": "1.0.0",
4 "description": "ST24 H2H-OUT to FM based on belanjapulsa.com", 4 "description": "ST24 H2H-OUT to FM based on belanjapulsa.com",
5 "main": "index.js", 5 "main": "index.js",
6 "scripts": { 6 "scripts": {
7 "test": "mocha" 7 "test": "mocha"
8 }, 8 },
9 "repository": { 9 "repository": {
10 "type": "git", 10 "type": "git",
11 "url": "git@gitlab.kodesumber.com:reload97/sate24-to-fm-bp.git" 11 "url": "git@gitlab.kodesumber.com:reload97/sate24-to-fm-bp.git"
12 }, 12 },
13 "keywords": [ 13 "keywords": [
14 "st24", 14 "st24",
15 "reload97", 15 "reload97",
16 "r97", 16 "r97",
17 "ppob", 17 "ppob",
18 "fm", 18 "fm",
19 "flashmachine" 19 "flashmachine"
20 ], 20 ],
21 "author": "Adhidarma Hadiwinoto <me@adhisimon.org>", 21 "author": "Adhidarma Hadiwinoto <me@adhisimon.org>",
22 "license": "ISC", 22 "license": "ISC",
23 "dependencies": { 23 "dependencies": {
24 "lru-cache": "^4.0.1",
24 "redis": "^2.6.2", 25 "redis": "^2.6.2",
25 "request": "^2.74.0", 26 "request": "^2.74.0",
26 "sate24": "git+http://gitlab.kodesumber.com/reload97/node-sate24.git", 27 "sate24": "git+http://gitlab.kodesumber.com/reload97/node-sate24.git",
27 "sate24-expresso": "git+http://gitlab.kodesumber.com/reload97/sate24-expresso.git", 28 "sate24-expresso": "git+http://gitlab.kodesumber.com/reload97/sate24-expresso.git",
28 "xml2js": "^0.4.17" 29 "xml2js": "^0.4.17"
29 }, 30 },
30 "devDependencies": { 31 "devDependencies": {
31 "should": "^11.1.0" 32 "should": "^11.1.0"
32 } 33 }
33 } 34 }
34 35
1 var xml2js = require('xml2js'); 1 var xml2js = require('xml2js');
2 var request = require('request'); 2 var request = require('request');
3 var http = require('http'); 3 var http = require('http');
4 var redis = require('redis'); 4 var redis = require('redis');
5 var resendDelay = require('sate24/resend-delay.js') 5 var resendDelay = require('sate24/resend-delay.js');
6 var LRU = require('lru-cache');
6 7
7 var aaa; 8 var aaa;
8 var _callbackReport; 9 var _callbackReport;
9 var config; 10 var config;
10 var logger; 11 var logger;
11 var redisClient; 12 var redisClient;
12 13
13 var xmlBuilder = new xml2js.Builder(); 14 var xmlBuilder = new xml2js.Builder();
15 var taskHistory = LRU({max: 500, maxAge: 1000 * 3600 * 2});
14 16
15 function start(options) { 17 function start(options) {
16 if (!options) { 18 if (!options) {
17 console.log('Undefined options, terminating....'); 19 console.log('Undefined options, terminating....');
18 process.exit(1); 20 process.exit(1);
19 } 21 }
20 22
21 if (options.config) { 23 if (options.config) {
22 config = options.config; 24 config = options.config;
23 } else { 25 } else {
24 console.log('Undefined options.config, terminating....') 26 console.log('Undefined options.config, terminating....')
25 process.exit(1); 27 process.exit(1);
26 } 28 }
27 29
28 if (options.aaa) { 30 if (options.aaa) {
29 aaa = options.aaa; 31 aaa = options.aaa;
30 _callbackReport = options.aaa.callbackReportWithPushToMongoDb; 32 _callbackReport = options.aaa.callbackReportWithPushToMongoDb;
31 } else { 33 } else {
32 console.log('Undefined options.aaa, terminating....') 34 console.log('Undefined options.aaa, terminating....')
33 process.exit(1); 35 process.exit(1);
34 } 36 }
35 37
36 if (options && options.logger) { 38 if (options && options.logger) {
37 logger = options.logger; 39 logger = options.logger;
38 } else { 40 } else {
39 console.log('Undefined options.logger, terminating....') 41 console.log('Undefined options.logger, terminating....')
40 process.exit(1); 42 process.exit(1);
41 } 43 }
42 44
43 createRedisClient(config.globals.redis_host, config.globals.redis_port); 45 createRedisClient(config.globals.redis_host, config.globals.redis_port);
44 createServer(); 46 createServer();
45 47
46 resendDelay.init({ 48 resendDelay.init({
47 config: config, 49 config: config,
48 topupRequest: topupStatus, 50 topupRequest: topupStatus,
49 logger: logger 51 logger: logger
50 }); 52 });
51 } 53 }
52 54
53 function topupRequest(task) { 55 function topupRequest(task) {
54 aaa.insertTaskToMongoDb(task); 56 aaa.insertTaskToMongoDb(task);
55 57
56 getTaskFromHistory(task, function(err, archivedTask) { 58 getTaskFromHistory(task, function(err, archivedTask) {
57 putTaskToHistory(task); 59 putTaskToHistory(task);
58 60
59 if (archivedTask) { 61 if (archivedTask) {
60 logger.info('Task has been executed before, going to checkStatus', {task: task, archivedTask: archivedTask}); 62 logger.info('Task has been executed before, going to checkStatus', {task: task, archivedTask: archivedTask});
61 topupStatus(task); 63 topupStatus(task);
62 } else { 64 } else {
63 _topupRequest(task); 65 _topupRequest(task);
64 } 66 }
65 }); 67 });
66 } 68 }
67 69
68 function _topupRequest(task) { 70 function _topupRequest(task) {
69 71
70 var payload = composeTopupMessage( 72 var payload = composeTopupMessage(
71 config.h2h_out.pin, 73 config.h2h_out.pin,
72 task.remoteProduct, 74 task.remoteProduct,
73 task.destination, 75 task.destination,
74 task.requestId 76 task.requestId
75 ); 77 );
76 78
77 var reqOpts = { 79 var reqOpts = {
78 url: config.h2h_out.partner, 80 url: config.h2h_out.partner,
79 method: "POST", 81 method: "POST",
80 body: payload, 82 body: payload,
81 headers: { 83 headers: {
82 'Content-Type': 'text/xml', 84 'Content-Type': 'text/xml',
83 } 85 }
84 }; 86 };
85 87
86 logger.verbose('Requesting TOPUP to partner', {reqOpts: reqOpts, payload: payload}); 88 logger.verbose('Requesting TOPUP to partner', {reqOpts: reqOpts, payload: payload});
87 request(reqOpts, function (err, response, body) { 89 request(reqOpts, function (err, response, body) {
88 if (err) { 90 if (err) {
89 var msg = 'Error requesting TOPUP to partner: ' + err; 91 var msg = 'Error requesting TOPUP to partner: ' + err;
90 logger.warn(msg, {task: task, err: err}); 92 logger.warn(msg, {task: task, err: err});
91 callbackReport(task.requestId, '68', msg); 93 callbackReport(task.requestId, '68', msg);
92 return; 94 return;
93 } 95 }
94 96
95 logger.verbose('Got a direct response from TOPUP', {response: body, task: task}); 97 logger.verbose('Got a direct response from TOPUP', {response: body, task: task});
96 topupResponseHandler(body, task.requestId, callbackReport); 98 topupResponseHandler(body, task.requestId, callbackReport);
97 }); 99 });
98 } 100 }
99 101
100 function topupStatus(task) { 102 function topupStatus(task) {
101 var payload = composeTopupStatusMessage( 103 var payload = composeTopupStatusMessage(
102 config.h2h_out.pin, 104 config.h2h_out.pin,
103 task.requestId 105 task.requestId
104 ); 106 );
105 107
106 var reqOpts = { 108 var reqOpts = {
107 url: config.h2h_out.partner, 109 url: config.h2h_out.partner,
108 method: "POST", 110 method: "POST",
109 body: payload, 111 body: payload,
110 headers: { 112 headers: {
111 'Content-Type': 'text/xml', 113 'Content-Type': 'text/xml',
112 } 114 }
113 }; 115 };
114 116
115 logger.verbose('Requesting TOPUPSTATUS to partner', {reqOpts: reqOpts, payload: payload}); 117 logger.verbose('Requesting TOPUPSTATUS to partner', {reqOpts: reqOpts, payload: payload});
116 request(reqOpts, function (err, response, body) { 118 request(reqOpts, function (err, response, body) {
117 if (err) { 119 if (err) {
118 var msg = 'Error requesting TOPUPSTATUS to partner: ' + err; 120 var msg = 'Error requesting TOPUPSTATUS to partner: ' + err;
119 logger.warn(msg, {task: task, err: err}); 121 logger.warn(msg, {task: task, err: err});
120 callbackReport(task.requestId, '68', msg); 122 callbackReport(task.requestId, '68', msg);
121 return; 123 return;
122 } 124 }
123 125
124 logger.verbose('Got a direct response from TOPUPSTATUS', {response: body, task: task}); 126 logger.verbose('Got a direct response from TOPUPSTATUS', {response: body, task: task});
125 topupResponseHandler(body, task.requestId, callbackReport); 127 topupResponseHandler(body, task.requestId, callbackReport);
126 }); 128 });
127 } 129 }
128 130
129 function topupResponseHandler(xmlResponse, _requestId, cb) { 131 function topupResponseHandler(xmlResponse, _requestId, cb) {
130 var xmlParser = xml2js.parseString; 132 var xmlParser = xml2js.parseString;
131 xmlParser(xmlResponse, function(err, data) { 133 xmlParser(xmlResponse, function(err, data) {
132 var msg; 134 var msg;
133 var requestId; 135 var requestId;
134 var rc = '68'; 136 var rc = '68';
135 137
136 if (_requestId) { 138 if (_requestId) {
137 requestId = _requestId; 139 requestId = _requestId;
138 } 140 }
139 141
140 if (err) { 142 if (err) {
141 msg = 'Error parsing xml response: ' + err; 143 msg = 'Error parsing xml response: ' + err;
142 144
143 if (logger) { 145 if (logger) {
144 logger.warn(msg, {err: err, response: xmlResponse, task: task}); 146 logger.warn(msg, {err: err, response: xmlResponse, task: task});
145 } else { 147 } else {
146 console.log(msg); 148 console.log(msg);
147 } 149 }
148 } else { 150 } else {
149 151
150 try { 152 try {
151 msg = data.fm.message 153 msg = data.fm.message
152 } 154 }
153 catch(e) { 155 catch(e) {
154 msg = 'Unknown message' 156 msg = 'Unknown message'
155 } 157 }
156 158
157 if (data.fm.status == '0') { 159 if (data.fm.status == '0') {
158 160
159 rc = '00'; 161 rc = '00';
160 msg = modifyMessageWithSn(msg); 162 msg = modifyMessageWithSn(msg);
161 163
162 } else if (data.fm.status == '1') { 164 } else if (data.fm.status == '1') {
163 rc = '68'; 165 rc = '68';
164 } else if (data.fm.status == '2') { 166 } else if (data.fm.status == '2') {
165 rc = '40'; 167 rc = '40';
166 } else if (data.fm.status == '3') { 168 } else if (data.fm.status == '3') {
167 rc = '40'; 169 rc = '40';
168 } else { 170 } else {
169 rc = '68'; 171 rc = '68';
170 } 172 }
171 173
172 if (data.fm.refTrxid) { 174 if (data.fm.refTrxid) {
173 requestId = data.fm.refTrxid; 175 requestId = data.fm.refTrxid;
174 } 176 }
175 177
176 } 178 }
177 179
178 cb(requestId, rc, msg, xmlResponse) 180 cb(requestId, rc, msg, xmlResponse)
179 }); 181 });
180 } 182 }
181 183
182 function callbackReport(requestId, responseCode, msg, rawResponse) { 184 function callbackReport(requestId, responseCode, msg, rawResponse) {
183 if (requestId) { 185 if (requestId) {
184 _callbackReport(requestId, responseCode, msg, null, rawResponse); 186 _callbackReport(requestId, responseCode, msg, null, rawResponse);
185 } else { 187 } else {
186 logger.warn('Undefined requestId, not sending callbackReport', {rc: responseCode, msg: msg, rawResponse: rawResponse}); 188 logger.warn('Undefined requestId, not sending callbackReport', {rc: responseCode, msg: msg, rawResponse: rawResponse});
187 } 189 }
188 190
189 } 191 }
190 192
191 function getSnFromMessage(msg) { 193 function getSnFromMessage(msg) {
192 try { 194 try {
193 var matches = msg.match(/SN:(\w+)/); 195 var matches = msg.match(/SN:(\w+)/);
194 return matches[1]; 196 return matches[1];
195 } 197 }
196 catch(e) { 198 catch(e) {
197 return; 199 return;
198 } 200 }
199 } 201 }
200 202
201 function modifyMessageWithSn(msg) { 203 function modifyMessageWithSn(msg) {
202 var sn = getSnFromMessage(msg); 204 var sn = getSnFromMessage(msg);
203 if (sn) { 205 if (sn) {
204 msg = 'SN=' + sn + '; ' + msg; 206 msg = 'SN=' + sn + '; ' + msg;
205 } 207 }
206 return msg; 208 return msg;
207 } 209 }
208 210
209 function composeTopupMessage(pin, product, destination, requestId) { 211 function composeTopupMessage(pin, product, destination, requestId) {
210 var data = {fm: { 212 var data = {fm: {
211 command: 'TOPUP', 213 command: 'TOPUP',
212 pin: pin, 214 pin: pin,
213 product: product, 215 product: product,
214 msisdn: destination, 216 msisdn: destination,
215 refTrxid: requestId 217 refTrxid: requestId
216 }}; 218 }};
217 219
218 return xmlBuilder.buildObject(data); 220 return xmlBuilder.buildObject(data);
219 } 221 }
220 222
221 function composeTopupStatusMessage(pin, requestId) { 223 function composeTopupStatusMessage(pin, requestId) {
222 var data = {fm: { 224 var data = {fm: {
223 command: 'TOPUPSTATUS', 225 command: 'TOPUPSTATUS',
224 pin: pin, 226 pin: pin,
225 refTrxid: requestId 227 refTrxid: requestId
226 }} 228 }}
227 229
228 return xmlBuilder.buildObject(data); 230 return xmlBuilder.buildObject(data);
229 } 231 }
230 232
231 function createServer() { 233 function createServer() {
232 var httpServer = http.createServer(function(request, response) { 234 var httpServer = http.createServer(function(request, response) {
233 235
234 logger.info('Got request from partner'); 236 logger.info('Got request from partner');
235 237
236 var body = ""; 238 var body = "";
237 req.on('data', function (chunk) { 239 req.on('data', function (chunk) {
238 body += chunk; 240 body += chunk;
239 }); 241 });
240 242
241 req.on('end', function () { 243 req.on('end', function () {
242 res.writeHead(200); 244 res.writeHead(200);
243 res.end('OK'); 245 res.end('OK');
244 246
245 topupResponseHandler(body, null, callbackReport); 247 topupResponseHandler(body, null, callbackReport);
246 248
247 }); 249 });
248 250
249 }); 251 });
250 252
251 httpServer.listen(config.h2h_out.listen_port, function() { 253 httpServer.listen(config.h2h_out.listen_port, function() {
252 logger.info('HTTP Reverse/Report server listen on port ' + config.h2h_out.listen_port); 254 logger.info('HTTP Reverse/Report server listen on port ' + config.h2h_out.listen_port);
253 }); 255 });
254 } 256 }
255 257
256 function createRedisClient(host, port) { 258 function createRedisClient(host, port) {
257 if (!host && !port) { 259 if (!host && !port) {
258 logger.info('Not creating redis client because unspecified host or port'); 260 logger.info('Not creating redis client because unspecified host or port');
259 return; 261 return;
260 } 262 }
261 263
262 try { 264 try {
263 redisClient = redis.createClient(port, host); 265 redisClient = redis.createClient(port, host);
264 } catch(err) { 266 } catch(err) {
265 logger.warn("Error creating redis client to " + host + ':' + port); 267 logger.warn("Error creating redis client to " + host + ':' + port);
266 } 268 }
267 } 269 }
268 270
269 function getTaskKey(task, chipInfo) { 271 function getTaskKey(task, chipInfo) {
270 var requestId; 272 var requestId;
271 273
272 if (typeof task === 'string') { 274 if (typeof task === 'string') {
273 requestId = task; 275 requestId = task;
274 } else { 276 } else {
275 try { 277 try {
276 requestId = task.requestId; 278 requestId = task.requestId;
277 } 279 }
278 catch(e) { 280 catch(e) {
279 logger.warn('Something wrong', {task: task}); 281 logger.warn('Something wrong', {task: task});
280 console.trace('Cekidot'); 282 console.trace('Cekidot');
281 process.exit(1); 283 process.exit(1);
282 } 284 }
283 285
284 } 286 }
285 287
286 if (!chipInfo && config && config.globals && config.globals.gateway_name) { 288 if (!chipInfo && config && config.globals && config.globals.gateway_name) {
287 chipInfo = config.globals.gateway_name; 289 chipInfo = config.globals.gateway_name;
288 } 290 }
289 291
290 return chipInfo + '.hitachi.rid:' + requestId; 292 return chipInfo + '.hitachi.rid:' + requestId;
291 } 293 }
292 294
293 295
294 function putTaskToHistory(task, cb) { 296 function putTaskToHistory(task, cb) {
295 if (Number(config.globals.no_dupe_check)) { 297 if (Number(config.globals.no_dupe_check)) {
296 if (cb) { cb(); } 298 if (cb) { cb(); }
297 return; 299 return;
298 } 300 }
299 var key = getTaskKey(task, config.globals.gateway_name); 301 var key = getTaskKey(task, config.globals.gateway_name);
300 logger.verbose('Saving task to history LRU', {key: key, task: task}); 302 logger.verbose('Saving task to history LRU', {key: key, task: task});
301 303
302 try { 304 try {
303 taskHistory.set(key, JSON.parse(JSON.stringify(task))); 305 taskHistory.set(key, JSON.parse(JSON.stringify(task)));
304 } catch (e) { } 306 } catch (e) { }
305 307
306 putTaskToRedis(task, cb); 308 putTaskToRedis(task, cb);
307 } 309 }
308 310
309 function putTaskToRedis(task, cb) { 311 function putTaskToRedis(task, cb) {
310 if (!redisClient) { 312 if (!redisClient) {
311 logger.verbose('Not saving to redis because of undefined redisClient') 313 logger.verbose('Not saving to redis because of undefined redisClient')
312 if (cb) { cb(); } 314 if (cb) { cb(); }
313 return; 315 return;
314 } 316 }
315 317
316 var key = getTaskKey(task, config.globals.gateway_name); 318 var key = getTaskKey(task, config.globals.gateway_name);
317 logger.verbose('Saving task to redis', {key: key, task: task}); 319 logger.verbose('Saving task to redis', {key: key, task: task});
318 320
319 redisClient.set(key, JSON.stringify(task), function() { 321 redisClient.set(key, JSON.stringify(task), function() {
320 redisClient.expire(key, 3600*24*30); 322 redisClient.expire(key, 3600*24*30);
321 if (cb) { 323 if (cb) {
322 cb(); 324 cb();
323 } 325 }
324 }); 326 });
325 } 327 }
326 328
327 function getTaskFromHistory(task, cb) { 329 function getTaskFromHistory(task, cb) {
328 logger.verbose('Getting task from history', {task: task}); 330 logger.verbose('Getting task from history', {task: task});
329 var key = getTaskKey(task, config.globals.gateway_name); 331 var key = getTaskKey(task, config.globals.gateway_name);
330 var archive = taskHistory.get(key); 332 var archive = taskHistory.get(key);
331 333
332 if (archive) { 334 if (archive) {
333 if (cb) { cb(null, archive); } 335 if (cb) { cb(null, archive); }
334 } 336 }
335 else { 337 else {
336 getTaskFromRedis(task, cb); 338 getTaskFromRedis(task, cb);
337 } 339 }
338 } 340 }
339 341
340 function getTaskFromRedis(task, cb) { 342 function getTaskFromRedis(task, cb) {
341 if (!redisClient) { 343 if (!redisClient) {
342 if (cb) { cb(null, null); } 344 if (cb) { cb(null, null); }
343 return; 345 return;
344 } 346 }
345 347
346 var key = getTaskKey(task, config.globals.gateway_name); 348 var key = getTaskKey(task, config.globals.gateway_name);
347 redisClient.get(key, function(err, result) { 349 redisClient.get(key, function(err, result) {
348 if (err) { 350 if (err) {
349 logger.warn('Error retrieving task from redis', {err: err}); 351 logger.warn('Error retrieving task from redis', {err: err});
350 cb(err, null); 352 cb(err, null);
351 return; 353 return;
352 } 354 }
353 355
354 var task; 356 var task;
355 try { 357 try {
356 task = JSON.parse(result); 358 task = JSON.parse(result);
357 } 359 }
358 catch(e) { 360 catch(e) {
359 logger.warn('Exception on parsing redis result as a json', {err: e}); 361 logger.warn('Exception on parsing redis result as a json', {err: e});
360 } 362 }
361 363
362 cb(null, task); 364 cb(null, task);
363 }) 365 })
364 } 366 }
365 367
366 368
367 exports.start = start; 369 exports.start = start;
368 exports.topupRequest = topupRequest; 370 exports.topupRequest = topupRequest;
369 exports.composeTopupMessage = composeTopupMessage; 371 exports.composeTopupMessage = composeTopupMessage;
370 exports.getSnFromMessage = getSnFromMessage; 372 exports.getSnFromMessage = getSnFromMessage;
371 exports.modifyMessageWithSn = modifyMessageWithSn; 373 exports.modifyMessageWithSn = modifyMessageWithSn;
372 374