Commit 4e651067774f61c8628cd8905389269117891ea9
1 parent
c47be51fe8
Exists in
master
mulai adaptasi agar bisa tangani selain st24 asli
Showing 3 changed files with 57 additions and 15 deletions Inline Diff
lib/partner.js
1 | "use strict"; | 1 | "use strict"; |
2 | 2 | ||
3 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; | 3 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; |
4 | 4 | ||
5 | const fs = require('fs'); | 5 | const fs = require('fs'); |
6 | const url = require('url'); | 6 | const url = require('url'); |
7 | const https = require('https'); | 7 | const https = require('https'); |
8 | const xmlrpc = require('xmlrpc'); | 8 | const xmlrpc = require('xmlrpc'); |
9 | 9 | ||
10 | const config = require('komodo-sdk/config'); | 10 | const config = require('komodo-sdk/config'); |
11 | const logger = require('komodo-sdk/logger'); | 11 | const logger = require('komodo-sdk/logger'); |
12 | const matrix = require('komodo-sdk/matrix'); | 12 | const matrix = require('komodo-sdk/matrix'); |
13 | const pull = require('komodo-sdk/gateway/pull'); | 13 | const pull = require('komodo-sdk/gateway/pull'); |
14 | 14 | ||
15 | const st24 = require('./st24'); | 15 | const st24 = require('./st24'); |
16 | 16 | ||
17 | const partnerRc = fs.existsSync(__dirname + '/../rc-local.json') ? require('../rc-local.json') : require('./partner-rc.json'); | 17 | const partnerRc = fs.existsSync(__dirname + '/../rc-local.json') ? require('../rc-local.json') : require('./partner-rc.json'); |
18 | 18 | ||
19 | if (config.partner.use_sslv3) { | 19 | if (config.partner.use_sslv3) { |
20 | https.globalAgent.options.secureProtocol = 'SSLv3_method'; | 20 | https.globalAgent.options.secureProtocol = 'SSLv3_method'; |
21 | } | 21 | } |
22 | 22 | ||
23 | function createXmlRpcClient(endpoint) { | 23 | function createXmlRpcClient(endpoint) { |
24 | const partnerUrl = url.parse(endpoint); | 24 | const partnerUrl = url.parse(endpoint); |
25 | const clientOptions = { | 25 | const clientOptions = { |
26 | host: partnerUrl.hostname, | 26 | host: partnerUrl.hostname, |
27 | port: partnerUrl.port, | 27 | port: partnerUrl.port, |
28 | path: partnerUrl.pathname | 28 | path: partnerUrl.pathname |
29 | }; | 29 | }; |
30 | 30 | ||
31 | logger.verbose('Creating XML-RPC client using ' + partnerUrl.protocol, clientOptions); | 31 | logger.verbose('Creating XML-RPC client using ' + partnerUrl.protocol, clientOptions); |
32 | 32 | ||
33 | return (partnerUrl.protocol === 'https:') ? xmlrpc.createSecureClient(clientOptions) : xmlrpc.createClient(clientOptions); | 33 | return (partnerUrl.protocol === 'https:') ? xmlrpc.createSecureClient(clientOptions) : xmlrpc.createClient(clientOptions); |
34 | } | 34 | } |
35 | 35 | ||
36 | function buy(task) { | 36 | function buy(task) { |
37 | _topUpRequest(task); | ||
38 | } | ||
39 | |||
40 | function _topUpRequest(task, isAdvice) { | ||
37 | const params = { | 41 | const params = { |
38 | MSISDN: config.partner.msisdn || config.partner.userid, | 42 | MSISDN: config.partner.msisdn || config.partner.userid, |
39 | REQUESTID: task.trx_id, | 43 | REQUESTID: task.trx_id, |
40 | PIN: config.partner.pin || config.partner.password, | 44 | PIN: config.partner.pin || config.partner.password, |
41 | NOHP: task.destination, | 45 | NOHP: task.destination, |
42 | NOM: task.remote_product | 46 | NOM: task.remote_product |
43 | }; | 47 | }; |
44 | 48 | ||
45 | const xmlrpcMethod = 'topUpRequest'; | 49 | const xmlrpcMethod = 'topUpRequest'; |
46 | logger.info('Preparing XMLRPC request', {method: xmlrpcMethod, params: params, partnerUrl: config.partner.url}); | 50 | logger.info('Preparing XMLRPC request', {method: xmlrpcMethod, params: params, partnerUrl: config.partner.url}); |
47 | 51 | ||
48 | const client = createXmlRpcClient(config.partner.url); | 52 | const client = createXmlRpcClient(config.partner.url); |
49 | client.methodCall(xmlrpcMethod, [ params ], function (err, value) { | 53 | client.methodCall(xmlrpcMethod, [ params ], function (err, value) { |
50 | 54 | ||
51 | if (err) { | 55 | if (err) { |
52 | 56 | ||
53 | let msg = 'XMLRPC Client Error: ' + err; | 57 | let msg = 'XMLRPC Client Error: ' + err; |
54 | let rc = '68'; | 58 | let rc = '68'; |
55 | 59 | ||
56 | if ( | 60 | if ( |
57 | err.code === 'ECONNREFUSED' | 61 | !isAdvice && |
58 | || err.code === 'EHOSTUNREACH' | 62 | ( |
59 | || (err.code === 'ETIMEDOUT' && err.syscall === "connect") | 63 | err.code === 'ECONNREFUSED' |
60 | || (err.code === 'EPROTO' && err.syscall === "write") | 64 | || err.code === 'EHOSTUNREACH' |
65 | || (err.code === 'ETIMEDOUT' && err.syscall === "connect") | ||
66 | || (err.code === 'EPROTO' && err.syscall === "write") | ||
67 | ) | ||
61 | ) { | 68 | ) { |
62 | rc = '91'; | 69 | rc = '91'; |
63 | } | 70 | } |
64 | 71 | ||
65 | logger.warn(msg, {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, err: err}); | 72 | logger.warn(msg, {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, err: err}); |
66 | report({ | 73 | report({ |
67 | trx_id: task.trx_id, | 74 | trx_id: task.trx_id, |
68 | rc: rc, | 75 | rc: rc, |
69 | message: 'INTERNAL: ' + msg, | 76 | message: 'INTERNAL: ' + msg, |
70 | misc: { | 77 | misc: { |
71 | task: task | 78 | task: task |
72 | } | 79 | } |
73 | }); | 80 | }); |
74 | 81 | ||
75 | if (rc === '68') { | 82 | if (rc === '68') { |
76 | setTimeout( | 83 | setTimeout( |
77 | function() { advice(task); }, | 84 | function() { advice(task); }, |
78 | 5 * 60 * 1000 | 85 | 5 * 60 * 1000 |
79 | ); | 86 | ); |
80 | } | 87 | } |
81 | 88 | ||
82 | return; | 89 | return; |
83 | } | 90 | } |
84 | 91 | ||
85 | logger.info('Got XMLRPC response from partner for', {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, response: value}); | 92 | logger.info('Got XMLRPC response from partner for', {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, response: value}); |
86 | matrix.last_topupRequest_ack = value; | 93 | matrix.last_topupRequest_ack = value; |
87 | 94 | ||
88 | report({ | 95 | report({ |
89 | trx_id: task.trx_id, | 96 | trx_id: task.trx_id, |
90 | rc: partnerRc[value.RESPONSECODE] || '40', | 97 | rc: partnerRc[value.RESPONSECODE] || '40', |
91 | message: value.MESSAGE, | 98 | message: value.MESSAGE, |
92 | sn: (value.SN || '').replace(/;$/, '') || st24.extractSnFromMessage(value.MESSAGE), | 99 | sn: (value.SN || '').replace(/;$/, '') || st24.extractSnFromMessage(value.MESSAGE), |
93 | amount: value.PRICE || st24.extractPriceFromMsg(value.MESSAGE), | 100 | amount: value.PRICE || st24.extractPriceFromMsg(value.MESSAGE), |
94 | raw: value, | 101 | raw: value, |
95 | misc: { | 102 | misc: { |
96 | task: task | 103 | task: task |
97 | } | 104 | } |
98 | }); | 105 | }); |
99 | }); | 106 | }); |
100 | } | 107 | } |
101 | 108 | ||
102 | function advice(task) { | 109 | function _topUpInquiry(task) { |
103 | const params = { | 110 | const params = { |
104 | REQUESTID: task.trx_id, | 111 | REQUESTID: task.trx_id, |
105 | MSISDN: config.partner.msisdn || config.partner.userid, | 112 | MSISDN: config.partner.msisdn || config.partner.userid, |
106 | PIN: config.partner.pin || config.partner.password, | 113 | PIN: config.partner.pin || config.partner.password, |
107 | NOHP: task.destination | 114 | NOHP: task.destination |
108 | }; | 115 | }; |
109 | 116 | ||
110 | const xmlrpcMethod = 'topUpInquiry'; | 117 | const xmlrpcMethod = 'topUpInquiry'; |
111 | logger.info('Preparing XMLRPC request', {method: xmlrpcMethod, params: params, partnerUrl: config.partner.url}); | 118 | logger.info('Preparing XMLRPC request', {method: xmlrpcMethod, params: params, partnerUrl: config.partner.url}); |
112 | 119 | ||
113 | const client = createXmlRpcClient(config.partner.url); | 120 | const client = createXmlRpcClient(config.partner.url); |
114 | client.methodCall(xmlrpcMethod, [ params ], function (err, value) { | 121 | client.methodCall(xmlrpcMethod, [ params ], function (err, value) { |
115 | 122 | ||
116 | if (err) { | 123 | if (err) { |
117 | 124 | ||
118 | const msg = 'XMLRPC Client Error: ' + err; | 125 | const msg = 'XMLRPC Client Error: ' + err; |
119 | 126 | ||
120 | logger.warn(msg, {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, err: err}); | 127 | logger.warn(msg, {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, err: err}); |
121 | report({ | 128 | report({ |
122 | trx_id: task.trx_id, | 129 | trx_id: task.trx_id, |
123 | rc: '68', | 130 | rc: '68', |
124 | message: 'INTERNAL: ' + msg, | 131 | message: 'INTERNAL: ' + msg, |
125 | misc: { | 132 | misc: { |
126 | task: task | 133 | task: task |
127 | } | 134 | } |
128 | }); | 135 | }); |
129 | 136 | ||
130 | setTimeout( | ||
131 | function() { advice(task); }, | ||
132 | 60 * 1000 | ||
133 | ); | ||
134 | |||
135 | return; | 137 | return; |
136 | } | 138 | } |
137 | 139 | ||
138 | logger.info('Got XMLRPC response from partner for', {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, response: value}); | 140 | logger.info('Got XMLRPC response from partner for', {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, response: value}); |
139 | //matrix.last_topupRequest_ack = value; | 141 | //matrix.last_topupRequest_ack = value; |
140 | 142 | ||
141 | report({ | 143 | report({ |
142 | trx_id: task.trx_id, | 144 | trx_id: task.trx_id, |
143 | rc: partnerRc[value.RESPONSECODE] || '40', | 145 | rc: partnerRc[value.RESPONSECODE] || '40', |
144 | message: value.MESSAGE, | 146 | message: value.MESSAGE, |
145 | sn: (value.SN || '').replace(/;$/, '') || st24.extractSnFromMessage(value.MESSAGE), | 147 | sn: (value.SN || '').replace(/;$/, '') || st24.extractSnFromMessage(value.MESSAGE), |
146 | amount: value.PRICE || st24.extractPriceFromMsg(value.MESSAGE), | 148 | amount: value.PRICE || st24.extractPriceFromMsg(value.MESSAGE), |
147 | raw: value, | 149 | raw: value, |
148 | misc: { | 150 | misc: { |
149 | task: task | 151 | task: task |
150 | } | 152 | } |
151 | }); | 153 | }); |
152 | }); | 154 | }); |
153 | } | 155 | } |
154 | 156 | ||
157 | function advice(task) { | ||
158 | if (config && advice_is_not_allowed) { | ||
159 | return; | ||
160 | } | ||
161 | |||
162 | if (config && advice_is_topuprequest) { | ||
163 | _topUpRequest(task, true); | ||
164 | } | ||
165 | else { | ||
166 | _topUpInquiry(task); | ||
167 | } | ||
168 | } | ||
169 | |||
155 | function report(data) { | 170 | function report(data) { |
156 | if (!data) { | 171 | if (!data) { |
157 | return; | 172 | return; |
158 | } | 173 | } |
159 | 174 | ||
160 | matrix.last_report_to_core = data; | 175 | matrix.last_report_to_core = data; |
161 | pull.report(data); | 176 | pull.report(data); |
162 | } | 177 | } |
lib/st24.js
1 | "use strict"; | 1 | "use strict"; |
2 | 2 | ||
3 | function extractSnFromMessage(msg) { | 3 | function extractSnFromMessage(msg, custom_rule) { |
4 | if (!msg || typeof msg !== 'string') { | 4 | if (!msg || typeof msg !== 'string') { |
5 | return; | 5 | return; |
6 | } | 6 | } |
7 | 7 | ||
8 | let match = msg.match(/^SN=(.*?);/); | 8 | let pattern; |
9 | if (!match || match.length < 2) { | 9 | let pattern_match_idx; |
10 | return; | 10 | |
11 | if (custom_rule && custom_rule.pattern) { | ||
12 | pattern = custom_rule.pattern; | ||
13 | pattern_match_idx = custom_rule.match_idx; | ||
14 | } | ||
15 | else { | ||
16 | pattern = "^SN=(.*?);"; | ||
17 | pattern_match_idx = 1; | ||
11 | } | 18 | } |
12 | 19 | ||
13 | return match[1]; | 20 | const re = new RegExp(pattern); |
21 | const matches = msg.match(re); | ||
22 | |||
23 | if (!matches) return; | ||
24 | |||
25 | if (pattern_match_idx < matches.length) { | ||
26 | return matches[pattern_match_idx]; | ||
27 | } else { | ||
28 | return; | ||
29 | } | ||
14 | } | 30 | } |
15 | 31 | ||
16 | function extractPriceFromMsg(msg) { | 32 | function extractPriceFromMsg(msg) { |
17 | if (!msg || typeof msg !== 'string') { | 33 | if (!msg || typeof msg !== 'string') { |
18 | return; | 34 | return; |
19 | } | 35 | } |
20 | 36 | ||
21 | let match = msg.match(/\d,HRG=(.*?),ID=/); | 37 | let match = msg.match(/\d,HRG=(.*?),ID=/); |
22 | if (!match || match.length < 2) { | 38 | if (!match || match.length < 2) { |
23 | return; | 39 | return; |
24 | } | 40 | } |
25 | 41 | ||
26 | if (!match[1]) { | 42 | if (!match[1]) { |
27 | return; | 43 | return; |
28 | } | 44 | } |
29 | 45 | ||
30 | return parseInt(match[1].replace(/\./g, '')); | 46 | return parseInt(match[1].replace(/\./g, '')); |
31 | } | 47 | } |
32 | 48 | ||
33 | 49 | ||
34 | exports.extractSnFromMessage = extractSnFromMessage; | 50 | exports.extractSnFromMessage = extractSnFromMessage; |
35 | exports.extractPriceFromMsg = extractPriceFromMsg; | 51 | exports.extractPriceFromMsg = extractPriceFromMsg; |
36 | 52 |
rc-local.sample.kopnus.json
File was created | 1 | { | |
2 | "00": "00", | ||
3 | "14": "14", | ||
4 | "15": "88", | ||
5 | "68": "68", | ||
6 | "74": "77", | ||
7 | "82": "94", | ||
8 | "I8": "14", | ||
9 | "I9": "14", | ||
10 | "A0": "68" | ||
11 | } | ||
12 |