Commit c36f8503f6a07bc816f6f83923e5da549c6c44fd
1 parent
9989604292
Exists in
master
typo
Showing 1 changed file with 1 additions and 1 deletions Inline Diff
partner-simplepay.js
1 | "use strict"; | 1 | "use strict"; |
2 | 2 | ||
3 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; | 3 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; |
4 | 4 | ||
5 | const request = require('request'); | 5 | const request = require('request'); |
6 | const crypto = require('crypto'); | 6 | const crypto = require('crypto'); |
7 | const moment = require('moment'); | 7 | const moment = require('moment'); |
8 | 8 | ||
9 | const http = require('http'); | 9 | const http = require('http'); |
10 | http.globalAgent.maxSockets = Infinity; | 10 | http.globalAgent.maxSockets = Infinity; |
11 | 11 | ||
12 | const seconds_to_wait_before_resend_on_pending = 30; | 12 | const seconds_to_wait_before_resend_on_pending = 30; |
13 | 13 | ||
14 | var config; | 14 | var config; |
15 | var aaa; | 15 | var aaa; |
16 | var logger; | 16 | var logger; |
17 | 17 | ||
18 | function start(options) { | 18 | function start(options) { |
19 | if (!options) { | 19 | if (!options) { |
20 | console.log('Undefined options, terminating....'); | 20 | console.log('Undefined options, terminating....'); |
21 | process.exit(1); | 21 | process.exit(1); |
22 | } | 22 | } |
23 | 23 | ||
24 | if (options.config) { | 24 | if (options.config) { |
25 | config = options.config; | 25 | config = options.config; |
26 | } else { | 26 | } else { |
27 | console.log('Undefined options.config, terminating....') | 27 | console.log('Undefined options.config, terminating....') |
28 | process.exit(1); | 28 | process.exit(1); |
29 | } | 29 | } |
30 | 30 | ||
31 | if (options.aaa) { | 31 | if (options.aaa) { |
32 | aaa = options.aaa; | 32 | aaa = options.aaa; |
33 | } else { | 33 | } else { |
34 | console.log('Undefined options.aaa, terminating....') | 34 | console.log('Undefined options.aaa, terminating....') |
35 | process.exit(1); | 35 | process.exit(1); |
36 | } | 36 | } |
37 | 37 | ||
38 | if (options && options.logger) { | 38 | if (options && options.logger) { |
39 | logger = options.logger; | 39 | logger = options.logger; |
40 | } else { | 40 | } else { |
41 | console.log('Undefined options.logger, terminating....') | 41 | console.log('Undefined options.logger, terminating....') |
42 | process.exit(1); | 42 | process.exit(1); |
43 | } | 43 | } |
44 | 44 | ||
45 | createReverseHttpServer(); | 45 | createReverseHttpServer(); |
46 | } | 46 | } |
47 | 47 | ||
48 | function callbackReport(requestId, rc, message, options) { | 48 | function callbackReport(requestId, rc, message, options) { |
49 | aaa.callbackReportWithPushToMongoDb(requestId, rc, message); | 49 | aaa.callbackReportWithPushToMongoDb(requestId, rc, message); |
50 | 50 | ||
51 | // resend trx as status check if rc 68 and task is defined | 51 | // resend trx as status check if rc 68 and task is defined |
52 | if (options && options.task && (rc == '68')) { | 52 | if (options && options.task && (rc == '68')) { |
53 | logger.verbose('Got pending trx, requesting in ' + seconds_to_wait_before_resend_on_pending + ' secs'); | 53 | logger.verbose('Got pending trx, requesting in ' + seconds_to_wait_before_resend_on_pending + ' secs'); |
54 | setTimeout(function() { | 54 | setTimeout(function() { |
55 | checkStatus(options.task); | 55 | checkStatus(options.task); |
56 | }, seconds_to_wait_before_resend_on_pending * 1000) | 56 | }, seconds_to_wait_before_resend_on_pending * 1000) |
57 | } | 57 | } |
58 | } | 58 | } |
59 | 59 | ||
60 | function calculateSign(dt, trx_ref_id, cust_num, username, password) { | 60 | function calculateSign(dt, trx_ref_id, cust_num, username, password) { |
61 | const cryptoHashPassword = crypto.createHash('sha1'); | 61 | const cryptoHashPassword = crypto.createHash('sha1'); |
62 | const cryptoHashUsernamePassword = crypto.createHash('sha1'); | 62 | const cryptoHashUsernamePassword = crypto.createHash('sha1'); |
63 | const cryptoHashOutest = crypto.createHash('sha1'); | 63 | const cryptoHashOutest = crypto.createHash('sha1'); |
64 | 64 | ||
65 | cryptoHashPassword.update(password); | 65 | cryptoHashPassword.update(password); |
66 | const hashPassword = cryptoHashPassword.digest('hex'); | 66 | const hashPassword = cryptoHashPassword.digest('hex'); |
67 | 67 | ||
68 | cryptoHashUsernamePassword.update(username + hashPassword); | 68 | cryptoHashUsernamePassword.update(username + hashPassword); |
69 | const hashUsernamePassword = cryptoHashUsernamePassword.digest('hex'); | 69 | const hashUsernamePassword = cryptoHashUsernamePassword.digest('hex'); |
70 | 70 | ||
71 | cryptoHashOutest.update(dt + trx_ref_id + cust_num + hashUsernamePassword); | 71 | cryptoHashOutest.update(dt + trx_ref_id + cust_num + hashUsernamePassword); |
72 | return cryptoHashOutest.digest('hex'); | 72 | return cryptoHashOutest.digest('hex'); |
73 | } | 73 | } |
74 | 74 | ||
75 | function _decodeResponseBody(responseBody) { | 75 | function _decodeResponseBody(responseBody) { |
76 | let response; | 76 | let response; |
77 | 77 | ||
78 | try { | 78 | try { |
79 | response = JSON.parse(responseBody); | 79 | response = JSON.parse(responseBody); |
80 | } | 80 | } |
81 | catch(e) { | 81 | catch(e) { |
82 | logger.warn('Error parsing response body'); | 82 | logger.warn('Error parsing response body'); |
83 | } | 83 | } |
84 | 84 | ||
85 | return response; | 85 | return response; |
86 | } | 86 | } |
87 | 87 | ||
88 | function _composeMessageFromResponseData(responseDataObj) { | 88 | function _composeMessageFromResponseData(responseDataObj) { |
89 | const diag = _getPropertyFromObjectSafe(responseDataObj, 'diag'); | 89 | const diag = _getPropertyFromObjectSafe(responseDataObj, 'diag'); |
90 | const msg = _getPropertyFromObjectSafe(responseDataObj, 'message'); | 90 | const msg = _getPropertyFromObjectSafe(responseDataObj, 'message'); |
91 | const balance = _getPropertyFromObjectSafe(responseDataObj, 'balance'); | 91 | const balance = _getPropertyFromObjectSafe(responseDataObj, 'balance'); |
92 | const timestamp = _getPropertyFromObjectSafe(responseDataObj, 'timestamp'); | 92 | const timestamp = _getPropertyFromObjectSafe(responseDataObj, 'timestamp'); |
93 | const price = _getPropertyFromObjectSafe(response.data, 'harga'); | 93 | const price = _getPropertyFromObjectSafe(responseDataObj, 'harga'); |
94 | 94 | ||
95 | let messages = []; | 95 | let messages = []; |
96 | 96 | ||
97 | if (timestamp) { | 97 | if (timestamp) { |
98 | messages.push(timestamp); | 98 | messages.push(timestamp); |
99 | } | 99 | } |
100 | 100 | ||
101 | if (diag) { | 101 | if (diag) { |
102 | messages.push(diag); | 102 | messages.push(diag); |
103 | } | 103 | } |
104 | 104 | ||
105 | if (msg) { | 105 | if (msg) { |
106 | messages.push(msg); | 106 | messages.push(msg); |
107 | } | 107 | } |
108 | 108 | ||
109 | if (balance) { | 109 | if (balance) { |
110 | messages.push('Balance: ' + balance); | 110 | messages.push('Balance: ' + balance); |
111 | } | 111 | } |
112 | 112 | ||
113 | if (price) { | 113 | if (price) { |
114 | messages.push('Price: ' + price); | 114 | messages.push('Price: ' + price); |
115 | } | 115 | } |
116 | 116 | ||
117 | return messages.join('. ') + '.'; | 117 | return messages.join('. ') + '.'; |
118 | } | 118 | } |
119 | 119 | ||
120 | function _composeCompleteSn(responseDataObj) { | 120 | function _composeCompleteSn(responseDataObj) { |
121 | const serial = _getPropertyFromObjectSafe(responseDataObj, 'serial'); | 121 | const serial = _getPropertyFromObjectSafe(responseDataObj, 'serial'); |
122 | const info = _getPropertyFromObjectSafe(responseDataObj, 'info'); | 122 | const info = _getPropertyFromObjectSafe(responseDataObj, 'info'); |
123 | 123 | ||
124 | if (!info) { | 124 | if (!info) { |
125 | //logger.warn('Undefined data.info on _composeCompleteSn'); | 125 | //logger.warn('Undefined data.info on _composeCompleteSn'); |
126 | return serial; | 126 | return serial; |
127 | } | 127 | } |
128 | 128 | ||
129 | const cleanedData = { | 129 | const cleanedData = { |
130 | token: serial, | 130 | token: serial, |
131 | cust_name: _getPropertyFromObjectSafe(info, 'cust_name'), | 131 | cust_name: _getPropertyFromObjectSafe(info, 'cust_name'), |
132 | tariff: _getPropertyFromObjectSafe(info, 'kelas') + 'VA', | 132 | tariff: _getPropertyFromObjectSafe(info, 'kelas') + 'VA', |
133 | total_kwh: _getPropertyFromObjectSafe(info, 'size') | 133 | total_kwh: _getPropertyFromObjectSafe(info, 'size') |
134 | } | 134 | } |
135 | 135 | ||
136 | if (cleanedData.token) { | 136 | if (cleanedData.token) { |
137 | cleanedData.token = cleanedData.token.replace(/ /g, '-'); | 137 | cleanedData.token = cleanedData.token.replace(/ /g, '-'); |
138 | } | 138 | } |
139 | 139 | ||
140 | if (cleanedData.cust_name) { | 140 | if (cleanedData.cust_name) { |
141 | cleanedData.cust_name = cleanedData.cust_name.replace(/\W+/g, ' ').trim().replace(/\W+/g, '-').toUpperCase(); | 141 | cleanedData.cust_name = cleanedData.cust_name.replace(/\W+/g, ' ').trim().replace(/\W+/g, '-').toUpperCase(); |
142 | } | 142 | } |
143 | 143 | ||
144 | if (cleanedData.total_kwh) { | 144 | if (cleanedData.total_kwh) { |
145 | cleanedData.total_kwh = cleanedData.total_kwh.replace(/kWh /g, ''); | 145 | cleanedData.total_kwh = cleanedData.total_kwh.replace(/kWh /g, ''); |
146 | } | 146 | } |
147 | 147 | ||
148 | 148 | ||
149 | logger.verbose('Detail token info extracted', {originalResponseInfo: info, cleanedData: cleanedData}); | 149 | logger.verbose('Detail token info extracted', {originalResponseInfo: info, cleanedData: cleanedData}); |
150 | 150 | ||
151 | return [ | 151 | return [ |
152 | cleanedData.token, cleanedData.cust_name, cleanedData.tariff, cleanedData.total_kwh | 152 | cleanedData.token, cleanedData.cust_name, cleanedData.tariff, cleanedData.total_kwh |
153 | ].join('/'); | 153 | ].join('/'); |
154 | } | 154 | } |
155 | 155 | ||
156 | function _responseBodyHandler(responseBody, task) { | 156 | function _responseBodyHandler(responseBody, task) { |
157 | let rc = '68'; | 157 | let rc = '68'; |
158 | let response = _decodeResponseBody(responseBody); | 158 | let response = _decodeResponseBody(responseBody); |
159 | 159 | ||
160 | logger.verbose('RESPONSE', {response: response}); | 160 | logger.verbose('RESPONSE', {response: response}); |
161 | 161 | ||
162 | const responseStatus = _getPropertyFromObjectSafe(response, 'status'); | 162 | const responseStatus = _getPropertyFromObjectSafe(response, 'status'); |
163 | const responseInfo = _getPropertyFromObjectSafe(response, 'info'); | 163 | const responseInfo = _getPropertyFromObjectSafe(response, 'info'); |
164 | 164 | ||
165 | if (responseStatus == 'Error') { | 165 | if (responseStatus == 'Error') { |
166 | if (['insufficient balance', 'System Cut-Off'].indexOf(responseInfo) >= 0) { | 166 | if (['insufficient balance', 'System Cut-Off'].indexOf(responseInfo) >= 0) { |
167 | rc = '91'; | 167 | rc = '91'; |
168 | } | 168 | } |
169 | callbackReport(task.requestId, '91', [responseStatus, responseInfo].join(': '), {task: task}); | 169 | callbackReport(task.requestId, '91', [responseStatus, responseInfo].join(': '), {task: task}); |
170 | return; | 170 | return; |
171 | } | 171 | } |
172 | 172 | ||
173 | const requestId = _getPropertyFromObjectSafe(response.data, 'request_id'); | 173 | const requestId = _getPropertyFromObjectSafe(response.data, 'request_id'); |
174 | const trxStatus = _getPropertyFromObjectSafe(response.data, 'trx_status'); | 174 | const trxStatus = _getPropertyFromObjectSafe(response.data, 'trx_status'); |
175 | const diag = _getPropertyFromObjectSafe(response.data, 'diag'); | 175 | const diag = _getPropertyFromObjectSafe(response.data, 'diag'); |
176 | let balance = _getPropertyFromObjectSafe(response.data, 'balance'); | 176 | let balance = _getPropertyFromObjectSafe(response.data, 'balance'); |
177 | 177 | ||
178 | if (balance && aaa.updateBalance) { | 178 | if (balance && aaa.updateBalance) { |
179 | balance = balance.replace(/\D/g, ''); | 179 | balance = balance.replace(/\D/g, ''); |
180 | if (balance) { | 180 | if (balance) { |
181 | aaa.updateBalance(balance); | 181 | aaa.updateBalance(balance); |
182 | } | 182 | } |
183 | } | 183 | } |
184 | 184 | ||
185 | let aaaMessage = _composeMessageFromResponseData(response.data); | 185 | let aaaMessage = _composeMessageFromResponseData(response.data); |
186 | if (!aaaMessage) { | 186 | if (!aaaMessage) { |
187 | aaaMessage = 'Transaksi sedang diproses'; | 187 | aaaMessage = 'Transaksi sedang diproses'; |
188 | } | 188 | } |
189 | 189 | ||
190 | if (trxStatus == 'P') { | 190 | if (trxStatus == 'P') { |
191 | logger.verbose('Got pending trx response', {response: response.data}); | 191 | logger.verbose('Got pending trx response', {response: response.data}); |
192 | rc = '68'; | 192 | rc = '68'; |
193 | } | 193 | } |
194 | else if (trxStatus == 'S') { | 194 | else if (trxStatus == 'S') { |
195 | logger.verbose('Got succcess trx response', {response: response.data}); | 195 | logger.verbose('Got succcess trx response', {response: response.data}); |
196 | 196 | ||
197 | rc = '00'; | 197 | rc = '00'; |
198 | aaaMessage = 'SN=' + _composeCompleteSn(response.data) + '; ' + aaaMessage; | 198 | aaaMessage = 'SN=' + _composeCompleteSn(response.data) + '; ' + aaaMessage; |
199 | } | 199 | } |
200 | else if (trxStatus == 'R') { | 200 | else if (trxStatus == 'R') { |
201 | logger.verbose('Got rejected trx response', {response: response.data}); | 201 | logger.verbose('Got rejected trx response', {response: response.data}); |
202 | 202 | ||
203 | const partnerRC = getPartnerRCFromDiagMessage(diag); | 203 | const partnerRC = getPartnerRCFromDiagMessage(diag); |
204 | if (partnerRC == '15') { | 204 | if (partnerRC == '15') { |
205 | rc = '14'; | 205 | rc = '14'; |
206 | } | 206 | } |
207 | else { | 207 | else { |
208 | rc = '40'; | 208 | rc = '40'; |
209 | } | 209 | } |
210 | } | 210 | } |
211 | 211 | ||
212 | callbackReport(requestId, rc, aaaMessage, {task: task}); | 212 | callbackReport(requestId, rc, aaaMessage, {task: task}); |
213 | } | 213 | } |
214 | 214 | ||
215 | function getPartnerRCFromDiagMessage(diag) { | 215 | function getPartnerRCFromDiagMessage(diag) { |
216 | let matches = diag.match(/^\s*\[(.*)\]/); | 216 | let matches = diag.match(/^\s*\[(.*)\]/); |
217 | if (!matches || matches.length < 2) { | 217 | if (!matches || matches.length < 2) { |
218 | return; | 218 | return; |
219 | } | 219 | } |
220 | 220 | ||
221 | return matches[1]; | 221 | return matches[1]; |
222 | } | 222 | } |
223 | 223 | ||
224 | function _hitTopup(task, isCheckStatus) { | 224 | function _hitTopup(task, isCheckStatus) { |
225 | 225 | ||
226 | const dt = moment().format('YYYY-MM-DD HH:mm:ss'); | 226 | const dt = moment().format('YYYY-MM-DD HH:mm:ss'); |
227 | const username = config.h2h_out.username || config.h2h_out.userid; | 227 | const username = config.h2h_out.username || config.h2h_out.userid; |
228 | const password = config.h2h_out.password || config.h2h_out.pin; | 228 | const password = config.h2h_out.password || config.h2h_out.pin; |
229 | const sign = calculateSign(dt, task.requestId, task.destination, username, password); | 229 | const sign = calculateSign(dt, task.requestId, task.destination, username, password); |
230 | 230 | ||
231 | logger.verbose('Sign for ' + dt + ', ' + task.requestId + ', ' + task.destination + ', ' + username + ', ' + password + ' is ' + sign); | 231 | logger.verbose('Sign for ' + dt + ', ' + task.requestId + ', ' + task.destination + ', ' + username + ', ' + password + ' is ' + sign); |
232 | const requestOptions = { | 232 | const requestOptions = { |
233 | url: config.h2h_out.partner, | 233 | url: config.h2h_out.partner, |
234 | form: { | 234 | form: { |
235 | username: username, | 235 | username: username, |
236 | datetime: dt, | 236 | datetime: dt, |
237 | code: task.remoteProduct, | 237 | code: task.remoteProduct, |
238 | trx_ref_id: task.requestId, | 238 | trx_ref_id: task.requestId, |
239 | cust_num: task.destination, | 239 | cust_num: task.destination, |
240 | sign: sign | 240 | sign: sign |
241 | } | 241 | } |
242 | } | 242 | } |
243 | 243 | ||
244 | logger.verbose('Requesting to partner', {requestOptions: requestOptions}); | 244 | logger.verbose('Requesting to partner', {requestOptions: requestOptions}); |
245 | 245 | ||
246 | request.post(requestOptions, function(error, response, body) { | 246 | request.post(requestOptions, function(error, response, body) { |
247 | if (error) { | 247 | if (error) { |
248 | let rc = '68'; | 248 | let rc = '68'; |
249 | 249 | ||
250 | if (!isCheckStatus && (error.syscall == 'connect')) { | 250 | if (!isCheckStatus && (error.syscall == 'connect')) { |
251 | rc = '91'; | 251 | rc = '91'; |
252 | } | 252 | } |
253 | 253 | ||
254 | logger.warn('Error requesting to partner', {task: task, rc: rc, error: error, isCheckStatus: isCheckStatus}); | 254 | logger.warn('Error requesting to partner', {task: task, rc: rc, error: error, isCheckStatus: isCheckStatus}); |
255 | callbackReport(task.requestId, rc, 'Error requesting to partner. ' + error, {task: task}); | 255 | callbackReport(task.requestId, rc, 'Error requesting to partner. ' + error, {task: task}); |
256 | return; | 256 | return; |
257 | } | 257 | } |
258 | 258 | ||
259 | if (response.statusCode != 200) { | 259 | if (response.statusCode != 200) { |
260 | let rc = '68'; | 260 | let rc = '68'; |
261 | 261 | ||
262 | logger.warn('HTTP status code is not 200', {task: task, http_status_code: response.statusCode, isCheckStatus: isCheckStatus}); | 262 | logger.warn('HTTP status code is not 200', {task: task, http_status_code: response.statusCode, isCheckStatus: isCheckStatus}); |
263 | callbackReport(task.requestId, rc, 'HTTP status code ' + response.statusCode, {task: task}); | 263 | callbackReport(task.requestId, rc, 'HTTP status code ' + response.statusCode, {task: task}); |
264 | return; | 264 | return; |
265 | } | 265 | } |
266 | 266 | ||
267 | logger.info('Transaksi sedang diproses', {task: task, response_body: body}); | 267 | logger.info('Transaksi sedang diproses', {task: task, response_body: body}); |
268 | 268 | ||
269 | _responseBodyHandler(body, task); | 269 | _responseBodyHandler(body, task); |
270 | 270 | ||
271 | }) | 271 | }) |
272 | } | 272 | } |
273 | 273 | ||
274 | function _getPropertyFromObjectSafe(obj, property) { | 274 | function _getPropertyFromObjectSafe(obj, property) { |
275 | let retval; | 275 | let retval; |
276 | 276 | ||
277 | if (!obj) { | 277 | if (!obj) { |
278 | logger.warn('Invalid object') | 278 | logger.warn('Invalid object') |
279 | return; | 279 | return; |
280 | } | 280 | } |
281 | 281 | ||
282 | try { | 282 | try { |
283 | retval = obj[property]; | 283 | retval = obj[property]; |
284 | } | 284 | } |
285 | catch(e) { | 285 | catch(e) { |
286 | logger.warn('Error getting ' + property + ' from object'); | 286 | logger.warn('Error getting ' + property + ' from object'); |
287 | } | 287 | } |
288 | 288 | ||
289 | return retval; | 289 | return retval; |
290 | } | 290 | } |
291 | 291 | ||
292 | function topupRequest(task) { | 292 | function topupRequest(task) { |
293 | aaa.insertTaskToMongoDb(task); | 293 | aaa.insertTaskToMongoDb(task); |
294 | _hitTopup(task); | 294 | _hitTopup(task); |
295 | } | 295 | } |
296 | 296 | ||
297 | function checkStatus(task) { | 297 | function checkStatus(task) { |
298 | _hitTopup(task, true); | 298 | _hitTopup(task, true); |
299 | } | 299 | } |
300 | 300 | ||
301 | 301 | ||
302 | function reverseReportHandler(body) { | 302 | function reverseReportHandler(body) { |
303 | logger.info('Got reverse report', {body: body}); | 303 | logger.info('Got reverse report', {body: body}); |
304 | } | 304 | } |
305 | 305 | ||
306 | function createReverseHttpServer() { | 306 | function createReverseHttpServer() { |
307 | var httpServer = http.createServer(function(request, response) { | 307 | var httpServer = http.createServer(function(request, response) { |
308 | 308 | ||
309 | logger.info('Got request from partner'); | 309 | logger.info('Got request from partner'); |
310 | 310 | ||
311 | var body = ""; | 311 | var body = ""; |
312 | request.on('data', function (chunk) { | 312 | request.on('data', function (chunk) { |
313 | body += chunk; | 313 | body += chunk; |
314 | }); | 314 | }); |
315 | 315 | ||
316 | request.on('end', function () { | 316 | request.on('end', function () { |
317 | response.writeHead(200); | 317 | response.writeHead(200); |
318 | response.end('OK'); | 318 | response.end('OK'); |
319 | 319 | ||
320 | reverseReportHandler(body); | 320 | reverseReportHandler(body); |
321 | }); | 321 | }); |
322 | 322 | ||
323 | }); | 323 | }); |
324 | 324 | ||
325 | httpServer.listen(config.h2h_out.listen_port, function() { | 325 | httpServer.listen(config.h2h_out.listen_port, function() { |
326 | logger.info('HTTP Reverse/Report server listen on port ' + config.h2h_out.listen_port); | 326 | logger.info('HTTP Reverse/Report server listen on port ' + config.h2h_out.listen_port); |
327 | }); | 327 | }); |
328 | } | 328 | } |
329 | 329 | ||
330 | 330 | ||
331 | exports.calculateSign = calculateSign; | 331 | exports.calculateSign = calculateSign; |
332 | exports.start = start; | 332 | exports.start = start; |
333 | exports.topupRequest = topupRequest; | 333 | exports.topupRequest = topupRequest; |
334 | exports.checkStatus = checkStatus; | 334 | exports.checkStatus = checkStatus; |
335 | 335 |