Commit 8c258ccd9821fd7f7081bf1b3b49665dbd6e4e1f
1 parent
f02e2a18b2
Exists in
master
advice prepaid
Showing 6 changed files with 299 additions and 142 deletions Inline Diff
lib/hit/axios-error-is-safe.js
File was created | 1 | const safeErrorCodes = [ | |
2 | 'EHOSTUNREACH', | ||
3 | 'ENOTFOUND', | ||
4 | 'ECONNREFUSED', | ||
5 | 'ETIMEDOUT', // timeout on connecting | ||
6 | |||
7 | // Berikut adalah error code yang tidak aman untuk dianggap gagal | ||
8 | |||
9 | // terjadi karena timeout setelah konek (akibat pengaturan timeout pada request axios) | ||
10 | // 'ECONNABORTED', | ||
11 | ]; | ||
12 | |||
13 | module.exports = (e) => safeErrorCodes.indexOf(e.code) >= 0; | ||
14 |
lib/hit/dump-req-res.js
1 | const MODULE_NAME = 'DUMP-REQ-RES'; | 1 | const MODULE_NAME = 'DUMP-REQ-RES'; |
2 | 2 | ||
3 | const fs = require('fs'); | 3 | const fs = require('fs'); |
4 | const path = require('path'); | 4 | const path = require('path'); |
5 | const fsPromise = require('fs').promises; | 5 | const fsPromise = require('fs').promises; |
6 | const moment = require('moment'); | 6 | const moment = require('moment'); |
7 | 7 | ||
8 | const config = require('komodo-sdk/config'); | 8 | const config = require('komodo-sdk/config'); |
9 | const logger = require('komodo-sdk/logger'); | 9 | const logger = require('komodo-sdk/logger'); |
10 | 10 | ||
11 | const baseDumpDir = 'dump'; | 11 | const baseDumpDir = 'dump'; |
12 | const requestDumpDir = path.join(baseDumpDir, 'request'); | 12 | const requestDumpDir = path.join(baseDumpDir, 'request'); |
13 | 13 | ||
14 | if (!fs.existsSync(baseDumpDir)) { | 14 | if (!fs.existsSync(baseDumpDir)) { |
15 | logger.verbose(`${MODULE_NAME} 51105314: Creating base dump dir`); | 15 | logger.verbose(`${MODULE_NAME} 51105314: Creating base dump dir`); |
16 | fs.mkdirSync(baseDumpDir); | 16 | fs.mkdirSync(baseDumpDir); |
17 | } | 17 | } |
18 | 18 | ||
19 | if (!fs.existsSync(requestDumpDir)) { | 19 | if (!fs.existsSync(requestDumpDir)) { |
20 | logger.verbose(`${MODULE_NAME} 8A52891B: Creating request dump dir`); | 20 | logger.verbose(`${MODULE_NAME} 8A52891B: Creating request dump dir`); |
21 | fs.mkdirSync(requestDumpDir); | 21 | fs.mkdirSync(requestDumpDir); |
22 | } | 22 | } |
23 | 23 | ||
24 | module.exports = async ( | 24 | module.exports = async ( |
25 | xid, task, httpMethod, endpointUrl, params, responseBody, responseStatus, | 25 | xid, task, httpMethod, endpointUrl, params, responseBody, responseStatus, isAdvice, |
26 | ) => { | 26 | ) => { |
27 | if ( | 27 | if ( |
28 | !config | 28 | !config |
29 | || !config.partner | 29 | || !config.partner |
30 | || !config.partner.dump_request | 30 | || !config.partner.dump_request |
31 | ) { | 31 | ) { |
32 | return; | 32 | return; |
33 | } | 33 | } |
34 | 34 | ||
35 | const data = `-------- | 35 | const data = `-------- |
36 | XID: ${xid} | 36 | XID: ${xid} |
37 | PID: ${process.pid} | 37 | PID: ${process.pid} |
38 | DATE: ${moment().format('YYYY-MM-DD HH:mm:ss.SSS')} | 38 | DATE: ${moment().format('YYYY-MM-DD HH:mm:ss.SSS')} |
39 | 39 | ||
40 | TASK: | 40 | TASK: |
41 | ${JSON.stringify(task, null, 2)} | 41 | ${JSON.stringify(task, null, 2)} |
42 | 42 | ||
43 | IS-ADVICE: !!${isAdvice} | ||
43 | 44 | ||
44 | HTTP-METHOD: ${httpMethod} | 45 | HTTP-METHOD: ${httpMethod} |
45 | URL: ${endpointUrl} | 46 | URL: ${endpointUrl} |
46 | PARAMS: | 47 | PARAMS: |
47 | ${ | 48 | ${ |
48 | params && ( | 49 | params && ( |
49 | (typeof params === 'string' && params) | 50 | (typeof params === 'string' && params) |
50 | || JSON.stringify(params, null, 2) | 51 | || JSON.stringify(params, null, 2) |
51 | ) | 52 | ) |
52 | } | 53 | } |
53 | 54 | ||
54 | RESPONSE-STATUS: ${responseStatus} | 55 | RESPONSE-STATUS: ${responseStatus} |
55 | RESPONSE-BODY: | 56 | RESPONSE-BODY: |
56 | ${ | 57 | ${ |
57 | responseBody && ( | 58 | responseBody && ( |
58 | (typeof responseBody === 'string' && responseBody) | 59 | (typeof responseBody === 'string' && responseBody) |
59 | || JSON.stringify(responseBody, null, 2) | 60 | || JSON.stringify(responseBody, null, 2) |
60 | ) | 61 | ) |
61 | } | 62 | } |
63 | |||
62 | `; | 64 | `; |
63 | 65 | ||
64 | const dumpDir = path.join( | 66 | const dumpDir = path.join( |
65 | requestDumpDir, | 67 | requestDumpDir, |
66 | moment().format('YYYY-MM-DD'), | 68 | moment().format('YYYY-MM-DD'), |
67 | ); | 69 | ); |
68 | 70 | ||
69 | try { | 71 | try { |
70 | await fsPromise.stat(dumpDir); | 72 | await fsPromise.stat(dumpDir); |
71 | } catch { | 73 | } catch { |
72 | await fsPromise.mkdir(dumpDir, { recursive: true }); | 74 | await fsPromise.mkdir(dumpDir, { recursive: true }); |
73 | } | 75 | } |
74 | 76 | ||
75 | await fsPromise.writeFile( | 77 | await fsPromise.appendFile( |
76 | path.join( | 78 | path.join( |
77 | dumpDir, | 79 | dumpDir, |
78 | `trx_${task.trx_id}`, | 80 | `trx_${task.trx_id}`, |
79 | ), | 81 | ), |
80 | data, | 82 | data, |
81 | ); | 83 | ); |
82 | 84 | ||
83 | await fsPromise.writeFile( | 85 | await fsPromise.writeFile( |
84 | path.join(requestDumpDir, 'last'), | 86 | path.join(requestDumpDir, 'last'), |
85 | data, | 87 | data, |
86 | ); | 88 | ); |
87 | }; | 89 | }; |
88 | 90 |
lib/hit/prepaid-advice.js
File was created | 1 | const MODULE_NAME = 'HIT.PREPAID-ADVICE'; | |
2 | |||
3 | const axios = require('axios').default; | ||
4 | const urljoin = require('url-join'); | ||
5 | const uniqid = require('uniqid'); | ||
6 | |||
7 | const config = require('komodo-sdk/config'); | ||
8 | const logger = require('komodo-sdk/logger'); | ||
9 | |||
10 | const report = require('../report/prepaid'); | ||
11 | const translateRc = require('../translate-rc'); | ||
12 | const dumpReqRes = require('./dump-req-res'); | ||
13 | |||
14 | module.exports = async (task) => { | ||
15 | const xid = uniqid(); | ||
16 | |||
17 | logger.verbose(`${MODULE_NAME} 90350EF7: Processing task`, { | ||
18 | xid, | ||
19 | task, | ||
20 | }); | ||
21 | |||
22 | const params = { | ||
23 | request_id: task.trx_id, | ||
24 | terminal_name: config.partner.terminal_name, | ||
25 | password: config.partner.password, | ||
26 | }; | ||
27 | |||
28 | const endpointUrl = urljoin(config.partner.url, '/trx-status'); | ||
29 | let lastResponse = null; | ||
30 | |||
31 | try { | ||
32 | logger.verbose(`${MODULE_NAME} 453A48DC: Going to HIT ADVICE endpoint`, { | ||
33 | xid, | ||
34 | endpointUrl, | ||
35 | params, | ||
36 | }); | ||
37 | |||
38 | const response = await axios.get(endpointUrl, { | ||
39 | headers: { | ||
40 | 'User-Agent': 'KOMODO-GW-HTTPGETX', | ||
41 | }, | ||
42 | timeout: config.partner.hit_timeout_ms || 10 * 1000, | ||
43 | params, | ||
44 | }); | ||
45 | |||
46 | if (!response) { | ||
47 | const e = new Error(`${MODULE_NAME} FD20A1AF: Empty response`); | ||
48 | e.rc = '68'; | ||
49 | e.response = response; | ||
50 | throw e; | ||
51 | } | ||
52 | |||
53 | if (!response.data) { | ||
54 | const e = new Error(`${MODULE_NAME} 17FF8971: Empty response data`); | ||
55 | e.rc = '68'; | ||
56 | e.response = response; | ||
57 | throw e; | ||
58 | } | ||
59 | |||
60 | if (typeof response.data !== 'object') { | ||
61 | const e = new Error(`${MODULE_NAME} A6761E9F: Response data is not a JSON`); | ||
62 | e.rc = '68'; | ||
63 | e.response = response; | ||
64 | throw e; | ||
65 | } | ||
66 | |||
67 | lastResponse = response; | ||
68 | |||
69 | logger.verbose(`${MODULE_NAME} 3B5C70C4: Got a direct response`, { | ||
70 | xid, | ||
71 | responseBody: response.data, | ||
72 | }); | ||
73 | |||
74 | if (!response.data.trx_found || !response.data.trx) { | ||
75 | report(xid, { | ||
76 | trx_id: task.trx_id, | ||
77 | rc: '40', | ||
78 | message: { | ||
79 | xid, | ||
80 | 'ADVICE-DIRECT-RESPONSE': response.data, | ||
81 | }, | ||
82 | }); | ||
83 | |||
84 | return; | ||
85 | } | ||
86 | |||
87 | report(xid, { | ||
88 | trx_id: task.trx_id, | ||
89 | rc: response.data.trx.rc ? translateRc[response.data.trx.rc] || response.data.trx.rc | ||
90 | : '68', | ||
91 | sn: response.data.trx.sn || null, | ||
92 | amount: Number(response.data.trx.amount) || undefined, | ||
93 | balance: Number(response.data.trx.ending_balance) || undefined, | ||
94 | message: { | ||
95 | xid, | ||
96 | 'ADVICE-DIRECT-RESPONSE': response.data, | ||
97 | }, | ||
98 | }); | ||
99 | } catch (e) { | ||
100 | const rc = e.rc || '68'; | ||
101 | |||
102 | logger.warn(`${MODULE_NAME} 1427175A: Exception`, { | ||
103 | xid, | ||
104 | eCode: e.code, | ||
105 | eMessage: e.message, | ||
106 | eRc: e.rc, | ||
107 | rc, | ||
108 | responseHttpStatus: e.response && e.response.status, | ||
109 | responseBody: e.response && e.response.data, | ||
110 | }); | ||
111 | |||
112 | lastResponse = e.response; | ||
113 | |||
114 | report(xid, { | ||
115 | trx_id: task.trx_id, | ||
116 | rc, | ||
117 | message: { | ||
118 | xid, | ||
119 | 'KOMODO-GW-ADVICE-ERROR': { | ||
120 | eCode: e.code, | ||
121 | eMessage: e.message, | ||
122 | responseHttpStatus: e.response && e.response.status, | ||
123 | responseBody: e.response && e.response.data, | ||
124 | }, | ||
125 | }, | ||
126 | }); | ||
127 | } finally { | ||
128 | dumpReqRes( | ||
129 | xid, | ||
130 | task, | ||
131 | 'GET', | ||
132 | endpointUrl, | ||
133 | params, | ||
134 | lastResponse && lastResponse.data, | ||
135 | lastResponse && lastResponse.status, | ||
136 | lastResponse, | ||
137 | true, | ||
138 | ); | ||
139 | } | ||
140 | }; | ||
141 |
lib/hit/prepaid-topup.js
File was created | 1 | const MODULE_NAME = 'HIT.PREPAID-TOPUP'; | |
2 | |||
3 | const axios = require('axios').default; | ||
4 | const urljoin = require('url-join'); | ||
5 | const uniqid = require('uniqid'); | ||
6 | |||
7 | const config = require('komodo-sdk/config'); | ||
8 | const logger = require('komodo-sdk/logger'); | ||
9 | |||
10 | const report = require('../report/prepaid'); | ||
11 | const translateRc = require('../translate-rc'); | ||
12 | const composeCallbackUrl = require('./compose-callback-url'); | ||
13 | const dumpReqRes = require('./dump-req-res'); | ||
14 | const axiosErrorIsSafe = require('./axios-error-is-safe'); | ||
15 | |||
16 | module.exports = async (task) => { | ||
17 | const xid = uniqid(); | ||
18 | |||
19 | logger.verbose(`${MODULE_NAME} 2272F01F: Processing task`, { | ||
20 | xid, | ||
21 | task, | ||
22 | }); | ||
23 | |||
24 | const params = { | ||
25 | request_id: task.trx_id, | ||
26 | terminal_name: config.partner.terminal_name, | ||
27 | password: config.partner.password, | ||
28 | destination: task.destination, | ||
29 | product_name: task.remote_product, | ||
30 | reverse_url: composeCallbackUrl(xid, false), | ||
31 | }; | ||
32 | |||
33 | // const endpointUrl = isAdvice | ||
34 | // ? urljoin(config.partner.url, '/trx-status') | ||
35 | // : urljoin(config.partner.url, '/topup'); | ||
36 | |||
37 | const endpointUrl = urljoin(config.partner.url, '/topup'); | ||
38 | let lastResponse = null; | ||
39 | |||
40 | try { | ||
41 | logger.verbose(`${MODULE_NAME} 4AAD4F99: Going to HIT prepaid endpoint`, { | ||
42 | xid, | ||
43 | endpointUrl, | ||
44 | params, | ||
45 | }); | ||
46 | |||
47 | const response = await axios.get(endpointUrl, { | ||
48 | headers: { | ||
49 | 'User-Agent': 'KOMODO-GW-HTTPGETX', | ||
50 | }, | ||
51 | timeout: config.partner.hit_timeout_ms || 60 * 1000, | ||
52 | params, | ||
53 | }); | ||
54 | |||
55 | if (!response) { | ||
56 | const e = new Error(`${MODULE_NAME} 8CF4E04D: Empty response`); | ||
57 | e.rc = '68'; | ||
58 | e.response = response; | ||
59 | throw e; | ||
60 | } | ||
61 | |||
62 | if (!response.data) { | ||
63 | const e = new Error(`${MODULE_NAME} E72B5A53: Empty response data`); | ||
64 | e.rc = '68'; | ||
65 | e.response = response; | ||
66 | throw e; | ||
67 | } | ||
68 | |||
69 | if (typeof response.data !== 'object') { | ||
70 | const e = new Error(`${MODULE_NAME} 507680AB: Response data is not a JSON`); | ||
71 | e.rc = '68'; | ||
72 | e.response = response; | ||
73 | throw e; | ||
74 | } | ||
75 | |||
76 | lastResponse = response; | ||
77 | |||
78 | logger.verbose(`${MODULE_NAME} E51AFBBA: Got a direct response`, { | ||
79 | xid, | ||
80 | responseBody: response.data, | ||
81 | }); | ||
82 | |||
83 | report(xid, { | ||
84 | trx_id: task.trx_id, | ||
85 | rc: response.data.rc ? translateRc[response.data.rc] || response.data.rc | ||
86 | : '68', | ||
87 | sn: response.data.sn || null, | ||
88 | amount: Number(response.data.amount) || undefined, | ||
89 | balance: Number(response.data.ending_balance) || undefined, | ||
90 | message: { | ||
91 | xid, | ||
92 | 'DIRECT-RESPONSE': response.data, | ||
93 | }, | ||
94 | }); | ||
95 | } catch (e) { | ||
96 | const rc = e.rc | ||
97 | || (axiosErrorIsSafe(e) && '91') | ||
98 | || '68'; | ||
99 | |||
100 | logger.warn(`${MODULE_NAME} 8E8E49F5: Exception`, { | ||
101 | xid, | ||
102 | eCode: e.code, | ||
103 | eMessage: e.message, | ||
104 | eRc: e.rc, | ||
105 | rc, | ||
106 | responseHttpStatus: e.response && e.response.status, | ||
107 | responseBody: e.response && e.response.data, | ||
108 | }); | ||
109 | |||
110 | lastResponse = e.response; | ||
111 | |||
112 | report(xid, { | ||
113 | trx_id: task.trx_id, | ||
114 | rc, | ||
115 | message: { | ||
116 | xid, | ||
117 | 'KOMODO-GW-ERROR': { | ||
118 | eCode: e.code, | ||
119 | eMessage: e.message, | ||
120 | responseHttpStatus: e.response && e.response.status, | ||
121 | responseBody: e.response && e.response.data, | ||
122 | }, | ||
123 | }, | ||
124 | }); | ||
125 | } finally { | ||
126 | dumpReqRes( | ||
127 | xid, | ||
128 | task, | ||
129 | 'GET', | ||
130 | endpointUrl, | ||
131 | params, | ||
132 | lastResponse && lastResponse.data, | ||
133 | lastResponse && lastResponse.status, | ||
134 | lastResponse, | ||
135 | false, | ||
136 | ); | ||
137 | } | ||
138 | }; | ||
139 |
lib/hit/prepaid.js
1 | const MODULE_NAME = 'HIT.PREPAID'; | File was deleted | |
2 | |||
3 | const axios = require('axios').default; | ||
4 | const urljoin = require('url-join'); | ||
5 | const uniqid = require('uniqid'); | ||
6 | |||
7 | const config = require('komodo-sdk/config'); | ||
8 | const logger = require('komodo-sdk/logger'); | ||
9 | |||
10 | const report = require('../report/prepaid'); | ||
11 | const translateRc = require('../translate-rc'); | ||
12 | const composeCallbackUrl = require('./compose-callback-url'); | ||
13 | const dumpReqRes = require('./dump-req-res'); | ||
14 | |||
15 | module.exports = async (task, isAdvice) => { | ||
16 | const xid = uniqid(); | ||
17 | |||
18 | logger.verbose(`${MODULE_NAME} 2272F01F: Processing task`, { | ||
19 | xid, | ||
20 | isAdvice, | ||
21 | task, | ||
22 | }); | ||
23 | |||
24 | const params = { | ||
25 | request_id: task.trx_id, | ||
26 | terminal_name: config.partner.terminal_name, | ||
27 | password: config.partner.password, | ||
28 | destination: task.destination, | ||
29 | product_name: task.remote_product, | ||
30 | reverse_url: composeCallbackUrl(xid, false), | ||
31 | }; | ||
32 | |||
33 | const endpointUrl = isAdvice | ||
34 | ? urljoin(config.partner.url, '/trx-status') | ||
35 | : urljoin(config.partner.url, '/topup'); | ||
36 | |||
37 | let lastResponse = null; | ||
38 | |||
39 | try { | ||
40 | logger.verbose(`${MODULE_NAME} 4AAD4F99: Going to HIT prepaid endpoint`, { | ||
41 | xid, | ||
42 | endpointUrl, | ||
43 | params, | ||
44 | }); | ||
45 | |||
46 | const response = await axios.get(endpointUrl, { | ||
47 | headers: { | ||
48 | 'User-Agent': 'KOMODO-GW-HTTPGETX', | ||
49 | }, | ||
50 | timeout: config.partner.hit_timeout_ms || 60 * 1000, | ||
51 | params, | ||
52 | }); | ||
53 | |||
54 | if (!response) { | ||
55 | const e = new Error(`${MODULE_NAME} 8CF4E04D: Empty response`); | ||
56 | e.rc = '68'; | ||
57 | e.response = response; | ||
58 | throw e; | ||
59 | } | ||
60 | |||
61 | if (!response.data) { | ||
62 | const e = new Error(`${MODULE_NAME} E72B5A53: Empty response data`); | ||
63 | e.rc = '68'; | ||
64 | e.response = response; | ||
65 | throw e; | ||
66 | } | ||
67 | |||
68 | if (typeof response.data !== 'object') { | ||
69 | const e = new Error(`${MODULE_NAME} 507680AB: Response data is not a JSON`); | ||
70 | e.rc = '68'; | ||
71 | e.response = response; | ||
72 | throw e; | ||
73 | } | ||
74 | |||
75 | lastResponse = response; | ||
76 | |||
77 | logger.verbose(`${MODULE_NAME} E51AFBBA: Got a direct response`, { | ||
78 | xid, | ||
79 | responseBody: response.data, | ||
80 | }); | ||
81 | |||
82 | report(xid, { | ||
83 | trx_id: task.trx_id, | ||
84 | rc: response.data.rc ? translateRc[response.data.rc] || response.data.rc | ||
85 | : '68', | ||
86 | sn: response.data.sn || null, | ||
87 | amount: Number(response.data.amount) || undefined, | ||
88 | balance: Number(response.data.ending_balance) || undefined, | ||
89 | message: { | ||
90 | xid, | ||
91 | 'DIRECT-RESPONSE': response.data, | ||
92 | 'IS-ADVICE': !!isAdvice, | ||
93 | }, | ||
94 | }); | ||
95 | } catch (e) { | ||
96 | const rc = e.rc || '68'; | ||
97 | |||
98 | logger.warn(`${MODULE_NAME} 8E8E49F5: Exception`, { | ||
99 | xid, | ||
100 | eCode: e.code, | ||
101 | eMessage: e.message, | ||
102 | eRc: e.rc, | ||
103 | rc, | ||
104 | isAdvice, | ||
105 | responseHttpStatus: e.response && e.response.status, | ||
106 | responseBody: e.response && e.response.data, | ||
107 | }); | ||
108 | |||
109 | lastResponse = e.response; | ||
110 | |||
111 | report(xid, { | ||
112 | trx_id: task.trx_id, | ||
113 | rc, | ||
114 | message: { | ||
115 | xid, | ||
116 | 'KOMODO-GW-ERROR': { | ||
117 | eCode: e.code, | ||
118 | eMessage: e.message, | ||
119 | responseHttpStatus: e.response && e.response.status, | ||
120 | responseBody: e.response && e.response.data, | ||
121 | }, | ||
122 | 'IS-ADVICE': !!isAdvice, | ||
123 | }, | ||
124 | }); | ||
125 | } finally { | ||
126 | dumpReqRes( | ||
127 | xid, | ||
128 | task, | ||
129 | 'GET', | ||
130 | endpointUrl, | ||
131 | params, | ||
132 | lastResponse && lastResponse.data, | ||
133 | lastResponse && lastResponse.status, | ||
134 | lastResponse, | ||
135 | ); | ||
136 | } | ||
137 | }; | ||
138 | 1 | const MODULE_NAME = 'HIT.PREPAID'; |
lib/partner-prepaid.js
1 | const hit = require('./hit/prepaid'); | 1 | const hitAdvice = require('./hit/prepaid-advice'); |
2 | const hitTopup = require('./hit/prepaid-topup'); | ||
2 | 3 | ||
3 | exports.buy = (task) => { | 4 | exports.buy = (task) => { |
4 | hit(task, false); | 5 | hitTopup(task); |
5 | }; | 6 | }; |
6 | 7 | ||
7 | exports.advice = (task) => { | 8 | exports.advice = (task) => { |
8 | hit(task, true); | 9 | hitAdvice(task); |
9 | }; | 10 | }; |
10 | 11 |