Commit 385d8ff5945e244b3d4efaee1f68c8b54753cc7b
1 parent
d4661aa843
Exists in
master
PREPAID topup tested
Showing 7 changed files with 158 additions and 7 deletions Inline Diff
lib/callback/dumper.js
1 | const MODULE_NAME = 'CALLBACK.DUMPER'; | 1 | const MODULE_NAME = 'CALLBACK.DUMPER'; |
2 | 2 | ||
3 | const fs = require('fs'); | 3 | const fs = require('fs'); |
4 | const fsPromise = require('fs').promises; | 4 | const fsPromise = require('fs').promises; |
5 | const path = require('path'); | 5 | const path = require('path'); |
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 moment = require('moment'); | 8 | const moment = require('moment'); |
9 | 9 | ||
10 | const baseDumpDir = 'dump'; | 10 | const baseDumpDir = 'dump'; |
11 | const callbackDumpDir = path.join(baseDumpDir, 'callback'); | 11 | const callbackDumpDir = path.join(baseDumpDir, 'callback'); |
12 | 12 | ||
13 | if (!fs.existsSync(baseDumpDir)) { | 13 | if (!fs.existsSync(baseDumpDir)) { |
14 | logger.verbose(`${MODULE_NAME} D7E3D88E: Creating base dump dir`); | 14 | logger.verbose(`${MODULE_NAME} D7E3D88E: Creating base dump dir`); |
15 | fs.mkdirSync(baseDumpDir); | 15 | fs.mkdirSync(baseDumpDir); |
16 | } | 16 | } |
17 | 17 | ||
18 | if (!fs.existsSync(callbackDumpDir)) { | 18 | if (!fs.existsSync(callbackDumpDir)) { |
19 | logger.verbose(`${MODULE_NAME} 002EC4A8: Creating callback dump dir`); | 19 | logger.verbose(`${MODULE_NAME} 002EC4A8: Creating callback dump dir`); |
20 | fs.mkdirSync(callbackDumpDir); | 20 | fs.mkdirSync(callbackDumpDir); |
21 | } | 21 | } |
22 | 22 | ||
23 | module.exports = async (req, res, next) => { | 23 | module.exports = async (req, res, next) => { |
24 | if ( | 24 | if ( |
25 | !config | 25 | !config |
26 | || !config.partner | 26 | || !config.partner |
27 | || !config.partner.callback | 27 | || !config.partner.callback |
28 | || !config.partner.callback.dump_request | 28 | || !config.partner.callback.dump_request |
29 | ) { | 29 | ) { |
30 | next(); | 30 | next(); |
31 | return; | 31 | return; |
32 | } | 32 | } |
33 | 33 | ||
34 | const { xid } = res.locals; | 34 | const { xid } = res.locals; |
35 | 35 | ||
36 | const data = `-------- | 36 | const data = `-------- |
37 | XID: ${xid} | 37 | XID: ${xid} |
38 | PID: ${process.pid} | ||
38 | DATE: ${moment().format('YYYY-MM-DD HH:mm:ss.SSS')} | 39 | DATE: ${moment().format('YYYY-MM-DD HH:mm:ss.SSS')} |
39 | REMOTE-ADDR: ${req.ip} | 40 | REMOTE-ADDR: ${req.ip} |
40 | USER-AGENT: ${req.get('user-agent')} | 41 | USER-AGENT: ${req.get('user-agent')} |
41 | METHOD: ${req.method} | 42 | METHOD: ${req.method} |
42 | URL: ${req.url} | 43 | URL: ${req.url} |
43 | 44 | ||
44 | QUERY-STRING: | 45 | QUERY-STRING: |
45 | ${JSON.stringify(req.query, null, 2)} | 46 | ${JSON.stringify(req.query, null, 2)} |
46 | 47 | ||
47 | REQ-CONTENT-TYPE: ${req.get('content-type')} | 48 | REQ-CONTENT-TYPE: ${req.get('content-type')} |
48 | REQ-BODY-TYPEOF: ${typeof req.body} | 49 | REQ-BODY-TYPEOF: ${typeof req.body} |
49 | REQ-BODY: | 50 | REQ-BODY: |
50 | ${(req.body && typeof req.body === 'object' && JSON.stringify(req.body, null, 2)) || req.body} | 51 | ${(req.body && typeof req.body === 'object' && JSON.stringify(req.body, null, 2)) || req.body} |
51 | `; | 52 | `; |
52 | 53 | ||
53 | const dumpDir = path.join( | 54 | const dumpDir = path.join( |
54 | callbackDumpDir, | 55 | callbackDumpDir, |
55 | moment().format('YYYY-MM-DD'), | 56 | moment().format('YYYY-MM-DD'), |
56 | ); | 57 | ); |
57 | 58 | ||
58 | try { | 59 | try { |
59 | await fsPromise.stat(dumpDir); | 60 | await fsPromise.stat(dumpDir); |
60 | } catch { | 61 | } catch { |
61 | await fsPromise.mkdir(dumpDir, { recursive: true }); | 62 | await fsPromise.mkdir(dumpDir, { recursive: true }); |
62 | } | 63 | } |
63 | 64 | ||
64 | await fsPromise.writeFile( | 65 | await fsPromise.writeFile( |
65 | path.join( | 66 | path.join( |
66 | dumpDir, | 67 | dumpDir, |
67 | [ | 68 | [ |
68 | moment().format('YYMMDD_HHmmss_SSS'), | 69 | moment().format('YYMMDD_HHmmss_SSS'), |
69 | xid, | 70 | xid, |
70 | ].join('_'), | 71 | ].join('_'), |
71 | ), | 72 | ), |
72 | data, | 73 | data, |
73 | ); | 74 | ); |
74 | 75 | ||
75 | await fsPromise.writeFile( | 76 | await fsPromise.writeFile( |
76 | path.join(callbackDumpDir, 'last.trx'), | 77 | path.join(callbackDumpDir, 'last-callback'), |
77 | data, | 78 | data, |
78 | ); | 79 | ); |
79 | 80 | ||
80 | next(); | 81 | next(); |
81 | }; | 82 | }; |
82 | 83 |
lib/callback/handler-prepaid.js
1 | const MODULE_NAME = 'CALLBACK.HANDLER-PREPAID'; | ||
2 | |||
3 | const logger = require('komodo-sdk/logger'); | ||
4 | const report = require('../report/prepaid'); | ||
5 | const getFromReq = require('../get-from-params-body-qs'); | ||
6 | const translateRc = require('../translate-rc'); | ||
7 | |||
1 | module.exports = (req, res) => { | 8 | module.exports = (req, res) => { |
2 | const { xid } = res.locals; | 9 | const { xid } = res.locals; |
3 | res.json({ | 10 | res.json({ |
4 | status: 'OK', | 11 | status: 'OK', |
5 | error: null, | 12 | error: null, |
6 | ts: new Date(), | 13 | ts: new Date(), |
7 | xid, | 14 | xid, |
8 | }); | 15 | }); |
16 | |||
17 | const rcFromRequest = getFromReq(req, 'rc'); | ||
18 | const translatedRc = rcFromRequest && (translateRc[rcFromRequest] || rcFromRequest); | ||
19 | logger.verbose(`${MODULE_NAME} 2B7BE44D: RC translated`, { | ||
20 | xid, | ||
21 | rcFromRequest, | ||
22 | translatedRc, | ||
23 | }); | ||
24 | |||
25 | report(xid, { | ||
26 | trx_id: getFromReq(req, 'request_id'), | ||
27 | rc: translatedRc || '68', | ||
28 | sn: getFromReq(req, 'sn'), | ||
29 | amount: getFromReq(req, 'amount'), | ||
30 | balance: getFromReq(req, 'ending_balance'), | ||
31 | message: { | ||
32 | xid, | ||
33 | CALLBACK: { | ||
34 | ip: req.ip, | ||
35 | method: req.method, | ||
36 | body: req.body, | ||
37 | qs: req.query, | ||
38 | }, | ||
39 | }, | ||
40 | }); | ||
9 | }; | 41 | }; |
10 | 42 |
lib/get-from-params-body-qs.js
File was created | 1 | module.exports = (req, keyword) => (req.params && req.params[keyword]) | |
2 | || (req.body && req.body[keyword]) | ||
3 | || (req.query && req.query[keyword]); | ||
4 |
lib/hit/dump-req-res.js
File was created | 1 | const MODULE_NAME = 'DUMP-REQ-RES'; | |
2 | |||
3 | const fs = require('fs'); | ||
4 | const path = require('path'); | ||
5 | const fsPromise = require('fs').promises; | ||
6 | const moment = require('moment'); | ||
7 | |||
8 | const config = require('komodo-sdk/config'); | ||
9 | const logger = require('komodo-sdk/logger'); | ||
10 | |||
11 | const baseDumpDir = 'dump'; | ||
12 | const requestDumpDir = path.join(baseDumpDir, 'request'); | ||
13 | |||
14 | if (!fs.existsSync(baseDumpDir)) { | ||
15 | logger.verbose(`${MODULE_NAME} 51105314: Creating base dump dir`); | ||
16 | fs.mkdirSync(baseDumpDir); | ||
17 | } | ||
18 | |||
19 | if (!fs.existsSync(requestDumpDir)) { | ||
20 | logger.verbose(`${MODULE_NAME} 8A52891B: Creating request dump dir`); | ||
21 | fs.mkdirSync(requestDumpDir); | ||
22 | } | ||
23 | |||
24 | module.exports = async ( | ||
25 | xid, task, httpMethod, endpointUrl, params, responseBody, responseStatus, | ||
26 | ) => { | ||
27 | if ( | ||
28 | !config | ||
29 | || !config.partner | ||
30 | || !config.partner.dump_request | ||
31 | ) { | ||
32 | return; | ||
33 | } | ||
34 | |||
35 | const data = `-------- | ||
36 | XID: ${xid} | ||
37 | PID: ${process.pid} | ||
38 | DATE: ${moment().format('YYYY-MM-DD HH:mm:ss.SSS')} | ||
39 | |||
40 | TASK: | ||
41 | ${JSON.stringify(task, null, 2)} | ||
42 | |||
43 | |||
44 | HTTP-METHOD: ${httpMethod} | ||
45 | URL: ${endpointUrl} | ||
46 | PARAMS: | ||
47 | ${ | ||
48 | params && ( | ||
49 | (typeof params === 'string' && params) | ||
50 | || JSON.stringify(params, null, 2) | ||
51 | ) | ||
52 | } | ||
53 | |||
54 | RESPONSE-STATUS: ${responseStatus} | ||
55 | RESPONSE-BODY: | ||
56 | ${ | ||
57 | responseBody && ( | ||
58 | (typeof responseBody === 'string' && responseBody) | ||
59 | || JSON.stringify(responseBody, null, 2) | ||
60 | ) | ||
61 | } | ||
62 | `; | ||
63 | |||
64 | const dumpDir = path.join( | ||
65 | requestDumpDir, | ||
66 | moment().format('YYYY-MM-DD'), | ||
67 | ); | ||
68 | |||
69 | try { | ||
70 | await fsPromise.stat(dumpDir); | ||
71 | } catch { | ||
72 | await fsPromise.mkdir(dumpDir, { recursive: true }); | ||
73 | } | ||
74 | |||
75 | await fsPromise.writeFile( | ||
76 | path.join( | ||
77 | dumpDir, | ||
78 | `trx_${task.trx_id}`, | ||
79 | ), | ||
80 | data, | ||
81 | ); | ||
82 | |||
83 | await fsPromise.writeFile( | ||
84 | path.join(dumpDir, 'last-trx'), | ||
85 | data, | ||
86 | ); | ||
87 | }; | ||
88 |
lib/hit/prepaid.js
1 | const MODULE_NAME = 'HIT.PREPAID'; | 1 | const MODULE_NAME = 'HIT.PREPAID'; |
2 | 2 | ||
3 | const axios = require('axios').default; | 3 | const axios = require('axios').default; |
4 | const urljoin = require('url-join'); | 4 | const urljoin = require('url-join'); |
5 | const uniqid = require('uniqid'); | 5 | const uniqid = require('uniqid'); |
6 | 6 | ||
7 | const config = require('komodo-sdk/config'); | 7 | const config = require('komodo-sdk/config'); |
8 | const logger = require('komodo-sdk/logger'); | 8 | const logger = require('komodo-sdk/logger'); |
9 | 9 | ||
10 | const report = require('../report/prepaid'); | 10 | const report = require('../report/prepaid'); |
11 | const translateRc = require('../translate-rc'); | 11 | const translateRc = require('../translate-rc'); |
12 | const composeCallbackUrl = require('./compose-callback-url'); | 12 | const composeCallbackUrl = require('./compose-callback-url'); |
13 | const dumpReqRes = require('./dump-req-res'); | ||
13 | 14 | ||
14 | module.exports = async (task, isAdvice) => { | 15 | module.exports = async (task, isAdvice) => { |
15 | const xid = uniqid(); | 16 | const xid = uniqid(); |
16 | 17 | ||
18 | logger.verbose(`${MODULE_NAME} 2272F01F: Processing task`, { | ||
19 | xid, | ||
20 | isAdvice, | ||
21 | task, | ||
22 | }); | ||
23 | |||
17 | const params = { | 24 | const params = { |
18 | request_id: task.trx_id, | 25 | request_id: task.trx_id, |
19 | terminal_name: config.partner.terminal_name, | 26 | terminal_name: config.partner.terminal_name, |
20 | password: config.partner.password, | 27 | password: config.partner.password, |
21 | destination: task.destination, | 28 | destination: task.destination, |
22 | product_name: task.remote_product, | 29 | product_name: task.remote_product, |
23 | reverse_url: composeCallbackUrl(xid, false), | 30 | reverse_url: composeCallbackUrl(xid, false), |
24 | }; | 31 | }; |
25 | 32 | ||
26 | const endpointUrl = isAdvice | 33 | const endpointUrl = isAdvice |
27 | ? urljoin(config.partner.url, '/trx-status') | 34 | ? urljoin(config.partner.url, '/trx-status') |
28 | : urljoin(config.partner.url, '/topup'); | 35 | : urljoin(config.partner.url, '/topup'); |
29 | 36 | ||
37 | let lastResponse = null; | ||
38 | |||
30 | try { | 39 | try { |
31 | logger.verbose(`${MODULE_NAME} 4AAD4F99: Going to HIT prepaid endpoint`, { | 40 | logger.verbose(`${MODULE_NAME} 4AAD4F99: Going to HIT prepaid endpoint`, { |
32 | xid, | 41 | xid, |
33 | isAdvice, | ||
34 | endpointUrl, | 42 | endpointUrl, |
35 | params, | 43 | params, |
36 | }); | 44 | }); |
37 | 45 | ||
38 | const response = await axios.get(endpointUrl, { | 46 | const response = await axios.get(endpointUrl, { |
39 | headers: { | 47 | headers: { |
40 | 'User-Agent': 'KOMODO-GW-HTTPGETX', | 48 | 'User-Agent': 'KOMODO-GW-HTTPGETX', |
41 | }, | 49 | }, |
42 | timeout: config.partner.hit_timeout_ms || 60 * 1000, | 50 | timeout: config.partner.hit_timeout_ms || 60 * 1000, |
43 | params, | 51 | params, |
44 | }); | 52 | }); |
45 | 53 | ||
46 | if (!response) { | 54 | if (!response) { |
47 | const e = new Error(`${MODULE_NAME} 8CF4E04D: Empty response`); | 55 | const e = new Error(`${MODULE_NAME} 8CF4E04D: Empty response`); |
48 | e.rc = '68'; | 56 | e.rc = '68'; |
49 | e.response = response; | 57 | e.response = response; |
50 | throw e; | 58 | throw e; |
51 | } | 59 | } |
52 | 60 | ||
53 | if (!response.data) { | 61 | if (!response.data) { |
54 | const e = new Error(`${MODULE_NAME} E72B5A53: Empty response data`); | 62 | const e = new Error(`${MODULE_NAME} E72B5A53: Empty response data`); |
55 | e.rc = '68'; | 63 | e.rc = '68'; |
56 | e.response = response; | 64 | e.response = response; |
57 | throw e; | 65 | throw e; |
58 | } | 66 | } |
59 | 67 | ||
60 | if (typeof response.data !== 'object') { | 68 | if (typeof response.data !== 'object') { |
61 | const e = new Error(`${MODULE_NAME} 507680AB: Response data is not a JSON`); | 69 | const e = new Error(`${MODULE_NAME} 507680AB: Response data is not a JSON`); |
62 | e.rc = '68'; | 70 | e.rc = '68'; |
63 | e.response = response; | 71 | e.response = response; |
64 | throw e; | 72 | throw e; |
65 | } | 73 | } |
66 | 74 | ||
75 | lastResponse = response; | ||
76 | |||
67 | logger.verbose(`${MODULE_NAME} E51AFBBA: Got a direct response`, { | 77 | logger.verbose(`${MODULE_NAME} E51AFBBA: Got a direct response`, { |
68 | xid, | 78 | xid, |
69 | responseBody: response.data, | 79 | responseBody: response.data, |
70 | }); | 80 | }); |
71 | 81 | ||
72 | report(xid, { | 82 | report(xid, { |
73 | trx_id: task.trx_id, | 83 | trx_id: task.trx_id, |
74 | rc: response.data.rc ? translateRc[response.data.rc] || response.data.rc | 84 | rc: response.data.rc ? translateRc[response.data.rc] || response.data.rc |
75 | : '68', | 85 | : '68', |
76 | sn: response.data.sn || null, | 86 | sn: response.data.sn || null, |
77 | amount: 0, | 87 | amount: 0, |
78 | balance: 0, | 88 | balance: 0, |
79 | message: { | 89 | message: { |
80 | xid, | 90 | xid, |
81 | 'DIRECT-RESPONSE': response.data, | 91 | 'DIRECT-RESPONSE': response.data, |
82 | 'IS-ADVICE': !!isAdvice, | 92 | 'IS-ADVICE': !!isAdvice, |
83 | }, | 93 | }, |
84 | }); | 94 | }); |
85 | } catch (e) { | 95 | } catch (e) { |
86 | const rc = e.rc || '68'; | 96 | const rc = e.rc || '68'; |
87 | 97 | ||
98 | lastResponse = e.response; | ||
99 | |||
88 | report(xid, { | 100 | report(xid, { |
89 | trx_id: task.trx_id, | 101 | trx_id: task.trx_id, |
90 | rc, | 102 | rc, |
91 | message: { | 103 | message: { |
92 | xid, | 104 | xid, |
93 | 'KOMODO-GW-ERROR': { | 105 | 'KOMODO-GW-ERROR': { |
94 | eCode: e.code, | 106 | eCode: e.code, |
95 | eMessage: e.message, | 107 | eMessage: e.message, |
96 | responseHttpStatus: e.response && e.response.status, | 108 | responseHttpStatus: e.response && e.response.status, |
97 | responseBody: e.response && e.response.data, | 109 | responseBody: e.response && e.response.data, |
98 | }, | 110 | }, |
99 | 'IS-ADVICE': !!isAdvice, | 111 | 'IS-ADVICE': !!isAdvice, |
100 | }, | 112 | }, |
101 | }); | 113 | }); |
114 | } finally { | ||
115 | dumpReqRes( | ||
116 | xid, | ||
117 | task, | ||
118 | 'GET', | ||
119 | endpointUrl, | ||
120 | params, | ||
121 | lastResponse && lastResponse.data, | ||
122 | lastResponse && lastResponse.status, | ||
123 | lastResponse, | ||
124 | ); | ||
102 | } | 125 | } |
103 | }; | 126 | }; |
lib/partner-prepaid.js
1 | // const MODULE_NAME = 'PARTNER-PREPAID'; | 1 | const hit = require('./hit/prepaid'); |
2 | 2 | ||
3 | exports.buy = () => { | 3 | exports.buy = (task) => { |
4 | // | 4 | hit(task, false); |
5 | }; | 5 | }; |
6 | 6 | ||
7 | exports.advice = () => { | 7 | exports.advice = (task) => { |
8 | // | 8 | hit(task, true); |
9 | }; | 9 | }; |
10 | 10 |
lib/translate-rc.js
1 | module.exports = { | 1 | module.exports = { |
2 | '00': '00', | 2 | '00': '00', |
3 | '02': '90', | ||
4 | '03': '90', | ||
5 | '04': '90', | ||
3 | 13: '90', | 6 | 13: '90', |
4 | 30: '40', | 7 | 30: '40', |
5 | 68: '68', | 8 | 68: '68', |
6 | 91: '90', | 9 | 91: '90', |
10 | 92: '90', | ||
7 | 93: '94', | 11 | 93: '94', |
8 | 96: '68', | 12 | 96: '68', |
13 | 97: '90', | ||
9 | }; | 14 | }; |
10 | 15 |