Commit fdd1124680883e13b11aa8d2d3e767ea3cb6205e
1 parent
ddf7f00aff
Exists in
master
custom extract price
Showing 3 changed files with 19 additions and 17 deletions Inline Diff
examples/kopnus/config.json
1 | { | 1 | { |
2 | "auto_resend": { | 2 | "auto_resend": { |
3 | "delay_ms": 60000, | 3 | "delay_ms": 60000, |
4 | "max_retry": 1 | 4 | "max_retry": 1 |
5 | }, | 5 | }, |
6 | "sn_pattern": { | 6 | "sn_pattern": { |
7 | "pattern": "SN=(.*?)\\.", | 7 | "pattern": "SN=(.*?)\\.", |
8 | "match_idx": 1 | 8 | "match_idx": 1 |
9 | }, | 9 | }, |
10 | "amount_pattern": { | ||
11 | "pattern": "HRG=(\\d+)", | ||
12 | "match_idx": 1 | ||
13 | }, | ||
10 | "remote_products": { | 14 | "remote_products": { |
11 | "S10": "3043051", | 15 | "S10": "3043051", |
12 | "S20": "3023092" | 16 | "S20": "3023092" |
13 | }, | 17 | }, |
14 | "advice_max_age_ms": 240000, | 18 | "advice_max_age_ms": 240000, |
15 | "advice_is_topuprequest": true, | 19 | "advice_is_topuprequest": true, |
16 | "responsecode_tag": "RESPONCODE" | 20 | "responsecode_tag": "RESPONCODE" |
17 | } | 21 | } |
18 | 22 |
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 | const moment = require('moment'); | 9 | const moment = require('moment'); |
10 | const stringify = require("json-stringify-pretty-compact"); | 10 | const stringify = require("json-stringify-pretty-compact"); |
11 | 11 | ||
12 | const config = require('komodo-sdk/config'); | 12 | const config = require('komodo-sdk/config'); |
13 | const logger = require('komodo-sdk/logger'); | 13 | const logger = require('komodo-sdk/logger'); |
14 | const matrix = require('komodo-sdk/matrix'); | 14 | const matrix = require('komodo-sdk/matrix'); |
15 | const pull = require('komodo-sdk/gateway/pull'); | 15 | const pull = require('komodo-sdk/gateway/pull'); |
16 | const resendDelay = require('komodo-sdk/gateway/resend-delay'); | 16 | const resendDelay = require('komodo-sdk/gateway/resend-delay'); |
17 | 17 | ||
18 | const st24 = require('./st24'); | 18 | const st24 = require('./st24'); |
19 | 19 | ||
20 | const partnerRc = fs.existsSync(__dirname + '/../rc-local.json') ? require('../rc-local.json') : require('./partner-rc.json'); | 20 | const partnerRc = fs.existsSync(__dirname + '/../rc-local.json') ? require('../rc-local.json') : require('./partner-rc.json'); |
21 | logger.verbose('Partner RC dictionary loaded', {partner_rc: partnerRc}); | 21 | logger.verbose('Partner RC dictionary loaded', {partner_rc: partnerRc}); |
22 | 22 | ||
23 | if (config.partner.use_sslv3) { | 23 | if (config.partner.use_sslv3) { |
24 | https.globalAgent.options.secureProtocol = 'SSLv3_method'; | 24 | https.globalAgent.options.secureProtocol = 'SSLv3_method'; |
25 | } | 25 | } |
26 | 26 | ||
27 | function createXmlRpcClient(endpoint) { | 27 | function createXmlRpcClient(endpoint) { |
28 | const partnerUrl = url.parse(endpoint); | 28 | const partnerUrl = url.parse(endpoint); |
29 | const clientOptions = { | 29 | const clientOptions = { |
30 | host: partnerUrl.hostname, | 30 | host: partnerUrl.hostname, |
31 | port: partnerUrl.port, | 31 | port: partnerUrl.port, |
32 | path: partnerUrl.pathname | 32 | path: partnerUrl.pathname |
33 | }; | 33 | }; |
34 | 34 | ||
35 | logger.verbose('Creating XML-RPC client using ' + partnerUrl.protocol, clientOptions); | 35 | logger.verbose('Creating XML-RPC client using ' + partnerUrl.protocol, clientOptions); |
36 | 36 | ||
37 | return (partnerUrl.protocol === 'https:') ? xmlrpc.createSecureClient(clientOptions) : xmlrpc.createClient(clientOptions); | 37 | return (partnerUrl.protocol === 'https:') ? xmlrpc.createSecureClient(clientOptions) : xmlrpc.createClient(clientOptions); |
38 | } | 38 | } |
39 | 39 | ||
40 | function buy(task) { | 40 | function buy(task) { |
41 | _topUpRequest(task); | 41 | _topUpRequest(task); |
42 | } | 42 | } |
43 | 43 | ||
44 | function _topUpRequest(task, isAdvice) { | 44 | function _topUpRequest(task, isAdvice) { |
45 | const params = { | 45 | const params = { |
46 | MSISDN: config.partner.msisdn || config.partner.userid, | 46 | MSISDN: config.partner.msisdn || config.partner.userid, |
47 | REQUESTID: task.trx_id, | 47 | REQUESTID: task.trx_id, |
48 | PIN: config.partner.pin || config.partner.password, | 48 | PIN: config.partner.pin || config.partner.password, |
49 | NOHP: task.destination, | 49 | NOHP: task.destination, |
50 | NOM: task.remote_product | 50 | NOM: task.remote_product |
51 | }; | 51 | }; |
52 | 52 | ||
53 | const xmlrpcMethod = 'topUpRequest'; | 53 | const xmlrpcMethod = 'topUpRequest'; |
54 | logger.info('Preparing XMLRPC request', {method: xmlrpcMethod, params: params, partnerUrl: config.partner.url}); | 54 | logger.info('Preparing XMLRPC request', {method: xmlrpcMethod, params: params, partnerUrl: config.partner.url}); |
55 | 55 | ||
56 | const client = createXmlRpcClient(config.partner.url); | 56 | const client = createXmlRpcClient(config.partner.url); |
57 | client.methodCall(xmlrpcMethod, [ params ], function (err, value) { | 57 | client.methodCall(xmlrpcMethod, [ params ], function (err, value) { |
58 | 58 | ||
59 | if (err) { | 59 | if (err) { |
60 | 60 | ||
61 | let msg = 'XMLRPC Client Error: ' + err; | 61 | let msg = 'XMLRPC Client Error: ' + err; |
62 | let rc = '68'; | 62 | let rc = '68'; |
63 | 63 | ||
64 | if ( | 64 | if ( |
65 | !isAdvice && | 65 | !isAdvice && |
66 | ( | 66 | ( |
67 | err.code === 'ECONNREFUSED' | 67 | err.code === 'ECONNREFUSED' |
68 | || err.code === 'EHOSTUNREACH' | 68 | || err.code === 'EHOSTUNREACH' |
69 | || (err.code === 'ETIMEDOUT' && err.syscall === "connect") | 69 | || (err.code === 'ETIMEDOUT' && err.syscall === "connect") |
70 | || (err.code === 'EPROTO' && err.syscall === "write") | 70 | || (err.code === 'EPROTO' && err.syscall === "write") |
71 | ) | 71 | ) |
72 | ) { | 72 | ) { |
73 | rc = '91'; | 73 | rc = '91'; |
74 | } | 74 | } |
75 | 75 | ||
76 | logger.warn(msg, {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, err: err}); | 76 | logger.warn(msg, {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, err: err}); |
77 | report({ | 77 | report({ |
78 | trx_id: task.trx_id, | 78 | trx_id: task.trx_id, |
79 | rc: rc, | 79 | rc: rc, |
80 | message: 'INTERNAL: ' + msg, | 80 | message: 'INTERNAL: ' + msg, |
81 | misc: { | 81 | misc: { |
82 | task: task | 82 | task: task |
83 | } | 83 | } |
84 | }); | 84 | }); |
85 | 85 | ||
86 | return; | 86 | return; |
87 | } | 87 | } |
88 | 88 | ||
89 | logger.info('Got XMLRPC response from partner for', {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, response: value}); | 89 | logger.info('Got XMLRPC response from partner for', {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, response: value}); |
90 | matrix.last_topupRequest_ack = value; | 90 | matrix.last_topupRequest_ack = value; |
91 | 91 | ||
92 | const RESPONSECODE_TAG = config.responsecode_tag ? config.responsecode_tag : 'RESPONSECODE'; | 92 | const RESPONSECODE_TAG = config.responsecode_tag ? config.responsecode_tag : 'RESPONSECODE'; |
93 | 93 | ||
94 | report({ | 94 | report({ |
95 | trx_id: task.trx_id, | 95 | trx_id: task.trx_id, |
96 | rc: partnerRc[value[RESPONSECODE_TAG]] || '40', | 96 | rc: partnerRc[value[RESPONSECODE_TAG]] || '40', |
97 | message: stringify(value), | 97 | message: stringify(value), |
98 | sn: (value.SN || '').replace(/;$/, '') || st24.extractSnFromMessage(value.MESSAGE, config.sn_pattern), | 98 | sn: (value.SN || '').replace(/;$/, '') || st24.extractSnFromMessage(value.MESSAGE, config.sn_pattern), |
99 | amount: value.PRICE || st24.extractPriceFromMsg(value.MESSAGE), | 99 | amount: value.PRICE || st24.extractPriceFromMsg(value.MESSAGE, config.amount_pattern), |
100 | raw: value, | 100 | raw: value, |
101 | misc: { | 101 | misc: { |
102 | task: task | 102 | task: task |
103 | } | 103 | } |
104 | }); | 104 | }); |
105 | }); | 105 | }); |
106 | } | 106 | } |
107 | 107 | ||
108 | function _topUpInquiry(task) { | 108 | function _topUpInquiry(task) { |
109 | const params = { | 109 | const params = { |
110 | REQUESTID: task.trx_id, | 110 | REQUESTID: task.trx_id, |
111 | MSISDN: config.partner.msisdn || config.partner.userid, | 111 | MSISDN: config.partner.msisdn || config.partner.userid, |
112 | PIN: config.partner.pin || config.partner.password, | 112 | PIN: config.partner.pin || config.partner.password, |
113 | NOHP: task.destination | 113 | NOHP: task.destination |
114 | }; | 114 | }; |
115 | 115 | ||
116 | const xmlrpcMethod = 'topUpInquiry'; | 116 | const xmlrpcMethod = 'topUpInquiry'; |
117 | logger.info('Preparing XMLRPC request', {method: xmlrpcMethod, params: params, partnerUrl: config.partner.url}); | 117 | logger.info('Preparing XMLRPC request', {method: xmlrpcMethod, params: params, partnerUrl: config.partner.url}); |
118 | 118 | ||
119 | const client = createXmlRpcClient(config.partner.url); | 119 | const client = createXmlRpcClient(config.partner.url); |
120 | client.methodCall(xmlrpcMethod, [ params ], function (err, value) { | 120 | client.methodCall(xmlrpcMethod, [ params ], function (err, value) { |
121 | 121 | ||
122 | if (err) { | 122 | if (err) { |
123 | 123 | ||
124 | const msg = 'XMLRPC Client Error: ' + err; | 124 | const msg = 'XMLRPC Client Error: ' + err; |
125 | 125 | ||
126 | logger.warn(msg, {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, err: err}); | 126 | logger.warn(msg, {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, err: err}); |
127 | report({ | 127 | report({ |
128 | trx_id: task.trx_id, | 128 | trx_id: task.trx_id, |
129 | rc: '68', | 129 | rc: '68', |
130 | message: 'INTERNAL: ' + msg, | 130 | message: 'INTERNAL: ' + msg, |
131 | misc: { | 131 | misc: { |
132 | task: task | 132 | task: task |
133 | } | 133 | } |
134 | }); | 134 | }); |
135 | 135 | ||
136 | return; | 136 | return; |
137 | } | 137 | } |
138 | 138 | ||
139 | logger.info('Got XMLRPC response from partner for', {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, response: value}); | 139 | logger.info('Got XMLRPC response from partner for', {method: xmlrpcMethod, trx_id: task.trx_id, destination: task.destination, response: value}); |
140 | //matrix.last_topupRequest_ack = value; | 140 | //matrix.last_topupRequest_ack = value; |
141 | 141 | ||
142 | report({ | 142 | report({ |
143 | trx_id: task.trx_id, | 143 | trx_id: task.trx_id, |
144 | rc: partnerRc[value.RESPONSECODE] || '40', | 144 | rc: partnerRc[value.RESPONSECODE] || '40', |
145 | message: stringify(value), | 145 | message: stringify(value), |
146 | sn: (value.SN || '').replace(/;$/, '') || st24.extractSnFromMessage(value.MESSAGE, config.sn_pattern), | 146 | sn: (value.SN || '').replace(/;$/, '') || st24.extractSnFromMessage(value.MESSAGE, config.sn_pattern), |
147 | amount: value.PRICE || st24.extractPriceFromMsg(value.MESSAGE), | 147 | amount: value.PRICE || st24.extractPriceFromMsg(value.MESSAGE, config.amount_pattern), |
148 | raw: value, | 148 | raw: value, |
149 | misc: { | 149 | misc: { |
150 | task: task | 150 | task: task |
151 | } | 151 | } |
152 | }); | 152 | }); |
153 | }); | 153 | }); |
154 | } | 154 | } |
155 | 155 | ||
156 | function advice(task) { | 156 | function advice(task) { |
157 | if (config && config.advice_is_not_allowed) { | 157 | if (config && config.advice_is_not_allowed) { |
158 | return; | 158 | return; |
159 | } | 159 | } |
160 | 160 | ||
161 | if (config && config.advice_max_age_ms) { | 161 | if (config && config.advice_max_age_ms) { |
162 | if (moment() - moment(task.created) > config.advice_max_age_ms) { | 162 | if (moment() - moment(task.created) > config.advice_max_age_ms) { |
163 | logger.verbose('Ignoring advice request because of expired task', {trx_id: task.trx_id, destination: task.destination, product: task.product, created: task.created, max_age: config.advice_max_age_ms}); | 163 | logger.verbose('Ignoring advice request because of expired task', {trx_id: task.trx_id, destination: task.destination, product: task.product, created: task.created, max_age: config.advice_max_age_ms}); |
164 | return; | 164 | return; |
165 | } | 165 | } |
166 | } | 166 | } |
167 | 167 | ||
168 | if (config && config.advice_is_topuprequest) { | 168 | if (config && config.advice_is_topuprequest) { |
169 | _topUpRequest(task, true); | 169 | _topUpRequest(task, true); |
170 | } | 170 | } |
171 | else { | 171 | else { |
172 | _topUpInquiry(task); | 172 | _topUpInquiry(task); |
173 | } | 173 | } |
174 | } | 174 | } |
175 | 175 | ||
176 | function report(data) { | 176 | function report(data) { |
177 | if (!data) { | 177 | if (!data) { |
178 | return; | 178 | return; |
179 | } | 179 | } |
180 | 180 | ||
181 | if (config && config.force_all_to_pending) { | 181 | if (config && config.force_all_to_pending) { |
182 | data.rc = '68'; | 182 | data.rc = '68'; |
183 | } | 183 | } |
184 | 184 | ||
185 | matrix.last_report_to_core = data; | 185 | matrix.last_report_to_core = data; |
186 | pull.report(data); | 186 | pull.report(data); |
187 | 187 | ||
188 | if (!data.misc && !data.misc.task) { | 188 | if (!data.misc && !data.misc.task) { |
189 | return; | 189 | return; |
190 | } | 190 | } |
191 | 191 | ||
192 | const task = data.misc.task; | 192 | const task = data.misc.task; |
193 | 193 | ||
194 | if (!resendDelay.isEnabled()) { | 194 | if (!resendDelay.isEnabled()) { |
195 | logger.verbose('Skipping resend delay because resend delay has not configured yet', {trx_id: task.trx_id, destination: task.destination, product: task.product}); | 195 | logger.verbose('Skipping resend delay because resend delay has not configured yet', {trx_id: task.trx_id, destination: task.destination, product: task.product}); |
196 | return; | 196 | return; |
197 | } | 197 | } |
198 | 198 | ||
199 | if (data.rc && data.rc === '68') { | 199 | if (data.rc && data.rc === '68') { |
200 | logger.verbose('Registering resend delay', {trx_id: task.trx_id, destination: task.destination, product: task.product}) | 200 | logger.verbose('Registering resend delay', {trx_id: task.trx_id, destination: task.destination, product: task.product}) |
201 | resendDelay.register(task, advice); | 201 | resendDelay.register(task, advice); |
202 | } | 202 | } |
203 | else { | 203 | else { |
204 | logger.verbose('Canceling resend delay', {trx_id: task.trx_id, destination: task.destination, product: task.product}) | 204 | logger.verbose('Canceling resend delay', {trx_id: task.trx_id, destination: task.destination, product: task.product}) |
205 | resendDelay.cancel(task.trx_id); | 205 | resendDelay.cancel(task.trx_id); |
206 | } | 206 | } |
207 | } | 207 | } |
208 | 208 | ||
209 | exports.buy = buy; | 209 | exports.buy = buy; |
210 | exports.advice = advice; | 210 | exports.advice = advice; |
211 | exports.report = report; | 211 | exports.report = report; |
212 | 212 |
lib/st24.js
1 | "use strict"; | 1 | "use strict"; |
2 | 2 | ||
3 | function extractSnFromMessage(msg, custom_rule) { | 3 | function extractFromMessage(msg, default_pattern, default_match_idx, custom_rule) { |
4 | if (!msg || typeof msg !== 'string') { | 4 | if (!msg || typeof msg !== 'string') { |
5 | return; | 5 | return; |
6 | } | 6 | } |
7 | 7 | ||
8 | let pattern; | 8 | let pattern; |
9 | let match_idx; | 9 | let match_idx; |
10 | 10 | ||
11 | if (custom_rule && custom_rule.pattern) { | 11 | if (custom_rule && custom_rule.pattern) { |
12 | pattern = custom_rule.pattern; | 12 | pattern = custom_rule.pattern; |
13 | match_idx = custom_rule.match_idx; | 13 | match_idx = custom_rule.match_idx; |
14 | } | 14 | } |
15 | else { | 15 | else { |
16 | pattern = "^SN=(.*?);"; | 16 | pattern = default_pattern; |
17 | match_idx = 1; | 17 | match_idx = default_match_idx; |
18 | } | 18 | } |
19 | 19 | ||
20 | const re = new RegExp(pattern); | 20 | const re = new RegExp(pattern); |
21 | const matches = msg.match(re); | 21 | const matches = msg.match(re); |
22 | 22 | ||
23 | if (!matches) return; | 23 | if (!matches) return; |
24 | 24 | ||
25 | if (match_idx < matches.length) { | 25 | if (match_idx < matches.length) { |
26 | return matches[match_idx] || null; | 26 | return matches[match_idx] || null; |
27 | } else { | 27 | } else { |
28 | return; | 28 | return; |
29 | } | 29 | } |
30 | |||
30 | } | 31 | } |
31 | 32 | ||
32 | function extractPriceFromMsg(msg) { | 33 | function extractSnFromMessage(msg, custom_rule) { |
33 | if (!msg || typeof msg !== 'string') { | 34 | const default_pattern = "^SN=(.*?);"; |
34 | return; | 35 | const default_match_idx = 1; |
35 | } | ||
36 | 36 | ||
37 | let match = msg.match(/\d,HRG=(.*?),ID=/); | 37 | return extractFromMessage(msg, default_pattern, default_match_idx, custom_rule); |
38 | if (!match || match.length < 2) { | 38 | } |
39 | return; | ||
40 | } | ||
41 | 39 | ||
42 | if (!match[1]) { | 40 | function extractPriceFromMsg(msg, custom_rule) { |
43 | return; | 41 | const default_pattern = "\\d,HRG=(.*?),ID="; |
44 | } | 42 | const default_match_idx = 1; |
45 | 43 | ||
46 | return parseInt(match[1].replace(/\./g, '')); | 44 | return extractFromMessage(msg, default_pattern, default_match_idx, custom_rule); |
47 | } | 45 | } |
48 | 46 | ||
49 | 47 | ||
50 | exports.extractSnFromMessage = extractSnFromMessage; | 48 | exports.extractSnFromMessage = extractSnFromMessage; |
51 | exports.extractPriceFromMsg = extractPriceFromMsg; | 49 | exports.extractPriceFromMsg = extractPriceFromMsg; |