Commit eae16d143fcfe51522b982038b0fef2b064e370c
1 parent
593ada1205
Exists in
master
replace space in token to dash
Showing 1 changed file with 4 additions and 0 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 | 93 | ||
94 | let messages = []; | 94 | let messages = []; |
95 | 95 | ||
96 | if (timestamp) { | 96 | if (timestamp) { |
97 | messages.push(timestamp); | 97 | messages.push(timestamp); |
98 | } | 98 | } |
99 | 99 | ||
100 | if (diag) { | 100 | if (diag) { |
101 | messages.push(diag); | 101 | messages.push(diag); |
102 | } | 102 | } |
103 | 103 | ||
104 | if (msg) { | 104 | if (msg) { |
105 | messages.push(msg); | 105 | messages.push(msg); |
106 | } | 106 | } |
107 | 107 | ||
108 | if (balance) { | 108 | if (balance) { |
109 | messages.push('Balance: ' + balance); | 109 | messages.push('Balance: ' + balance); |
110 | } | 110 | } |
111 | 111 | ||
112 | return messages.join('. ') + '.'; | 112 | return messages.join('. ') + '.'; |
113 | } | 113 | } |
114 | 114 | ||
115 | function _composeCompleteSn(responseDataObj) { | 115 | function _composeCompleteSn(responseDataObj) { |
116 | const serial = _getPropertyFromObjectSafe(responseDataObj, 'serial'); | 116 | const serial = _getPropertyFromObjectSafe(responseDataObj, 'serial'); |
117 | const info = _getPropertyFromObjectSafe(responseDataObj, 'info'); | 117 | const info = _getPropertyFromObjectSafe(responseDataObj, 'info'); |
118 | 118 | ||
119 | if (!info) { | 119 | if (!info) { |
120 | //logger.warn('Undefined data.info on _composeCompleteSn'); | 120 | //logger.warn('Undefined data.info on _composeCompleteSn'); |
121 | return serial; | 121 | return serial; |
122 | } | 122 | } |
123 | 123 | ||
124 | const cleanedData = { | 124 | const cleanedData = { |
125 | token: serial, | 125 | token: serial, |
126 | cust_name: _getPropertyFromObjectSafe(info, 'cust_name'), | 126 | cust_name: _getPropertyFromObjectSafe(info, 'cust_name'), |
127 | tariff: _getPropertyFromObjectSafe(info, 'kelas') + 'VA', | 127 | tariff: _getPropertyFromObjectSafe(info, 'kelas') + 'VA', |
128 | total_kwh: _getPropertyFromObjectSafe(info, 'size') | 128 | total_kwh: _getPropertyFromObjectSafe(info, 'size') |
129 | } | 129 | } |
130 | 130 | ||
131 | if (cleanedData.token) { | ||
132 | cleanedData.token = cleanedData.token.replace(/ /g, '-'); | ||
133 | } | ||
134 | |||
131 | if (cleanedData.cust_name) { | 135 | if (cleanedData.cust_name) { |
132 | cleanedData.cust_name = cleanedData.cust_name.replace(/\W+/g, ' ').trim().replace(/\W+/g, '-').toUpperCase(); | 136 | cleanedData.cust_name = cleanedData.cust_name.replace(/\W+/g, ' ').trim().replace(/\W+/g, '-').toUpperCase(); |
133 | } | 137 | } |
134 | logger.verbose('Detail token info extracted', {originalResponseInfo: info, cleanedData: cleanedData}); | 138 | logger.verbose('Detail token info extracted', {originalResponseInfo: info, cleanedData: cleanedData}); |
135 | 139 | ||
136 | return [ | 140 | return [ |
137 | cleanedData.token, cleanedData.cust_name, cleanedData.tariff, cleanedData.total_kwh | 141 | cleanedData.token, cleanedData.cust_name, cleanedData.tariff, cleanedData.total_kwh |
138 | ].join('/'); | 142 | ].join('/'); |
139 | } | 143 | } |
140 | 144 | ||
141 | function _responseBodyHandler(responseBody, task) { | 145 | function _responseBodyHandler(responseBody, task) { |
142 | let rc = '68'; | 146 | let rc = '68'; |
143 | let response = _decodeResponseBody(responseBody); | 147 | let response = _decodeResponseBody(responseBody); |
144 | 148 | ||
145 | logger.verbose('RESPONSE', {response: response}); | 149 | logger.verbose('RESPONSE', {response: response}); |
146 | 150 | ||
147 | const responseStatus = _getPropertyFromObjectSafe(response, 'status'); | 151 | const responseStatus = _getPropertyFromObjectSafe(response, 'status'); |
148 | const responseInfo = _getPropertyFromObjectSafe(response, 'info'); | 152 | const responseInfo = _getPropertyFromObjectSafe(response, 'info'); |
149 | 153 | ||
150 | if (responseStatus == 'Error') { | 154 | if (responseStatus == 'Error') { |
151 | if (['insufficient balance', 'System Cut-Off'].indexOf(responseInfo) >= 0) { | 155 | if (['insufficient balance', 'System Cut-Off'].indexOf(responseInfo) >= 0) { |
152 | rc = '91'; | 156 | rc = '91'; |
153 | } | 157 | } |
154 | callbackReport(task.requestId, '91', [responseStatus, responseInfo].join(': '), {task: task}); | 158 | callbackReport(task.requestId, '91', [responseStatus, responseInfo].join(': '), {task: task}); |
155 | return; | 159 | return; |
156 | } | 160 | } |
157 | 161 | ||
158 | const requestId = _getPropertyFromObjectSafe(response.data, 'request_id'); | 162 | const requestId = _getPropertyFromObjectSafe(response.data, 'request_id'); |
159 | const trxStatus = _getPropertyFromObjectSafe(response.data, 'trx_status'); | 163 | const trxStatus = _getPropertyFromObjectSafe(response.data, 'trx_status'); |
160 | const diag = _getPropertyFromObjectSafe(response.data, 'diag'); | 164 | const diag = _getPropertyFromObjectSafe(response.data, 'diag'); |
161 | let balance = _getPropertyFromObjectSafe(response.data, 'balance'); | 165 | let balance = _getPropertyFromObjectSafe(response.data, 'balance'); |
162 | 166 | ||
163 | if (balance && aaa.updateBalance) { | 167 | if (balance && aaa.updateBalance) { |
164 | balance = balance.replace(/\D/g, ''); | 168 | balance = balance.replace(/\D/g, ''); |
165 | if (balance) { | 169 | if (balance) { |
166 | aaa.updateBalance(balance); | 170 | aaa.updateBalance(balance); |
167 | } | 171 | } |
168 | } | 172 | } |
169 | 173 | ||
170 | let aaaMessage = _composeMessageFromResponseData(response.data); | 174 | let aaaMessage = _composeMessageFromResponseData(response.data); |
171 | if (!aaaMessage) { | 175 | if (!aaaMessage) { |
172 | aaaMessage = 'Transaksi sedang diproses'; | 176 | aaaMessage = 'Transaksi sedang diproses'; |
173 | } | 177 | } |
174 | 178 | ||
175 | if (trxStatus == 'P') { | 179 | if (trxStatus == 'P') { |
176 | logger.verbose('Got pending trx response', {response: response.data}); | 180 | logger.verbose('Got pending trx response', {response: response.data}); |
177 | rc = '68'; | 181 | rc = '68'; |
178 | } | 182 | } |
179 | else if (trxStatus == 'S') { | 183 | else if (trxStatus == 'S') { |
180 | logger.verbose('Got succcess trx response', {response: response.data}); | 184 | logger.verbose('Got succcess trx response', {response: response.data}); |
181 | 185 | ||
182 | rc = '00'; | 186 | rc = '00'; |
183 | aaaMessage = 'SN=' + _composeCompleteSn(response.data) + '; ' + aaaMessage; | 187 | aaaMessage = 'SN=' + _composeCompleteSn(response.data) + '; ' + aaaMessage; |
184 | } | 188 | } |
185 | else if (trxStatus == 'R') { | 189 | else if (trxStatus == 'R') { |
186 | logger.verbose('Got rejected trx response', {response: response.data}); | 190 | logger.verbose('Got rejected trx response', {response: response.data}); |
187 | 191 | ||
188 | const partnerRC = getPartnerRCFromDiagMessage(diag); | 192 | const partnerRC = getPartnerRCFromDiagMessage(diag); |
189 | if (partnerRC == '15') { | 193 | if (partnerRC == '15') { |
190 | rc = '14'; | 194 | rc = '14'; |
191 | } | 195 | } |
192 | else { | 196 | else { |
193 | rc = '40'; | 197 | rc = '40'; |
194 | } | 198 | } |
195 | } | 199 | } |
196 | 200 | ||
197 | callbackReport(requestId, rc, aaaMessage, {task: task}); | 201 | callbackReport(requestId, rc, aaaMessage, {task: task}); |
198 | } | 202 | } |
199 | 203 | ||
200 | function getPartnerRCFromDiagMessage(diag) { | 204 | function getPartnerRCFromDiagMessage(diag) { |
201 | let matches = diag.match(/^\s*\[(.*)\]/); | 205 | let matches = diag.match(/^\s*\[(.*)\]/); |
202 | if (!matches || matches.length < 2) { | 206 | if (!matches || matches.length < 2) { |
203 | return; | 207 | return; |
204 | } | 208 | } |
205 | 209 | ||
206 | return matches[1]; | 210 | return matches[1]; |
207 | } | 211 | } |
208 | 212 | ||
209 | function _hitTopup(task, isCheckStatus) { | 213 | function _hitTopup(task, isCheckStatus) { |
210 | 214 | ||
211 | const dt = moment().format('YYYY-MM-DD HH:mm:ss'); | 215 | const dt = moment().format('YYYY-MM-DD HH:mm:ss'); |
212 | const username = config.h2h_out.username || config.h2h_out.userid; | 216 | const username = config.h2h_out.username || config.h2h_out.userid; |
213 | const password = config.h2h_out.password || config.h2h_out.pin; | 217 | const password = config.h2h_out.password || config.h2h_out.pin; |
214 | const sign = calculateSign(dt, task.requestId, task.destination, username, password); | 218 | const sign = calculateSign(dt, task.requestId, task.destination, username, password); |
215 | 219 | ||
216 | logger.verbose('Sign for ' + dt + ', ' + task.requestId + ', ' + task.destination + ', ' + username + ', ' + password + ' is ' + sign); | 220 | logger.verbose('Sign for ' + dt + ', ' + task.requestId + ', ' + task.destination + ', ' + username + ', ' + password + ' is ' + sign); |
217 | const requestOptions = { | 221 | const requestOptions = { |
218 | url: config.h2h_out.partner, | 222 | url: config.h2h_out.partner, |
219 | form: { | 223 | form: { |
220 | username: username, | 224 | username: username, |
221 | datetime: dt, | 225 | datetime: dt, |
222 | code: task.remoteProduct, | 226 | code: task.remoteProduct, |
223 | trx_ref_id: task.requestId, | 227 | trx_ref_id: task.requestId, |
224 | cust_num: task.destination, | 228 | cust_num: task.destination, |
225 | sign: sign | 229 | sign: sign |
226 | } | 230 | } |
227 | } | 231 | } |
228 | 232 | ||
229 | logger.verbose('Requeting to partner', {requestOptions: requestOptions}); | 233 | logger.verbose('Requeting to partner', {requestOptions: requestOptions}); |
230 | 234 | ||
231 | request.post(requestOptions, function(error, response, body) { | 235 | request.post(requestOptions, function(error, response, body) { |
232 | if (error) { | 236 | if (error) { |
233 | let rc = '68'; | 237 | let rc = '68'; |
234 | 238 | ||
235 | if (!isCheckStatus && (error.syscall == 'connect')) { | 239 | if (!isCheckStatus && (error.syscall == 'connect')) { |
236 | rc = '91'; | 240 | rc = '91'; |
237 | } | 241 | } |
238 | 242 | ||
239 | logger.warn('Error requesting to partner', {task: task, rc: rc, error: error, isCheckStatus: isCheckStatus}); | 243 | logger.warn('Error requesting to partner', {task: task, rc: rc, error: error, isCheckStatus: isCheckStatus}); |
240 | callbackReport(task.requestId, rc, 'Error requesting to partner. ' + error, {task: task}); | 244 | callbackReport(task.requestId, rc, 'Error requesting to partner. ' + error, {task: task}); |
241 | return; | 245 | return; |
242 | } | 246 | } |
243 | 247 | ||
244 | if (response.statusCode != 200) { | 248 | if (response.statusCode != 200) { |
245 | let rc = '68'; | 249 | let rc = '68'; |
246 | 250 | ||
247 | logger.warn('HTTP status code is not 200', {task: task, http_status_code: response.statusCode, isCheckStatus: isCheckStatus}); | 251 | logger.warn('HTTP status code is not 200', {task: task, http_status_code: response.statusCode, isCheckStatus: isCheckStatus}); |
248 | callbackReport(task.requestId, rc, 'HTTP status code ' + response.statusCode, {task: task}); | 252 | callbackReport(task.requestId, rc, 'HTTP status code ' + response.statusCode, {task: task}); |
249 | return; | 253 | return; |
250 | } | 254 | } |
251 | 255 | ||
252 | logger.info('Transaksi sedang diproses', {task: task, response_body: body}); | 256 | logger.info('Transaksi sedang diproses', {task: task, response_body: body}); |
253 | 257 | ||
254 | _responseBodyHandler(body, task); | 258 | _responseBodyHandler(body, task); |
255 | 259 | ||
256 | }) | 260 | }) |
257 | } | 261 | } |
258 | 262 | ||
259 | function _getPropertyFromObjectSafe(obj, property) { | 263 | function _getPropertyFromObjectSafe(obj, property) { |
260 | let retval; | 264 | let retval; |
261 | 265 | ||
262 | if (!obj) { | 266 | if (!obj) { |
263 | logger.warn('Invalid object') | 267 | logger.warn('Invalid object') |
264 | return; | 268 | return; |
265 | } | 269 | } |
266 | 270 | ||
267 | try { | 271 | try { |
268 | retval = obj[property]; | 272 | retval = obj[property]; |
269 | } | 273 | } |
270 | catch(e) { | 274 | catch(e) { |
271 | logger.warn('Error getting ' + property + ' from object'); | 275 | logger.warn('Error getting ' + property + ' from object'); |
272 | } | 276 | } |
273 | 277 | ||
274 | return retval; | 278 | return retval; |
275 | } | 279 | } |
276 | 280 | ||
277 | function topupRequest(task) { | 281 | function topupRequest(task) { |
278 | aaa.insertTaskToMongoDb(task); | 282 | aaa.insertTaskToMongoDb(task); |
279 | _hitTopup(task); | 283 | _hitTopup(task); |
280 | } | 284 | } |
281 | 285 | ||
282 | function checkStatus(task) { | 286 | function checkStatus(task) { |
283 | _hitTopup(task, true); | 287 | _hitTopup(task, true); |
284 | } | 288 | } |
285 | 289 | ||
286 | 290 | ||
287 | function reverseReportHandler(body) { | 291 | function reverseReportHandler(body) { |
288 | logger.info('Got reverse report', {body: body}); | 292 | logger.info('Got reverse report', {body: body}); |
289 | } | 293 | } |
290 | 294 | ||
291 | function createReverseHttpServer() { | 295 | function createReverseHttpServer() { |
292 | var httpServer = http.createServer(function(request, response) { | 296 | var httpServer = http.createServer(function(request, response) { |
293 | 297 | ||
294 | logger.info('Got request from partner'); | 298 | logger.info('Got request from partner'); |
295 | 299 | ||
296 | var body = ""; | 300 | var body = ""; |
297 | request.on('data', function (chunk) { | 301 | request.on('data', function (chunk) { |
298 | body += chunk; | 302 | body += chunk; |
299 | }); | 303 | }); |
300 | 304 | ||
301 | request.on('end', function () { | 305 | request.on('end', function () { |
302 | response.writeHead(200); | 306 | response.writeHead(200); |
303 | response.end('OK'); | 307 | response.end('OK'); |
304 | 308 | ||
305 | reverseReportHandler(body); | 309 | reverseReportHandler(body); |
306 | }); | 310 | }); |
307 | 311 | ||
308 | }); | 312 | }); |
309 | 313 | ||
310 | httpServer.listen(config.h2h_out.listen_port, function() { | 314 | httpServer.listen(config.h2h_out.listen_port, function() { |
311 | logger.info('HTTP Reverse/Report server listen on port ' + config.h2h_out.listen_port); | 315 | logger.info('HTTP Reverse/Report server listen on port ' + config.h2h_out.listen_port); |
312 | }); | 316 | }); |
313 | } | 317 | } |
314 | 318 | ||
315 | 319 | ||
316 | exports.calculateSign = calculateSign; | 320 | exports.calculateSign = calculateSign; |
317 | exports.start = start; | 321 | exports.start = start; |
318 | exports.topupRequest = topupRequest; | 322 | exports.topupRequest = topupRequest; |
319 | exports.checkStatus = checkStatus; | 323 | exports.checkStatus = checkStatus; |
320 | 324 |