Commit fea1167446661308b44b795e0f28b60dd3be5aae
1 parent
8124d89a46
Exists in
master
headless xml payload
Showing 2 changed files with 3 additions and 4 deletions Inline Diff
anti-same-day-dupe.js
1 | var redis = require('redis'); | 1 | var redis = require('redis'); |
2 | var LRU = require('lru-cache'); | 2 | var LRU = require('lru-cache'); |
3 | var moment = require('moment'); | 3 | var moment = require('moment'); |
4 | 4 | ||
5 | var config; | 5 | var config; |
6 | var logger; | 6 | var logger; |
7 | var redisClient; | 7 | var redisClient; |
8 | 8 | ||
9 | var taskCache = LRU({max: 100, maxAge: 1000 * 3600 * 2}); | 9 | var taskCache = LRU({max: 100, maxAge: 1000 * 3600 * 2}); |
10 | 10 | ||
11 | function createRedisClient(host, port) { | 11 | function createRedisClient(host, port) { |
12 | try { | 12 | try { |
13 | redisClient = redis.createClient(port, host); | 13 | redisClient = redis.createClient(port, host); |
14 | logger.verbose(__filename + ': Redis client for task history created'); | 14 | logger.verbose(__filename + ': Redis client for task history created'); |
15 | } catch(err) { | 15 | } catch(err) { |
16 | logger.warn(__filename + ": Error creating redis client to " + host + ':' + port); | 16 | logger.warn(__filename + ": Error creating redis client to " + host + ':' + port); |
17 | process.exit(1); | 17 | process.exit(1); |
18 | } | 18 | } |
19 | } | 19 | } |
20 | 20 | ||
21 | function init(options) { | 21 | function init(options) { |
22 | if (!options) { | 22 | if (!options) { |
23 | console.log('Undefined options, terminating....'); | 23 | console.log('Undefined options, terminating....'); |
24 | process.exit(1); | 24 | process.exit(1); |
25 | } | 25 | } |
26 | 26 | ||
27 | if (options.config) { | 27 | if (options.config) { |
28 | config = options.config; | 28 | config = options.config; |
29 | } else { | 29 | } else { |
30 | console.log('Undefined options.config, terminating....') | 30 | console.log('Undefined options.config, terminating....') |
31 | process.exit(1); | 31 | process.exit(1); |
32 | } | 32 | } |
33 | 33 | ||
34 | if (options && options.logger) { | 34 | if (options && options.logger) { |
35 | logger = options.logger; | 35 | logger = options.logger; |
36 | } else { | 36 | } else { |
37 | console.log('Undefined options.logger, terminating....') | 37 | console.log('Undefined options.logger, terminating....') |
38 | process.exit(1); | 38 | process.exit(1); |
39 | } | 39 | } |
40 | 40 | ||
41 | createRedisClient(config.globals.redis_host, config.globals.redis_port); | 41 | createRedisClient(config.globals.redis_host, config.globals.redis_port); |
42 | } | 42 | } |
43 | 43 | ||
44 | function getKey(task, chipInfo) { | 44 | function getKey(task, chipInfo) { |
45 | var today = moment(task.timestamp, 'YYYYMMDDHHmmss').format('YYYYMMDD'); | 45 | var today = moment(task.timestamp, 'YYYYMMDDHHmmss').format('YYYYMMDD'); |
46 | return chipInfo + '.antiSameDayDupe.trx.date:' + today + '.rProd:' + task.remoteProduct.toUpperCase() + '.dest:' + task.destination ; | 46 | return chipInfo + '.antiSameDayDupe.trx.date:' + today + '.rProd:' + task.remoteProduct.toUpperCase() + '.dest:' + task.destination ; |
47 | } | 47 | } |
48 | 48 | ||
49 | function register(task, cb) { | 49 | function register(task, cb) { |
50 | var key = getKey(task, config.globals.gateway_name); | 50 | var key = getKey(task, config.globals.gateway_name); |
51 | 51 | ||
52 | taskCache.set(key, task); | 52 | taskCache.set(key, task); |
53 | saveToRedis(task,cb); | 53 | saveToRedis(task,cb); |
54 | } | 54 | } |
55 | 55 | ||
56 | function saveToRedis(task, cb) { | 56 | function saveToRedis(task, cb) { |
57 | var key = getKey(task, config.globals.gateway_name); | 57 | var key = getKey(task, config.globals.gateway_name); |
58 | logger.verbose('Saving task', {key: key, task: task}); | 58 | logger.verbose('Saving task', {key: key, task: task}); |
59 | 59 | ||
60 | redisClient.set(key, JSON.stringify(task), function() { | 60 | redisClient.set(key, JSON.stringify(task), function() { |
61 | redisClient.expire(key, 3600*24); | 61 | redisClient.expire(key, 3600*24); |
62 | if (cb) { | 62 | if (cb) { |
63 | cb(); | 63 | cb(); |
64 | } | 64 | } |
65 | }); | 65 | }); |
66 | } | 66 | } |
67 | 67 | ||
68 | function createDummyTask(remoteProduct, destination) { | 68 | function createDummyTask(remoteProduct, destination) { |
69 | return { | 69 | return { |
70 | remoteProduct: remoteProduct, | 70 | remoteProduct: remoteProduct, |
71 | destination: destination, | 71 | destination: destination, |
72 | timestamp: moment().format('YYYYMMDD'), | 72 | timestamp: moment().format('YYYYMMDD'), |
73 | } | 73 | } |
74 | } | 74 | } |
75 | 75 | ||
76 | function get(remoteProduct, destination, cb) { | 76 | function get(remoteProduct, destination, cb) { |
77 | var dummyTask = createDummyTask(remoteProduct, destination); | 77 | var dummyTask = createDummyTask(remoteProduct, destination); |
78 | 78 | ||
79 | var key = getKey(dummyTask, config.globals.gateway_name); | 79 | var key = getKey(dummyTask, config.globals.gateway_name); |
80 | var task = taskCache.get(key); | 80 | var task = taskCache.get(key); |
81 | 81 | ||
82 | if (task) { | 82 | if (task) { |
83 | cb(null, task); | 83 | cb(null, task); |
84 | } | 84 | } |
85 | else { | 85 | else { |
86 | getFromRedis(remoteProduct, destination, cb); | 86 | getFromRedis(remoteProduct, destination, cb); |
87 | } | 87 | } |
88 | } | 88 | } |
89 | 89 | ||
90 | function getFromRedis(remoteProduct, destination, cb) { | 90 | function getFromRedis(remoteProduct, destination, cb) { |
91 | var dummyTask = createDummyTask(remoteProduct, destination); | 91 | var dummyTask = createDummyTask(remoteProduct, destination); |
92 | 92 | ||
93 | var key = getKey(dummyTask, config.globals.gateway_name, moment().format('YYYYMMDD')); | 93 | var key = getKey(dummyTask, config.globals.gateway_name, moment().format('YYYYMMDD')); |
94 | redisClient.get(key, function(err, result) { | 94 | redisClient.get(key, function(err, result) { |
95 | if (err) { | 95 | if (err) { |
96 | logger.warn('antiDupe.get: error getting task from redis', {key: key, params: dummyTask}); | 96 | logger.warn('antiDupe.get: error getting task from redis', {key: key, params: dummyTask}); |
97 | 97 | ||
98 | cb(err, null); | 98 | cb(err, null); |
99 | return; | 99 | return; |
100 | } | 100 | } |
101 | 101 | ||
102 | var task = {}; | 102 | var task = {}; |
103 | 103 | ||
104 | try { | 104 | try { |
105 | task = JSON.parse(result); | 105 | task = JSON.parse(result); |
106 | } | 106 | } |
107 | catch(e) { | 107 | catch(e) { |
108 | logger.warn('antiDupe.get: Can not parse result', {key: key, params: dummyTask, data: result}); | 108 | logger.warn('antiDupe.get: Can not parse result', {key: key, params: dummyTask, data: result}); |
109 | err = "Can not parse task"; | 109 | err = "Can not parse task"; |
110 | cb(err, null); | 110 | cb(err, null); |
111 | return; | 111 | return; |
112 | } | 112 | } |
113 | 113 | ||
114 | cb(err, task); | 114 | cb(err, task); |
115 | }); | 115 | }); |
116 | } | 116 | } |
117 | 117 | ||
118 | function check(task, cbNoDupe, cbDupe, cbDupeWithSameReqId) { | 118 | function check(task, cbNoDupe, cbDupe, cbDupeWithSameReqId) { |
119 | if (Number(config.globals.no_same_day_dupe_check)) { | 119 | if (Number(config.globals.no_same_day_dupe_check)) { |
120 | logger.verbose('Skipping same day dupe check because of config.globals.no_same_day_dupe_check'); | 120 | logger.verbose('Skipping same day dupe check because of config.globals.no_same_day_dupe_check'); |
121 | cbNoDupe(task); | 121 | cbNoDupe(task); |
122 | return; | ||
122 | } | 123 | } |
123 | 124 | ||
124 | get(task.remoteProduct, task.destination, function(err, archivedTask) { | 125 | get(task.remoteProduct, task.destination, function(err, archivedTask) { |
125 | if (err) { | 126 | if (err) { |
126 | logger.warn('Error on checking same day duplicate', {task: task}); | 127 | logger.warn('Error on checking same day duplicate', {task: task}); |
127 | cbNoDupe(task); | 128 | cbNoDupe(task); |
128 | return; | 129 | return; |
129 | } | 130 | } |
130 | 131 | ||
131 | if (archivedTask && archivedTask.requestId) { | 132 | if (archivedTask && archivedTask.requestId) { |
132 | if (task.requestId == archivedTask.requestId) { | 133 | if (task.requestId == archivedTask.requestId) { |
133 | logger.verbose('Duplicate trx on same day with same requestId', {task: task}); | 134 | logger.verbose('Duplicate trx on same day with same requestId', {task: task}); |
134 | cbDupeWithSameReqId(task); | 135 | cbDupeWithSameReqId(task); |
135 | return; | 136 | return; |
136 | } | 137 | } |
137 | 138 | ||
138 | logger.verbose('Duplicate trx on same day', {task: task, archivedTask: archivedTask}); | 139 | logger.verbose('Duplicate trx on same day', {task: task, archivedTask: archivedTask}); |
139 | cbDupe(task); | 140 | cbDupe(task); |
140 | return; | 141 | return; |
141 | } | 142 | } |
142 | 143 | ||
143 | register(task, function() { | 144 | register(task, function() { |
144 | cbNoDupe(task); | 145 | cbNoDupe(task); |
145 | }); | 146 | }); |
146 | }); | 147 | }); |
147 | } | 148 | } |
148 | 149 | ||
149 | exports.init = init; | 150 | exports.init = init; |
150 | exports.check = check; | 151 | exports.check = check; |
151 | 152 |
partner-hpay.js
1 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; | 1 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; |
2 | 2 | ||
3 | var request = require('request'); | 3 | var request = require('request'); |
4 | var xml2js = require('xml2js'); | 4 | var xml2js = require('xml2js'); |
5 | 5 | ||
6 | var resendDelay = require('sate24/resend-delay.js'); | 6 | var resendDelay = require('sate24/resend-delay.js'); |
7 | var taskHistory = require('./task-history.js'); | 7 | var taskHistory = require('./task-history.js'); |
8 | var antiSameDayDupe = require('./anti-same-day-dupe.js'); | 8 | var antiSameDayDupe = require('./anti-same-day-dupe.js'); |
9 | 9 | ||
10 | var config; | 10 | var config; |
11 | var aaa; | 11 | var aaa; |
12 | var _callbackReport; | 12 | var _callbackReport; |
13 | var logger; | 13 | var logger; |
14 | 14 | ||
15 | var xmlBuilder = new xml2js.Builder({rootName: 'xml'}); | 15 | var xmlBuilder = new xml2js.Builder({rootName: 'xml', headless: true}); |
16 | 16 | ||
17 | function start(options) { | 17 | function start(options) { |
18 | if (!options) { | 18 | if (!options) { |
19 | console.log('Undefined options, terminating....'); | 19 | console.log('Undefined options, terminating....'); |
20 | process.exit(1); | 20 | process.exit(1); |
21 | } | 21 | } |
22 | 22 | ||
23 | if (options.config) { | 23 | if (options.config) { |
24 | config = options.config; | 24 | config = options.config; |
25 | } else { | 25 | } else { |
26 | console.log('Undefined options.config, terminating....') | 26 | console.log('Undefined options.config, terminating....') |
27 | process.exit(1); | 27 | process.exit(1); |
28 | } | 28 | } |
29 | 29 | ||
30 | if (options.aaa) { | 30 | if (options.aaa) { |
31 | aaa = options.aaa; | 31 | aaa = options.aaa; |
32 | _callbackReport = options.aaa.callbackReportWithPushToMongoDb; | 32 | _callbackReport = options.aaa.callbackReportWithPushToMongoDb; |
33 | } else { | 33 | } else { |
34 | console.log('Undefined options.aaa, terminating....') | 34 | console.log('Undefined options.aaa, terminating....') |
35 | process.exit(1); | 35 | process.exit(1); |
36 | } | 36 | } |
37 | 37 | ||
38 | if (options && options.logger) { | 38 | if (options && options.logger) { |
39 | logger = options.logger; | 39 | logger = options.logger; |
40 | } else { | 40 | } else { |
41 | console.log('Undefined options.logger, terminating....') | 41 | console.log('Undefined options.logger, terminating....') |
42 | process.exit(1); | 42 | process.exit(1); |
43 | } | 43 | } |
44 | 44 | ||
45 | resendDelay.init({ | 45 | resendDelay.init({ |
46 | config: config, | 46 | config: config, |
47 | topupRequest: _topupStatus, | 47 | topupRequest: _topupStatus, |
48 | logger: logger | 48 | logger: logger |
49 | }); | 49 | }); |
50 | 50 | ||
51 | taskHistory.init(options); | 51 | taskHistory.init(options); |
52 | antiSameDayDupe.init(options); | 52 | antiSameDayDupe.init(options); |
53 | } | 53 | } |
54 | 54 | ||
55 | function callbackReport(requestId, responseCode, msg) { | 55 | function callbackReport(requestId, responseCode, msg) { |
56 | if (responseCode != '68') { | 56 | if (responseCode != '68') { |
57 | resendDelay.cancel(requestId); | 57 | resendDelay.cancel(requestId); |
58 | 58 | ||
59 | } else { | 59 | } else { |
60 | taskHistory.get(requestId, function(err, task) { | 60 | taskHistory.get(requestId, function(err, task) { |
61 | if (task) { | 61 | if (task) { |
62 | resendDelay.register(task); | 62 | resendDelay.register(task); |
63 | } | 63 | } |
64 | }); | 64 | }); |
65 | } | 65 | } |
66 | 66 | ||
67 | _callbackReport(requestId, responseCode, msg); | 67 | _callbackReport(requestId, responseCode, msg); |
68 | } | 68 | } |
69 | 69 | ||
70 | function parseResponse(task, response) { | 70 | function parseResponse(task, response) { |
71 | var xmlParser = xml2js.parseString; | 71 | var xmlParser = xml2js.parseString; |
72 | 72 | ||
73 | xmlParser(response, function(err, result) { | 73 | xmlParser(response, function(err, result) { |
74 | if (err) { | 74 | if (err) { |
75 | var msg = 'Error parsing response. ' + err; | 75 | var msg = 'Error parsing response. ' + err; |
76 | logger.warn(msg, {task: task, response: response, err: err}); | 76 | logger.warn(msg, {task: task, response: response, err: err}); |
77 | callbackReport(task.requestId, '68', msg); | 77 | callbackReport(task.requestId, '68', msg); |
78 | return; | 78 | return; |
79 | } | 79 | } |
80 | 80 | ||
81 | logger.verbose('Response parsed', {result: result, task: task}); | 81 | logger.verbose('Response parsed', {result: result, task: task}); |
82 | 82 | ||
83 | var rc = '68'; | 83 | var rc = '68'; |
84 | 84 | ||
85 | var partnerResponseCode; | 85 | var partnerResponseCode; |
86 | try { | 86 | try { |
87 | partnerResponseCode = result.hpay.responseCode[0]; | 87 | partnerResponseCode = result.hpay.responseCode[0]; |
88 | } catch(e) {} | 88 | } catch(e) {} |
89 | 89 | ||
90 | var partnerMessage = ''; | 90 | var partnerMessage = ''; |
91 | try { | 91 | try { |
92 | partnerMessage = result.hpay.responseMessage[0]; | 92 | partnerMessage = result.hpay.responseMessage[0]; |
93 | } catch(e) {} | 93 | } catch(e) {} |
94 | 94 | ||
95 | if (partnerResponseCode == '00') { | 95 | if (partnerResponseCode == '00') { |
96 | rc = '00'; | 96 | rc = '00'; |
97 | } | 97 | } |
98 | else if ((partnerResponseCode == '99') && (partnerMessage == 'Member Not Found')) { | 98 | else if ((partnerResponseCode == '99') && (partnerMessage == 'Member Not Found')) { |
99 | rc = '40'; | 99 | rc = '40'; |
100 | } | 100 | } |
101 | else if (partnerResponseCode == '99') { | 101 | else if (partnerResponseCode == '99') { |
102 | rc = '68'; | 102 | rc = '68'; |
103 | } | 103 | } |
104 | else { | 104 | else { |
105 | rc = '40'; | 105 | rc = '40'; |
106 | } | 106 | } |
107 | 107 | ||
108 | var msg = ''; | 108 | var msg = ''; |
109 | if (partnerResponseCode) { | 109 | if (partnerResponseCode) { |
110 | msg = partnerResponseCode; | 110 | msg = partnerResponseCode; |
111 | } | 111 | } |
112 | 112 | ||
113 | if (partnerMessage) { | 113 | if (partnerMessage) { |
114 | msg = msg + ' ' + partnerMessage; | 114 | msg = msg + ' ' + partnerMessage; |
115 | } | 115 | } |
116 | msg = msg.trim(); | 116 | msg = msg.trim(); |
117 | 117 | ||
118 | callbackReport(task.requestId, rc, msg); | 118 | callbackReport(task.requestId, rc, msg); |
119 | }); | 119 | }); |
120 | } | 120 | } |
121 | 121 | ||
122 | function requestToPartner(methodName, task) { | 122 | function requestToPartner(methodName, task) { |
123 | var payload = createPayload(methodName, task, config.h2h_out.userid, config.h2h_out.noid, config.h2h_out.password); | 123 | var payload = createPayload(methodName, task, config.h2h_out.userid, config.h2h_out.noid, config.h2h_out.password); |
124 | if (!payload) { | 124 | if (!payload) { |
125 | callbackReport(task.requestId, '40', 'ERROR: Undefined payload'); | 125 | callbackReport(task.requestId, '40', 'ERROR: Undefined payload'); |
126 | return; | 126 | return; |
127 | } | 127 | } |
128 | 128 | ||
129 | var requestOpts = { | 129 | var requestOpts = { |
130 | url: config.h2h_out.partner, | 130 | url: config.h2h_out.partner, |
131 | method: "POST", | 131 | method: "POST", |
132 | body: payload, | 132 | body: payload, |
133 | headers: { | 133 | |
134 | 'Content-Type': 'text/xml', | ||
135 | } | ||
136 | } | 134 | } |
137 | 135 | ||
138 | logger.verbose('Requesting to partner', {methodName: methodName, task: task, requestOpts: requestOpts}); | 136 | logger.verbose('Requesting to partner', {methodName: methodName, task: task, requestOpts: requestOpts}); |
139 | request(requestOpts, function (err, response, responseBody) { | 137 | request(requestOpts, function (err, response, responseBody) { |
140 | if (err) { | 138 | if (err) { |
141 | var rc = '68'; | 139 | var rc = '68'; |
142 | if (methodName === 'pay') { | 140 | if (methodName === 'pay') { |
143 | rc = '40'; | 141 | rc = '40'; |
144 | } | 142 | } |
145 | 143 | ||
146 | var msg = 'Error requesting pay method. ' + err; | 144 | var msg = 'Error requesting pay method. ' + err; |
147 | callbackReport(task.requestId, rc, msg); | 145 | callbackReport(task.requestId, rc, msg); |
148 | return; | 146 | return; |
149 | } | 147 | } |
150 | 148 | ||
151 | logger.info('Got direct response from partner', {task: task, responseBody: responseBody}); | 149 | logger.info('Got direct response from partner', {task: task, responseBody: responseBody}); |
152 | parseResponse(task, responseBody); | 150 | parseResponse(task, responseBody); |
153 | }); | 151 | }); |
154 | } | 152 | } |
155 | 153 | ||
156 | 154 | ||
157 | function topupRequest(task) { | 155 | function topupRequest(task) { |
158 | if (!aaa.isTodayTrx(task)) { | 156 | if (!aaa.isTodayTrx(task)) { |
159 | logger.warn('Maaf, transaksi beda hari tidak dapat dilakukan'); | 157 | logger.warn('Maaf, transaksi beda hari tidak dapat dilakukan'); |
160 | callbackReport(task.requestId, '68', 'Maaf, transaksi beda hari tidak dapat dilakukan'); | 158 | callbackReport(task.requestId, '68', 'Maaf, transaksi beda hari tidak dapat dilakukan'); |
161 | resendDelay.cancel(task); | 159 | resendDelay.cancel(task); |
162 | return; | 160 | return; |
163 | } | 161 | } |
164 | 162 | ||
165 | antiSameDayDupe.check(task, _topupRequest, onSameDayDupe, _topupStatus); | 163 | antiSameDayDupe.check(task, _topupRequest, onSameDayDupe, _topupStatus); |
166 | } | 164 | } |
167 | 165 | ||
168 | function _topupRequest(task) { | 166 | function _topupRequest(task) { |
169 | taskHistory.put(task, function() { | 167 | taskHistory.put(task, function() { |
170 | requestToPartner('pay', task);; | 168 | requestToPartner('pay', task);; |
171 | }); | 169 | }); |
172 | } | 170 | } |
173 | 171 | ||
174 | function _topupStatus(task) { | 172 | function _topupStatus(task) { |
175 | requestToPartner('checking', task); | 173 | requestToPartner('checking', task); |
176 | } | 174 | } |
177 | 175 | ||
178 | function onSameDayDupe(task) { | 176 | function onSameDayDupe(task) { |
179 | callbackReport(task.requestId, '55', 'Transaksi duplikat dalam satu hari yang sama'); | 177 | callbackReport(task.requestId, '55', 'Transaksi duplikat dalam satu hari yang sama'); |
180 | } | 178 | } |
181 | 179 | ||
182 | function extractProductDetail(remoteProduct) { | 180 | function extractProductDetail(remoteProduct) { |
183 | var product; | 181 | var product; |
184 | 182 | ||
185 | try { | 183 | try { |
186 | product = remoteProduct.split(','); | 184 | product = remoteProduct.split(','); |
187 | } | 185 | } |
188 | catch(e) { | 186 | catch(e) { |
189 | logger.warn('extractProductDetail: exception on split'); | 187 | logger.warn('extractProductDetail: exception on split'); |
190 | return null; | 188 | return null; |
191 | } | 189 | } |
192 | 190 | ||
193 | if (product.length !== 3) { | 191 | if (product.length !== 3) { |
194 | logger.warn('extractProductDetail: product.length <> 3'); | 192 | logger.warn('extractProductDetail: product.length <> 3'); |
195 | return null; | 193 | return null; |
196 | } | 194 | } |
197 | 195 | ||
198 | return {product: product[0], productType: product[1], nominal: product[2]}; | 196 | return {product: product[0], productType: product[1], nominal: product[2]}; |
199 | } | 197 | } |
200 | 198 | ||
201 | function createPayload(transactionType, task, user, noid, pin) { | 199 | function createPayload(transactionType, task, user, noid, pin) { |
202 | var product = extractProductDetail(task.remoteProduct); | 200 | var product = extractProductDetail(task.remoteProduct); |
203 | if (!product) { | 201 | if (!product) { |
204 | logger.warn('createPayload: undefined product'); | 202 | logger.warn('createPayload: undefined product'); |
205 | return; | 203 | return; |
206 | } | 204 | } |
207 | 205 | ||
208 | var payload = { | 206 | var payload = { |
209 | transactionType: transactionType, | 207 | transactionType: transactionType, |
210 | product: product.product, | 208 | product: product.product, |
211 | productType: product.productType, | 209 | productType: product.productType, |
212 | idpel: task.destination, | 210 | idpel: task.destination, |
213 | pin: pin, | 211 | pin: pin, |
214 | noid: noid, | 212 | noid: noid, |
215 | user: user, | 213 | user: user, |
216 | nominal: product.nominal | 214 | nominal: product.nominal |
217 | }; | 215 | }; |
218 | 216 | ||
219 | return xmlBuilder.buildObject(payload); | 217 | return xmlBuilder.buildObject(payload); |
220 | } | 218 | } |
221 | 219 | ||
222 | exports.start = start; | 220 | exports.start = start; |
223 | exports.topupRequest = topupRequest; | 221 | exports.topupRequest = topupRequest; |
224 | 222 |