Commit d05e75946eba1441ff17cb0fb026d634a3f1cbc8
1 parent
12f6d059c5
Exists in
master
and in
1 other branch
CORE-CALLBACK dumper
Showing 4 changed files with 189 additions and 1 deletions Inline Diff
lib/core-callback/dumper/req.js
File was created | 1 | const MODULE_NAME = 'CORE-CALLBACK.DUMPER.REQ'; | |
2 | |||
3 | const fs = require('fs'); | ||
4 | const fsPromise = require('fs').promises; | ||
5 | const path = require('path'); | ||
6 | const config = require('komodo-sdk/config'); | ||
7 | const logger = require('komodo-sdk/logger'); | ||
8 | const moment = require('moment'); | ||
9 | |||
10 | const baseDumpDir = 'dump'; | ||
11 | const subBaseDumpDir = path.join(baseDumpDir, 'core-callback'); | ||
12 | const lastSymbolicLinkName = path.join(subBaseDumpDir, 'last-req'); | ||
13 | |||
14 | if (!fs.existsSync(baseDumpDir)) { | ||
15 | logger.verbose(`${MODULE_NAME} B5785525: Creating base dump dir`); | ||
16 | fs.mkdirSync(baseDumpDir); | ||
17 | } | ||
18 | |||
19 | if (!fs.existsSync(subBaseDumpDir)) { | ||
20 | logger.verbose(`${MODULE_NAME} 13BD289A: Creating dump dir`); | ||
21 | fs.mkdirSync(subBaseDumpDir); | ||
22 | } | ||
23 | |||
24 | module.exports = async (req, res, next) => { | ||
25 | if ( | ||
26 | !config | ||
27 | || !config.listener | ||
28 | || !config.listener.core | ||
29 | || !config.listener.core.dump | ||
30 | ) { | ||
31 | next(); | ||
32 | return; | ||
33 | } | ||
34 | |||
35 | const { xid } = res.locals; | ||
36 | |||
37 | const data = `-------- | ||
38 | XID: ${xid} | ||
39 | PID: ${process.pid} | ||
40 | DATE: ${moment().format('YYYY-MM-DD HH:mm:ss.SSS')} | ||
41 | |||
42 | REQ-CONTENT-TYPE: ${req.get('content-type')} | ||
43 | |||
44 | REQ QUERY-STRING: | ||
45 | ${JSON.stringify(req.query, null, 2)} | ||
46 | |||
47 | REQ BODY: | ||
48 | ${JSON.stringify(req.body, null, 2)} | ||
49 | `; | ||
50 | |||
51 | const dumpDir = path.join( | ||
52 | subBaseDumpDir, | ||
53 | moment().format('YYYY-MM-DD'), | ||
54 | ); | ||
55 | |||
56 | try { | ||
57 | await fsPromise.stat(dumpDir); | ||
58 | } catch (e) { | ||
59 | await fsPromise.mkdir(dumpDir, { recursive: true }); | ||
60 | } | ||
61 | |||
62 | const dumpFileName = path.join( | ||
63 | dumpDir, | ||
64 | [ | ||
65 | 'req', | ||
66 | moment().format('YYMMDD_HHmmss_SSS'), | ||
67 | xid, | ||
68 | ].join('_'), | ||
69 | ); | ||
70 | |||
71 | await fsPromise.writeFile(dumpFileName, data); | ||
72 | |||
73 | try { | ||
74 | await fsPromise.unlink(lastSymbolicLinkName); | ||
75 | } catch (e) { | ||
76 | // | ||
77 | } | ||
78 | |||
79 | await fs.symlink(dumpFileName, lastSymbolicLinkName); | ||
80 | |||
81 | next(); | ||
82 | }; | ||
83 |
lib/core-callback/dumper/sender.js
File was created | 1 | const MODULE_NAME = 'CORE-CALLBACK.DUMPER.SENDER'; | |
2 | |||
3 | const fs = require('fs'); | ||
4 | const fsPromise = require('fs').promises; | ||
5 | const path = require('path'); | ||
6 | const config = require('komodo-sdk/config'); | ||
7 | const logger = require('komodo-sdk/logger'); | ||
8 | const moment = require('moment'); | ||
9 | |||
10 | const baseDumpDir = 'dump'; | ||
11 | const subBaseDumpDir = path.join(baseDumpDir, 'core-callback'); | ||
12 | const lastSymbolicLinkName = path.join(subBaseDumpDir, 'last-sent'); | ||
13 | |||
14 | if (!fs.existsSync(baseDumpDir)) { | ||
15 | logger.verbose(`${MODULE_NAME} CE7EA06A: Creating base dump dir`); | ||
16 | fs.mkdirSync(baseDumpDir); | ||
17 | } | ||
18 | |||
19 | if (!fs.existsSync(subBaseDumpDir)) { | ||
20 | logger.verbose(`${MODULE_NAME} A9807434: Creating dump dir`); | ||
21 | fs.mkdirSync(subBaseDumpDir); | ||
22 | } | ||
23 | |||
24 | module.exports = async (xid, httpMethod, endpointUrl, params, axiosResponse, axiosError) => { | ||
25 | if ( | ||
26 | !config | ||
27 | || !config.listener | ||
28 | || !config.listener.core | ||
29 | || !config.listener.core.dump | ||
30 | ) { | ||
31 | return; | ||
32 | } | ||
33 | |||
34 | const data = `-------- | ||
35 | XID: ${xid} | ||
36 | PID: ${process.pid} | ||
37 | DATE: ${moment().format('YYYY-MM-DD HH:mm:ss.SSS')} | ||
38 | |||
39 | HTTP METHOD: ${httpMethod} | ||
40 | ENDPOINT URL: ${endpointUrl} | ||
41 | |||
42 | REQ PARAMS: | ||
43 | ${JSON.stringify(params, null, 2)} | ||
44 | |||
45 | ERROR CODE: ${(axiosError && axiosError.code) || ''} | ||
46 | ERROR MESSAGE: ${(axiosError && axiosError.message) || ''} | ||
47 | |||
48 | RES HTTP STATUS: ${axiosResponse && axiosResponse.status} | ||
49 | RES BODY: | ||
50 | ${ | ||
51 | axiosResponse && axiosResponse.data && ( | ||
52 | ((typeof axiosResponse.data === 'string') && axiosResponse.data) | ||
53 | || JSON.stringify(axiosResponse.data, null, 2) | ||
54 | ) | ||
55 | } | ||
56 | `; | ||
57 | |||
58 | const dumpDir = path.join( | ||
59 | subBaseDumpDir, | ||
60 | moment().format('YYYY-MM-DD'), | ||
61 | ); | ||
62 | |||
63 | try { | ||
64 | await fsPromise.stat(dumpDir); | ||
65 | } catch (e) { | ||
66 | await fsPromise.mkdir(dumpDir, { recursive: true }); | ||
67 | } | ||
68 | |||
69 | const dumpFileName = path.join( | ||
70 | dumpDir, | ||
71 | [ | ||
72 | 'sent', | ||
73 | moment().format('YYMMDD_HHmmss_SSS'), | ||
74 | xid, | ||
75 | ].join('_'), | ||
76 | ); | ||
77 | |||
78 | await fsPromise.writeFile(dumpFileName, data); | ||
79 | |||
80 | try { | ||
81 | await fsPromise.unlink(lastSymbolicLinkName); | ||
82 | } catch (e) { | ||
83 | // | ||
84 | } | ||
85 | |||
86 | await fs.symlink(dumpFileName, lastSymbolicLinkName); | ||
87 | }; | ||
88 |
lib/core-callback/index.js
1 | const MODULE_NAME = 'CORE-CALLBACK'; | 1 | const MODULE_NAME = 'CORE-CALLBACK'; |
2 | 2 | ||
3 | const DEFAULT_LISTENER_FROM_CORE = 25613; | 3 | const DEFAULT_LISTENER_FROM_CORE = 25613; |
4 | 4 | ||
5 | const express = require('express'); | 5 | const express = require('express'); |
6 | const config = require('komodo-sdk/config'); | 6 | const config = require('komodo-sdk/config'); |
7 | const logger = require('komodo-sdk/logger'); | 7 | const logger = require('komodo-sdk/logger'); |
8 | const middlewareCommon = require('../middlewares/common'); | 8 | const middlewareCommon = require('../middlewares/common'); |
9 | const sender = require('./sender'); | 9 | const sender = require('./sender'); |
10 | const dumperReq = require('./dumper/req'); | ||
10 | const matrix = require('../matrix'); | 11 | const matrix = require('../matrix'); |
11 | 12 | ||
12 | |||
13 | const app = express(); | 13 | const app = express(); |
14 | 14 | ||
15 | app.use(express.json({ extended: true })); | 15 | app.use(express.json({ extended: true })); |
16 | app.use(express.urlencoded({ extended: true })); | 16 | app.use(express.urlencoded({ extended: true })); |
17 | 17 | ||
18 | app.use((req, res, next) => { | 18 | app.use((req, res, next) => { |
19 | res.locals.httpgetx_subsystem = MODULE_NAME; | 19 | res.locals.httpgetx_subsystem = MODULE_NAME; |
20 | next(); | 20 | next(); |
21 | }); | 21 | }); |
22 | 22 | ||
23 | app.use(middlewareCommon); | 23 | app.use(middlewareCommon); |
24 | app.use(dumperReq); | ||
24 | 25 | ||
25 | app.use((req, res) => { | 26 | app.use((req, res) => { |
26 | matrix.core.received += 1; | 27 | matrix.core.received += 1; |
27 | res.end('OK'); | 28 | res.end('OK'); |
28 | sender(req.query, res.locals.xid); | 29 | sender(req.query, res.locals.xid); |
29 | }); | 30 | }); |
30 | 31 | ||
31 | const port = (config.listener && config.listener.core && config.listener.core.port) | 32 | const port = (config.listener && config.listener.core && config.listener.core.port) |
32 | || DEFAULT_LISTENER_FROM_CORE; | 33 | || DEFAULT_LISTENER_FROM_CORE; |
33 | 34 | ||
34 | app.listen(port, () => { | 35 | app.listen(port, () => { |
35 | logger.info(`${MODULE_NAME} 0375DC4E: Listen from CORE callback on port ${port}`); | 36 | logger.info(`${MODULE_NAME} 0375DC4E: Listen from CORE callback on port ${port}`); |
36 | }).on('error', (e) => { | 37 | }).on('error', (e) => { |
37 | logger.error(`${MODULE_NAME} A90E42D5: Can not listen CORE callback on port ${port}. ${e.toString()}`); | 38 | logger.error(`${MODULE_NAME} A90E42D5: Can not listen CORE callback on port ${port}. ${e.toString()}`); |
38 | process.exit(1); | 39 | process.exit(1); |
39 | }); | 40 | }); |
lib/core-callback/sender.js
1 | const MODULE_NAME = 'CORE-CALLBACK.SENDER'; | 1 | const MODULE_NAME = 'CORE-CALLBACK.SENDER'; |
2 | 2 | ||
3 | const axios = require('axios').default; | 3 | const axios = require('axios').default; |
4 | const config = require('komodo-sdk/config'); | 4 | const config = require('komodo-sdk/config'); |
5 | const logger = require('komodo-sdk/logger'); | 5 | const logger = require('komodo-sdk/logger'); |
6 | 6 | ||
7 | const dumper = require('./dumper/sender'); | ||
7 | const matrix = require('../matrix'); | 8 | const matrix = require('../matrix'); |
8 | 9 | ||
9 | const HTTP_TIMEOUT = Number( | 10 | const HTTP_TIMEOUT = Number( |
10 | config.callback_sender && config.callback_sender.http_timeout_ms, | 11 | config.callback_sender && config.callback_sender.http_timeout_ms, |
11 | ) || 30 * 1000; | 12 | ) || 30 * 1000; |
12 | 13 | ||
13 | const SLEEP_BEFORE_RETRY_MS = Number( | 14 | const SLEEP_BEFORE_RETRY_MS = Number( |
14 | config.callback_sender && config.callback_sender.sleep_before_retry_ms, | 15 | config.callback_sender && config.callback_sender.sleep_before_retry_ms, |
15 | ) || 10 * 1000; | 16 | ) || 10 * 1000; |
16 | 17 | ||
17 | const MAX_RETRY = Number( | 18 | const MAX_RETRY = Number( |
18 | config.callback_sender && config.callback_sender.max_retry, | 19 | config.callback_sender && config.callback_sender.max_retry, |
19 | ) || 10; | 20 | ) || 10; |
20 | 21 | ||
21 | logger.verbose(`${MODULE_NAME} 848B9104: Initialized`, { | 22 | logger.verbose(`${MODULE_NAME} 848B9104: Initialized`, { |
22 | HTTP_TIMEOUT, | 23 | HTTP_TIMEOUT, |
23 | SLEEP_BEFORE_RETRY_MS, | 24 | SLEEP_BEFORE_RETRY_MS, |
24 | MAX_RETRY, | 25 | MAX_RETRY, |
25 | }); | 26 | }); |
26 | 27 | ||
27 | const axiosHeaders = { | 28 | const axiosHeaders = { |
28 | 'Content-Type': 'application/json', | 29 | 'Content-Type': 'application/json', |
29 | 'User-Agent': 'KOMODO-HTTPGETX callback sender', | 30 | 'User-Agent': 'KOMODO-HTTPGETX callback sender', |
30 | }; | 31 | }; |
31 | 32 | ||
32 | const sleep = require('../sleep'); | 33 | const sleep = require('../sleep'); |
33 | const urlConcatQs = require('../url-concat-qs'); | 34 | const urlConcatQs = require('../url-concat-qs'); |
34 | 35 | ||
35 | const sender = async (data, xid, retry) => { | 36 | const sender = async (data, xid, retry) => { |
36 | if (!data.reverse_url) { | 37 | if (!data.reverse_url) { |
37 | logger.verbose(`${MODULE_NAME} C4FF18FB: Ignoring missing reverse url`, { | 38 | logger.verbose(`${MODULE_NAME} C4FF18FB: Ignoring missing reverse url`, { |
38 | xid, | 39 | xid, |
39 | dataFromCore: data, | 40 | dataFromCore: data, |
40 | }); | 41 | }); |
41 | 42 | ||
42 | return; | 43 | return; |
43 | } | 44 | } |
44 | 45 | ||
45 | const params = { | 46 | const params = { |
46 | httpgetx_xid: xid, | 47 | httpgetx_xid: xid, |
47 | command: data.command, | 48 | command: data.command, |
48 | 49 | ||
49 | request_id: data.request_id && data.request_id.toString(), | 50 | request_id: data.request_id && data.request_id.toString(), |
50 | transaction_id: data.transaction_id && data.transaction_id.toString(), | 51 | transaction_id: data.transaction_id && data.transaction_id.toString(), |
51 | transaction_date: data.transaction_date, | 52 | transaction_date: data.transaction_date, |
52 | 53 | ||
53 | store_name: data.store_name, | 54 | store_name: data.store_name, |
54 | terminal_name: data.terminal_name, | 55 | terminal_name: data.terminal_name, |
55 | 56 | ||
56 | product_name: data.product_name, | 57 | product_name: data.product_name, |
57 | destination: data.destination, | 58 | destination: data.destination, |
58 | 59 | ||
59 | rc: data.rc, | 60 | rc: data.rc, |
60 | sn: data.sn || undefined, | 61 | sn: data.sn || undefined, |
61 | amount: Number(data.amount) || undefined, | 62 | amount: Number(data.amount) || undefined, |
62 | ending_balance: Number(data.ending_balance) || undefined, | 63 | ending_balance: Number(data.ending_balance) || undefined, |
63 | 64 | ||
64 | message: data.message, | 65 | message: data.message, |
65 | 66 | ||
66 | bill_count: Number(data.bill_count) || undefined, | 67 | bill_count: Number(data.bill_count) || undefined, |
67 | bill_amount: Number(data.bill_amount) || undefined, | 68 | bill_amount: Number(data.bill_amount) || undefined, |
68 | fee_per_bill: Number(data.fee) || undefined, | 69 | fee_per_bill: Number(data.fee) || undefined, |
69 | fee_total: Number(data.fee_total) || undefined, | 70 | fee_total: Number(data.fee_total) || undefined, |
70 | 71 | ||
71 | bill_detail: data.bill_detail || undefined, | 72 | bill_detail: data.bill_detail || undefined, |
72 | customer_data: data.customer_data || undefined, | 73 | customer_data: data.customer_data || undefined, |
73 | }; | 74 | }; |
74 | 75 | ||
75 | if (data.command === 'INQUIRY' && data.amount_to_charge) { | 76 | if (data.command === 'INQUIRY' && data.amount_to_charge) { |
76 | params.amount_to_charge = data.amount_to_charge; | 77 | params.amount_to_charge = data.amount_to_charge; |
77 | } | 78 | } |
78 | 79 | ||
79 | const isPostpaid = ['INQUIRY', 'PAY'].indexOf(data.command) >= 0; | 80 | const isPostpaid = ['INQUIRY', 'PAY'].indexOf(data.command) >= 0; |
80 | const isHttpPost = isPostpaid; | 81 | const isHttpPost = isPostpaid; |
81 | 82 | ||
82 | const endpointUrl = isHttpPost ? data.reverse_url : urlConcatQs(data.reverse_url, params); | 83 | const endpointUrl = isHttpPost ? data.reverse_url : urlConcatQs(data.reverse_url, params); |
83 | 84 | ||
84 | logger.info(`${MODULE_NAME} 8B6A4CEC: Sending to PARTNER`, { | 85 | logger.info(`${MODULE_NAME} 8B6A4CEC: Sending to PARTNER`, { |
85 | xid, | 86 | xid, |
86 | retry, | 87 | retry, |
87 | isPostpaid, | 88 | isPostpaid, |
88 | isHttpPost, | 89 | isHttpPost, |
89 | endpointUrl, | 90 | endpointUrl, |
90 | }); | 91 | }); |
91 | 92 | ||
93 | let responseToDump; | ||
94 | let errorResponseToDump; | ||
95 | |||
92 | try { | 96 | try { |
93 | const response = isHttpPost | 97 | const response = isHttpPost |
94 | ? await axios.post(data.reverse_url, params, { | 98 | ? await axios.post(data.reverse_url, params, { |
95 | timeout: HTTP_TIMEOUT, | 99 | timeout: HTTP_TIMEOUT, |
96 | headers: axiosHeaders, | 100 | headers: axiosHeaders, |
97 | }) | 101 | }) |
98 | : await axios.get(data.reverse_url, { | 102 | : await axios.get(data.reverse_url, { |
99 | params, | 103 | params, |
100 | timeout: HTTP_TIMEOUT, | 104 | timeout: HTTP_TIMEOUT, |
101 | headers: axiosHeaders, | 105 | headers: axiosHeaders, |
102 | }); | 106 | }); |
103 | 107 | ||
108 | responseToDump = response; | ||
109 | |||
104 | matrix.callback_sender.sent += 1; | 110 | matrix.callback_sender.sent += 1; |
105 | matrix.callback_sender.active_count += 1; | 111 | matrix.callback_sender.active_count += 1; |
106 | matrix.callback_sender.active_sending[xid] = { | 112 | matrix.callback_sender.active_sending[xid] = { |
107 | ts: new Date(), | 113 | ts: new Date(), |
108 | trxId: data.trx_id, | 114 | trxId: data.trx_id, |
109 | reverseUrl: data.reverse_url, | 115 | reverseUrl: data.reverse_url, |
110 | }; | 116 | }; |
111 | 117 | ||
112 | if (isPostpaid) { | 118 | if (isPostpaid) { |
113 | matrix.callback_sender.sent_using_post += 1; | 119 | matrix.callback_sender.sent_using_post += 1; |
114 | } else { | 120 | } else { |
115 | matrix.callback_sender.sent_using_get += 1; | 121 | matrix.callback_sender.sent_using_get += 1; |
116 | } | 122 | } |
117 | 123 | ||
118 | logger.info(`${MODULE_NAME} 3641FBD7: Has been sent to PARTNER successfully`, { | 124 | logger.info(`${MODULE_NAME} 3641FBD7: Has been sent to PARTNER successfully`, { |
119 | xid, | 125 | xid, |
120 | retry, | 126 | retry, |
121 | httpStatus: response.status, | 127 | httpStatus: response.status, |
122 | responseBody: response && response.data, | 128 | responseBody: response && response.data, |
123 | }); | 129 | }); |
124 | } catch (e) { | 130 | } catch (e) { |
125 | matrix.callback_sender.sent_failed += 1; | 131 | matrix.callback_sender.sent_failed += 1; |
126 | matrix.callback_sender.last_error = { | 132 | matrix.callback_sender.last_error = { |
127 | xid, | 133 | xid, |
128 | ts: new Date(), | 134 | ts: new Date(), |
129 | eCode: e.code, | 135 | eCode: e.code, |
130 | eMessage: e.message, | 136 | eMessage: e.message, |
131 | trxId: data.trx_id, | 137 | trxId: data.trx_id, |
132 | reverseUrl: data.reverse_url, | 138 | reverseUrl: data.reverse_url, |
133 | httpStatus: e.response && e.response.status, | 139 | httpStatus: e.response && e.response.status, |
134 | responseBody: e.response && e.response.data, | 140 | responseBody: e.response && e.response.data, |
135 | }; | 141 | }; |
136 | 142 | ||
143 | responseToDump = e.response && e.response.data; | ||
144 | errorResponseToDump = e; | ||
145 | |||
137 | logger.warn(`${MODULE_NAME} A1EC9E70: Failed on sending to PARTNER`, { | 146 | logger.warn(`${MODULE_NAME} A1EC9E70: Failed on sending to PARTNER`, { |
138 | xid, | 147 | xid, |
139 | retry, | 148 | retry, |
140 | maxRetry: MAX_RETRY, | 149 | maxRetry: MAX_RETRY, |
141 | errCode: e.code, | 150 | errCode: e.code, |
142 | errMessage: e.message, | 151 | errMessage: e.message, |
143 | reverseUrl: data.reverse_url, | 152 | reverseUrl: data.reverse_url, |
144 | endpointUrl, | 153 | endpointUrl, |
145 | httpStatus: e.response && e.response.status, | 154 | httpStatus: e.response && e.response.status, |
146 | responseBody: e.response && e.response.data, | 155 | responseBody: e.response && e.response.data, |
147 | }); | 156 | }); |
148 | 157 | ||
149 | if ((retry || 0) < MAX_RETRY) { | 158 | if ((retry || 0) < MAX_RETRY) { |
150 | await sleep(SLEEP_BEFORE_RETRY_MS); | 159 | await sleep(SLEEP_BEFORE_RETRY_MS); |
151 | logger.verbose(`${MODULE_NAME} D8958695: Going to retry sending CORE-CALLBACK TO PARTNER`, { | 160 | logger.verbose(`${MODULE_NAME} D8958695: Going to retry sending CORE-CALLBACK TO PARTNER`, { |
152 | xid, sleepTime: SLEEP_BEFORE_RETRY_MS, | 161 | xid, sleepTime: SLEEP_BEFORE_RETRY_MS, |
153 | }); | 162 | }); |
154 | sender(data, xid, (retry || 0) + 1); | 163 | sender(data, xid, (retry || 0) + 1); |
155 | } | 164 | } |
156 | } finally { | 165 | } finally { |
157 | matrix.callback_sender.active_count -= 1; | 166 | matrix.callback_sender.active_count -= 1; |
158 | if (matrix.callback_sender.active_sending[xid]) { | 167 | if (matrix.callback_sender.active_sending[xid]) { |
159 | delete matrix.callback_sender.active_sending[xid]; | 168 | delete matrix.callback_sender.active_sending[xid]; |
160 | } | 169 | } |
170 | |||
171 | dumper( | ||
172 | xid, | ||
173 | isHttpPost ? 'POST' : 'GET', | ||
174 | endpointUrl, | ||
175 | params, | ||
176 | responseToDump, | ||
177 | errorResponseToDump, | ||
178 | ); | ||
161 | } | 179 | } |
162 | }; | 180 | }; |
163 | 181 | ||
164 | module.exports = sender; | 182 | module.exports = sender; |
165 | 183 |