Commit 8c258ccd9821fd7f7081bf1b3b49665dbd6e4e1f

Authored by Adhidarma Hadiwinoto
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