Commit d05e75946eba1441ff17cb0fb026d634a3f1cbc8

Authored by Adhidarma Hadiwinoto
1 parent 12f6d059c5
Exists in master and in 1 other branch dev

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