Commit 1cdb39dd784df47208e45fcc3c22ac2ecf8d20d3
1 parent
cd70b1f414
Exists in
master
topupCheck on duplicate
Showing 2 changed files with 66 additions and 1 deletions Inline Diff
package.json
1 | { | 1 | { |
2 | "name": "sate24-to-bayarkilat", | 2 | "name": "sate24-to-bayarkilat", |
3 | "version": "1.0.0", | 3 | "version": "1.0.0", |
4 | "description": "ST24 ke BayarKilat.com", | 4 | "description": "ST24 ke BayarKilat.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-bayarkilat.git" | 11 | "url": "git@gitlab.kodesumber.com:reload97/sate24-to-bayarkilat.git" |
12 | }, | 12 | }, |
13 | "keywords": [ | 13 | "keywords": [ |
14 | "st24", | 14 | "st24", |
15 | "ppob", | 15 | "ppob", |
16 | "reload97", | 16 | "reload97", |
17 | "r97", | 17 | "r97", |
18 | "bayarkilat" | 18 | "bayarkilat" |
19 | ], | 19 | ], |
20 | "author": "Adhidarma Hadiwinoto <me@adhisimon.org>", | 20 | "author": "Adhidarma Hadiwinoto <me@adhisimon.org>", |
21 | "license": "ISC", | 21 | "license": "ISC", |
22 | "dependencies": { | 22 | "dependencies": { |
23 | "redis": "^2.6.2", | ||
23 | "request": "^2.72.0", | 24 | "request": "^2.72.0", |
24 | "sate24": "git+http://gitlab.kodesumber.com/reload97/node-sate24.git", | 25 | "sate24": "git+http://gitlab.kodesumber.com/reload97/node-sate24.git", |
25 | "sate24-expresso": "git+http://gitlab.kodesumber.com/reload97/sate24-expresso.git", | 26 | "sate24-expresso": "git+http://gitlab.kodesumber.com/reload97/sate24-expresso.git", |
26 | "xml2js": "^0.4.16" | 27 | "xml2js": "^0.4.16" |
27 | }, | 28 | }, |
28 | "devDependencies": { | 29 | "devDependencies": { |
29 | "should": "^9.0.2" | 30 | "should": "^9.0.2" |
30 | } | 31 | } |
31 | } | 32 | } |
32 | 33 |
partner-bayarkilat.js
1 | var request = require('request'); | 1 | var request = require('request'); |
2 | var url = require('url'); | 2 | var url = require('url'); |
3 | var winston = require('winston'); | 3 | var winston = require('winston'); |
4 | var xml2jsParser = require('xml2js').parseString; | 4 | var xml2jsParser = require('xml2js').parseString; |
5 | var redis = require('redis'); | ||
6 | |||
5 | 7 | ||
6 | var config; | 8 | var config; |
7 | var aaa; | 9 | var aaa; |
8 | var callbackReport; | 10 | var callbackReport; |
9 | var logger; | 11 | var logger; |
12 | var redisClient; | ||
10 | 13 | ||
11 | var maxCheckRetry = 20; | 14 | var maxCheckRetry = 20; |
12 | var delayBeforeCheckRetry = 30 * 1000; | 15 | var delayBeforeCheckRetry = 30 * 1000; |
13 | 16 | ||
14 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; | 17 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; |
15 | 18 | ||
19 | function createRedisClient(host, port) { | ||
20 | try { | ||
21 | redisClient = redis.createClient(port, host); | ||
22 | } catch(err) { | ||
23 | logger.warn("Error creating redis client to " + host + ':' + port); | ||
24 | } | ||
25 | } | ||
26 | |||
16 | function start(_config, _callbackReport, options) { | 27 | function start(_config, _callbackReport, options) { |
17 | config = _config; | 28 | config = _config; |
18 | callbackReport = _callbackReport; | 29 | callbackReport = _callbackReport; |
19 | 30 | ||
20 | if (options && options.aaa) { | 31 | if (options && options.aaa) { |
21 | aaa = options.aaa; | 32 | aaa = options.aaa; |
22 | } | 33 | } |
23 | 34 | ||
24 | if (options && options.logger) { | 35 | if (options && options.logger) { |
25 | logger = options.logger; | 36 | logger = options.logger; |
26 | } else { | 37 | } else { |
27 | logger = new winston.Logger({ | 38 | logger = new winston.Logger({ |
28 | transports: [ | 39 | transports: [ |
29 | new (winston.transports.Console)() | 40 | new (winston.transports.Console)() |
30 | ] | 41 | ] |
31 | }); | 42 | }); |
32 | } | 43 | } |
44 | |||
45 | createRedisClient(config.globals.redis_host, config.globals.redis_port); | ||
33 | } | 46 | } |
34 | 47 | ||
35 | function createRequestOptions(methodName, task) { | 48 | function createRequestOptions(methodName, task) { |
36 | var partnerUrl = url.parse(config.h2h_out.partner); | 49 | var partnerUrl = url.parse(config.h2h_out.partner); |
37 | var product = prepareRemoteProductCode(task.remoteProduct); | 50 | var product = prepareRemoteProductCode(task.remoteProduct); |
38 | 51 | ||
39 | var destination = task.destination; | 52 | var destination = task.destination; |
40 | if (methodName == 'CHECKING') { | 53 | if (methodName == 'CHECKING') { |
41 | destination = paddingDestination(destination); | 54 | destination = paddingDestination(destination); |
42 | } | 55 | } |
43 | 56 | ||
44 | var options = { | 57 | var options = { |
45 | url: config.h2h_out.partner, | 58 | url: config.h2h_out.partner, |
46 | qs: { | 59 | qs: { |
47 | request: methodName + '*' | 60 | request: methodName + '*' |
48 | + task.requestId + '*' | 61 | + task.requestId + '*' |
49 | + product.product + '*' | 62 | + product.product + '*' |
50 | + product.productDetail + '*' | 63 | + product.productDetail + '*' |
51 | + destination + '*' | 64 | + destination + '*' |
52 | + product.nominal + '*' | 65 | + product.nominal + '*' |
53 | + '0*' | 66 | + '0*' |
54 | + config.h2h_out.noid + '*' | 67 | + config.h2h_out.noid + '*' |
55 | + config.h2h_out.userid + '*' | 68 | + config.h2h_out.userid + '*' |
56 | + config.h2h_out.password | 69 | + config.h2h_out.password |
57 | } | 70 | } |
58 | }; | 71 | }; |
59 | 72 | ||
60 | return options; | 73 | return options; |
61 | } | 74 | } |
62 | 75 | ||
63 | function topupCheck(task, retry) { | 76 | function topupCheck(task, retry) { |
64 | if (retry === null || retry === undefined) { | 77 | if (retry === null || retry === undefined) { |
65 | retry = maxCheckRetry + 1; | 78 | retry = maxCheckRetry + 1; |
66 | } | 79 | } |
67 | 80 | ||
68 | retry = retry - 1; | 81 | retry = retry - 1; |
69 | 82 | ||
70 | if (retry < 0) { | 83 | if (retry < 0) { |
71 | var message = 'Max retry check transaction retry exceeded'; | 84 | var message = 'Max retry check transaction retry exceeded'; |
72 | var response = { | 85 | var response = { |
73 | raw: message, | 86 | raw: message, |
74 | parsed: { | 87 | parsed: { |
75 | MESSAGE: message | 88 | MESSAGE: message |
76 | } | 89 | } |
77 | } | 90 | } |
78 | aaa.pushResponseToMongoDb(task, response, '68'); | 91 | aaa.pushResponseToMongoDb(task, response, '68'); |
79 | callbackReport(task.requestId, '68', message); | 92 | callbackReport(task.requestId, '68', message); |
80 | return; | 93 | return; |
81 | } | 94 | } |
82 | 95 | ||
83 | requestToPartner('CHECKING', task, retry); | 96 | requestToPartner('CHECKING', task, retry); |
84 | } | 97 | } |
85 | 98 | ||
86 | function topupRequest(task) { | 99 | function topupRequest(task) { |
87 | requestToPartner('PURCHASE', task); | 100 | if (!aaa.isTodayTrx(task)) { |
101 | logger.warn('Maaf, transaksi beda hari tidak dapat dilakukan'); | ||
102 | callbackReport(task.requestId, '68', 'Maaf, transaksi beda hari tidak dapat dilakukan'); | ||
103 | return; | ||
104 | } | ||
105 | |||
106 | getTaskForDupeCheck(task, function(err, dupeTask) { | ||
107 | if (dupeTask) { | ||
108 | logger.info('Duplicate task detected, requesting topupCheck', {task}); | ||
109 | topupCheck(task); | ||
110 | } else { | ||
111 | requestToPartner('PURCHASE', task); | ||
112 | } | ||
113 | }); | ||
114 | |||
88 | } | 115 | } |
89 | 116 | ||
90 | function requestToPartner(methodName, task, retry) { | 117 | function requestToPartner(methodName, task, retry) { |
91 | aaa.insertTaskToMongoDb(task); | 118 | aaa.insertTaskToMongoDb(task); |
92 | 119 | ||
93 | var options = createRequestOptions(methodName, task); | 120 | var options = createRequestOptions(methodName, task); |
94 | 121 | ||
95 | logger.info('Requesting to partner', {requestOption: options}); | 122 | logger.info('Requesting to partner', {requestOption: options}); |
96 | request(options, function(error, response, body) { | 123 | request(options, function(error, response, body) { |
97 | if (error) { | 124 | if (error) { |
98 | logger.warn('Error requesting to partner', {error: error}); | 125 | logger.warn('Error requesting to partner', {error: error}); |
99 | callbackReport(task.requestId, '68', 'Error requesting to partner. ' + error); | 126 | callbackReport(task.requestId, '68', 'Error requesting to partner. ' + error); |
100 | 127 | ||
101 | var _response = { | 128 | var _response = { |
102 | raw: 'Error requesting to partner. ' + error, | 129 | raw: 'Error requesting to partner. ' + error, |
103 | parsed: { | 130 | parsed: { |
104 | MESSAGE: 'Error requesting to partner. ' + error, | 131 | MESSAGE: 'Error requesting to partner. ' + error, |
105 | error: error | 132 | error: error |
106 | } | 133 | } |
107 | } | 134 | } |
108 | aaa.pushResponseToMongoDb(task, _response, '68'); | 135 | aaa.pushResponseToMongoDb(task, _response, '68'); |
109 | 136 | ||
110 | setTimeout( | 137 | setTimeout( |
111 | topupCheck, | 138 | topupCheck, |
112 | delayBeforeCheckRetry, | 139 | delayBeforeCheckRetry, |
113 | task, | 140 | task, |
114 | retry | 141 | retry |
115 | ); | 142 | ); |
116 | 143 | ||
117 | return; | 144 | return; |
118 | } | 145 | } |
119 | 146 | ||
120 | if (response.statusCode != 200) { | 147 | if (response.statusCode != 200) { |
121 | var message = 'Partner response with http status code other that 200 (' + response.statusCode + ')'; | 148 | var message = 'Partner response with http status code other that 200 (' + response.statusCode + ')'; |
122 | 149 | ||
123 | logger.warn(message); | 150 | logger.warn(message); |
124 | callbackReport(task.requestId, '68', message); | 151 | callbackReport(task.requestId, '68', message); |
125 | 152 | ||
126 | var _response = { | 153 | var _response = { |
127 | raw: 'Partner response with http status code other that 200 (' + response.statusCode + ')', | 154 | raw: 'Partner response with http status code other that 200 (' + response.statusCode + ')', |
128 | parsed: { | 155 | parsed: { |
129 | MESSAGE: 'Partner response with http status code other that 200 (' + response.statusCode + ')', | 156 | MESSAGE: 'Partner response with http status code other that 200 (' + response.statusCode + ')', |
130 | responseHttpStatus: response.statusCode, | 157 | responseHttpStatus: response.statusCode, |
131 | responseBody: body, | 158 | responseBody: body, |
132 | } | 159 | } |
133 | } | 160 | } |
134 | aaa.pushResponseToMongoDb(task, _response, '68'); | 161 | aaa.pushResponseToMongoDb(task, _response, '68'); |
135 | 162 | ||
136 | setTimeout( | 163 | setTimeout( |
137 | topupCheck, | 164 | topupCheck, |
138 | delayBeforeCheckRetry, | 165 | delayBeforeCheckRetry, |
139 | task, | 166 | task, |
140 | retry | 167 | retry |
141 | ); | 168 | ); |
142 | 169 | ||
143 | return; | 170 | return; |
144 | } | 171 | } |
145 | 172 | ||
146 | logger.verbose('Got respose', {rawBody: body}); | 173 | logger.verbose('Got respose', {rawBody: body}); |
147 | parseResponse(body, task); | 174 | parseResponse(body, task); |
148 | }); | 175 | }); |
149 | } | 176 | } |
150 | 177 | ||
151 | function getSn(response) { | 178 | function getSn(response) { |
152 | try { | 179 | try { |
153 | var sn = response.xml.ket1[0]; | 180 | var sn = response.xml.ket1[0]; |
154 | return sn; | 181 | return sn; |
155 | } | 182 | } |
156 | catch (err) { | 183 | catch (err) { |
157 | return; | 184 | return; |
158 | } | 185 | } |
159 | } | 186 | } |
160 | 187 | ||
161 | function getHarga(response) { | 188 | function getHarga(response) { |
162 | try { | 189 | try { |
163 | var harga = response.xml.amount[0]; | 190 | var harga = response.xml.amount[0]; |
164 | return harga; | 191 | return harga; |
165 | } | 192 | } |
166 | catch (err) { | 193 | catch (err) { |
167 | return; | 194 | return; |
168 | } | 195 | } |
169 | } | 196 | } |
170 | 197 | ||
171 | function getSaldo(response) { | 198 | function getSaldo(response) { |
172 | try { | 199 | try { |
173 | var saldo = response.xml.saldo[0]; | 200 | var saldo = response.xml.saldo[0]; |
174 | return saldo; | 201 | return saldo; |
175 | } | 202 | } |
176 | catch (err) { | 203 | catch (err) { |
177 | return; | 204 | return; |
178 | } | 205 | } |
179 | } | 206 | } |
180 | 207 | ||
181 | function parseResponse(body, task) { | 208 | function parseResponse(body, task) { |
182 | xml2jsParser(body, function(err, response) { | 209 | xml2jsParser(body, function(err, response) { |
183 | if (err) { | 210 | if (err) { |
184 | logger.warn('Error parsing XML', {error: err, task: task, responseBody: body}); | 211 | logger.warn('Error parsing XML', {error: err, task: task, responseBody: body}); |
185 | 212 | ||
186 | var message = 'Error parsing XML. ' + err + '. ' + body; | 213 | var message = 'Error parsing XML. ' + err + '. ' + body; |
187 | 214 | ||
188 | var _response = { | 215 | var _response = { |
189 | raw: body, | 216 | raw: body, |
190 | parsed: { | 217 | parsed: { |
191 | MESSAGE: message | 218 | MESSAGE: message |
192 | } | 219 | } |
193 | } | 220 | } |
194 | aaa.pushResponseToMongoDb(task, _response, '68'); | 221 | aaa.pushResponseToMongoDb(task, _response, '68'); |
195 | 222 | ||
196 | callbackReport(task.requestId, '68', message); | 223 | callbackReport(task.requestId, '68', message); |
197 | return; | 224 | return; |
198 | } | 225 | } |
199 | 226 | ||
200 | logger.info('Got response', {response: response}); | 227 | logger.info('Got response', {response: response}); |
201 | 228 | ||
202 | var responseCode; | 229 | var responseCode; |
203 | var message; | 230 | var message; |
204 | 231 | ||
205 | try { | 232 | try { |
206 | responseCode = response.xml.response_code[0]; | 233 | responseCode = response.xml.response_code[0]; |
207 | message = response.xml.response_message[0]; | 234 | message = response.xml.response_message[0]; |
208 | } | 235 | } |
209 | catch(errGetParam) { | 236 | catch(errGetParam) { |
210 | logger.warn('Exception saat parsing hasil', {error: errGetParam, task: task, responseBody: body}); | 237 | logger.warn('Exception saat parsing hasil', {error: errGetParam, task: task, responseBody: body}); |
211 | 238 | ||
212 | var _response = { | 239 | var _response = { |
213 | raw: body, | 240 | raw: body, |
214 | parsed: { | 241 | parsed: { |
215 | MESSAGE: 'Exception saat parsing hasil. ' + errGetParam, | 242 | MESSAGE: 'Exception saat parsing hasil. ' + errGetParam, |
216 | xml: response.xml | 243 | xml: response.xml |
217 | } | 244 | } |
218 | } | 245 | } |
219 | aaa.pushResponseToMongoDb(task, _response, '68'); | 246 | aaa.pushResponseToMongoDb(task, _response, '68'); |
220 | 247 | ||
221 | callbackReport(task.requestId, '68', 'Exception saat parsing hasil. ' + errGetParam); | 248 | callbackReport(task.requestId, '68', 'Exception saat parsing hasil. ' + errGetParam); |
222 | return; | 249 | return; |
223 | } | 250 | } |
224 | 251 | ||
225 | var st24rc; | 252 | var st24rc; |
226 | if (parseInt(responseCode) == 0) { | 253 | if (parseInt(responseCode) == 0) { |
227 | st24rc = '00'; | 254 | st24rc = '00'; |
228 | } | 255 | } |
229 | else if (parseInt(responseCode) == '99') { | 256 | else if (parseInt(responseCode) == '99') { |
230 | st24rc = '68' | 257 | st24rc = '68' |
231 | } | 258 | } |
232 | else { | 259 | else { |
233 | st24rc = '40'; | 260 | st24rc = '40'; |
234 | } | 261 | } |
235 | 262 | ||
236 | var st24message = message; | 263 | var st24message = message; |
237 | if (responseCode) { | 264 | if (responseCode) { |
238 | st24message = responseCode + ' ' + st24message; | 265 | st24message = responseCode + ' ' + st24message; |
239 | } | 266 | } |
240 | 267 | ||
241 | if (st24rc == '00') { | 268 | if (st24rc == '00') { |
242 | var sn = getSn(response); | 269 | var sn = getSn(response); |
243 | 270 | ||
244 | if (sn) { | 271 | if (sn) { |
245 | st24message = 'SN=' + sn + ';' + st24message + '. SN=' + sn; | 272 | st24message = 'SN=' + sn + ';' + st24message + '. SN=' + sn; |
246 | } | 273 | } |
247 | } | 274 | } |
248 | 275 | ||
249 | var harga = getHarga(response); | 276 | var harga = getHarga(response); |
250 | if (harga) { | 277 | if (harga) { |
251 | st24message = st24message + '. Harga ' + harga; | 278 | st24message = st24message + '. Harga ' + harga; |
252 | } | 279 | } |
253 | 280 | ||
254 | var saldo = getSaldo(response); | 281 | var saldo = getSaldo(response); |
255 | if (saldo) { | 282 | if (saldo) { |
256 | st24message = st24message + '. Saldo ' + saldo; | 283 | st24message = st24message + '. Saldo ' + saldo; |
257 | aaa.updateBalance(saldo); | 284 | aaa.updateBalance(saldo); |
258 | } | 285 | } |
259 | 286 | ||
260 | var _response = { | 287 | var _response = { |
261 | raw: body, | 288 | raw: body, |
262 | parsed: { | 289 | parsed: { |
263 | MESSAGE: st24message, | 290 | MESSAGE: st24message, |
264 | xml: response.xml | 291 | xml: response.xml |
265 | } | 292 | } |
266 | } | 293 | } |
267 | 294 | ||
268 | aaa.pushResponseToMongoDb(task, _response, st24rc); | 295 | aaa.pushResponseToMongoDb(task, _response, st24rc); |
269 | 296 | ||
270 | callbackReport(task.requestId, st24rc, st24message); | 297 | callbackReport(task.requestId, st24rc, st24message); |
271 | }); | 298 | }); |
272 | } | 299 | } |
273 | 300 | ||
274 | function prepareRemoteProductCode(remoteProduct) { | 301 | function prepareRemoteProductCode(remoteProduct) { |
275 | var product = remoteProduct.split(','); | 302 | var product = remoteProduct.split(','); |
276 | 303 | ||
277 | if (product.length != 3) { | 304 | if (product.length != 3) { |
278 | return; | 305 | return; |
279 | } | 306 | } |
280 | 307 | ||
281 | return { | 308 | return { |
282 | product: product[0], | 309 | product: product[0], |
283 | productDetail: product[1], | 310 | productDetail: product[1], |
284 | nominal: product[2] | 311 | nominal: product[2] |
285 | } | 312 | } |
286 | } | 313 | } |
287 | 314 | ||
288 | function paddingDestination(destination, width) { | 315 | function paddingDestination(destination, width) { |
289 | if (!width) { | 316 | if (!width) { |
290 | width = 13; | 317 | width = 13; |
291 | } | 318 | } |
292 | 319 | ||
293 | if (destination.length > width) { | 320 | if (destination.length > width) { |
294 | return destination; | 321 | return destination; |
295 | } | 322 | } |
296 | 323 | ||
297 | var padder = "000000000000000000000000000"; | 324 | var padder = "000000000000000000000000000"; |
298 | 325 | ||
299 | return (padder + destination).slice(-1 * width); | 326 | return (padder + destination).slice(-1 * width); |
300 | } | 327 | } |
301 | 328 | ||
329 | function getTaskKeyForDupeCheck(task) { | ||
330 | return config.globals.gateway_name + '.dupecheck.reqId:' + task.requestId; | ||
331 | } | ||
332 | |||
333 | function saveTaskForDupeCheck(task, cb) { | ||
334 | var key = getTaskKeyForDupeCheck(task); | ||
335 | |||
336 | redisClient.set(key, JSON.stringify(task), function() { | ||
337 | redisClient.expire(key, 3600*24*7); | ||
338 | if (cb) { | ||
339 | cb(null, task); | ||
340 | } | ||
341 | }); | ||
342 | } | ||
343 | |||
344 | function getTaskForDupeCheck(task, cb) { | ||
345 | var key = getTaskKeyForDupeCheck(task); | ||
346 | |||
347 | redisClient.get(key, function(err, result) { | ||
348 | if (err) { | ||
349 | cb(err, null); | ||
350 | return; | ||
351 | } | ||
352 | |||
353 | var taskOnRedis = null; | ||
354 | try { | ||
355 | taskOnRedis = JSON.parse(result); | ||
356 | } | ||
357 | catch(err1) { | ||
358 | cb(err1, null); | ||
359 | return; | ||
360 | } | ||
361 | |||
362 | cb(null, taskOnRedis); | ||
363 | }); | ||
364 | } | ||
365 | |||
302 | exports.start = start; | 366 | exports.start = start; |
303 | exports.topupRequest = topupRequest; | 367 | exports.topupRequest = topupRequest; |
304 | exports.prepareRemoteProductCode = prepareRemoteProductCode; | 368 | exports.prepareRemoteProductCode = prepareRemoteProductCode; |
305 | exports.paddingDestination = paddingDestination; | 369 | exports.paddingDestination = paddingDestination; |
306 | 370 |