Commit 52fa01ba174d5c1d5f4c6624d58907348ea12b01
1 parent
dff0c67ee7
Exists in
master
alpha
Showing 4 changed files with 309 additions and 1 deletions Inline Diff
index.js
File was created | 1 | "use strict"; | |
2 | process.chdir(__dirname); | ||
3 | |||
4 | const config = require('komodo-sdk/config'); | ||
5 | const logger = require('komodo-sdk/logger'); | ||
6 | const matrix = require('komodo-sdk/matrix'); | ||
7 | |||
8 | const pullgw = require('komodo-sdk/gateway/pull'); | ||
9 | const partner = require('./partner'); | ||
10 | |||
11 | pullgw.setPartner(partner); | ||
12 |
package.json
1 | { | 1 | { |
2 | "name": "komodo-to-simplepay-pln", | 2 | "name": "komodo-to-simplepay-pln", |
3 | "version": "1.0.0", | 3 | "version": "1.0.0", |
4 | "description": "KOMODO Gateway to SIMPLEPAY - PLN PREPAID", | 4 | "description": "KOMODO Gateway to SIMPLEPAY - PLN PREPAID", |
5 | "main": "index.js", | 5 | "main": "index.js", |
6 | "scripts": { | 6 | "scripts": { |
7 | "test": "mocha" | 7 | "test": "mocha" |
8 | }, | 8 | }, |
9 | "repository": { | 9 | "repository": { |
10 | "type": "git", | 10 | "type": "git", |
11 | "url": "git@gitlab.kodesumber.com:komodo/komodo-to-simplepay-pln-prepaid.git" | 11 | "url": "git@gitlab.kodesumber.com:komodo/komodo-to-simplepay-pln-prepaid.git" |
12 | }, | 12 | }, |
13 | "keywords": [ | 13 | "keywords": [ |
14 | "komodo", | 14 | "komodo", |
15 | "ppob", | 15 | "ppob", |
16 | "payment", | 16 | "payment", |
17 | "pln", | 17 | "pln", |
18 | "simplepay" | 18 | "simplepay" |
19 | ], | 19 | ], |
20 | "author": "Adhidarma Hadiwinoto <me@adhisimon.org>", | 20 | "author": "Adhidarma Hadiwinoto <me@adhisimon.org>", |
21 | "license": "ISC" | 21 | "license": "ISC", |
22 | "dependencies": { | ||
23 | "komodo-sdk": "git+http://gitlab.kodesumber.com/komodo/komodo-sdk.git", | ||
24 | "moment": "^2.18.1", | ||
25 | "request": "^2.81.0" | ||
26 | } | ||
22 | } | 27 | } |
23 | 28 |
partner-misc.js
File was created | 1 | "use strict"; | |
2 | |||
3 | const crypto = require('crypto'); | ||
4 | const moment = require('moment'); | ||
5 | |||
6 | const config = require('komodo-sdk/config'); | ||
7 | const logger = require('komodo-sdk/logger'); | ||
8 | const pull = require('komodo-sdk/gateway/pull'); | ||
9 | |||
10 | const partner = require('./partner'); | ||
11 | |||
12 | function calculateSign(dt, trx_ref_id, cust_num, username, password) { | ||
13 | const cryptoHashPassword = crypto.createHash('sha1'); | ||
14 | const cryptoHashUsernamePassword = crypto.createHash('sha1'); | ||
15 | const cryptoHashOutest = crypto.createHash('sha1'); | ||
16 | |||
17 | cryptoHashPassword.update(password); | ||
18 | const hashPassword = cryptoHashPassword.digest('hex'); | ||
19 | |||
20 | cryptoHashUsernamePassword.update(username + hashPassword); | ||
21 | const hashUsernamePassword = cryptoHashUsernamePassword.digest('hex'); | ||
22 | |||
23 | cryptoHashOutest.update(dt + trx_ref_id + cust_num + hashUsernamePassword); | ||
24 | return cryptoHashOutest.digest('hex'); | ||
25 | } | ||
26 | |||
27 | function createRequestOptions(task, config) { | ||
28 | const dt = moment().format('YYYY-MM-DD HH:mm:ss'); | ||
29 | const sign = calculateSign(dt, task.trx_id, task.destination, config.partner.username, config.partner.password); | ||
30 | |||
31 | return = { | ||
32 | url: config.partner.url, | ||
33 | form: { | ||
34 | username: config.partner.username, | ||
35 | datetime: dt, | ||
36 | code: task.remote_product, | ||
37 | trx_ref_id: task.trx_id, | ||
38 | cust_num: task.destination, | ||
39 | sign: sign | ||
40 | } | ||
41 | } | ||
42 | } | ||
43 | |||
44 | function decodeResponseBody(responseBody) { | ||
45 | let response; | ||
46 | |||
47 | try { | ||
48 | response = JSON.parse(responseBody) | ||
49 | } | ||
50 | catch(e) { | ||
51 | logger.warn('Error parsing response body'); | ||
52 | } | ||
53 | |||
54 | return response; | ||
55 | } | ||
56 | |||
57 | function cleanBalance(balance) { | ||
58 | try { | ||
59 | balance = balance.replace(/\D/g, ''); | ||
60 | } | ||
61 | catch(e) { }; | ||
62 | |||
63 | return balance; | ||
64 | |||
65 | } | ||
66 | |||
67 | function processPartnerResponseBody(body, task) { | ||
68 | let response = decodeResponseBody(responseBody); | ||
69 | let messages = []; | ||
70 | |||
71 | if (!response) { | ||
72 | return; | ||
73 | } | ||
74 | |||
75 | logger.verbose('RESPONSE', {response: response}); | ||
76 | |||
77 | const responseStatus = response.status; | ||
78 | const responseInfo = response.info; | ||
79 | |||
80 | let taskTrxId; | ||
81 | if (task && task.trx_id) { | ||
82 | taskTrxId = task.trx_id; | ||
83 | } | ||
84 | |||
85 | let responseRequestId; | ||
86 | if (response.data && response.data.request_id) { | ||
87 | responseRequestId = response.data.request_id; | ||
88 | } | ||
89 | |||
90 | |||
91 | let responseTrxStatus; | ||
92 | if (response.data && response.data.trx_status) { | ||
93 | responseTrxStatus = response.data.trx_status; | ||
94 | } | ||
95 | |||
96 | let responseTimestamp; | ||
97 | if (response.data && response.data.timestamp) { | ||
98 | responseTimestamp = response.data.timestamp; | ||
99 | messages.push(responseTimestamp); | ||
100 | } | ||
101 | |||
102 | let responseDiag; | ||
103 | if (response.data && response.data.diag) { | ||
104 | responseDiag = response.data.diag; | ||
105 | messages.push(responseDiag); | ||
106 | } | ||
107 | |||
108 | let responseMessage; | ||
109 | if (response.data && response.data.message) { | ||
110 | responseMessage = response.data.message; | ||
111 | messages.push(responseMessage); | ||
112 | } | ||
113 | |||
114 | let responseBalance; | ||
115 | if (response.data && response.data.balance) { | ||
116 | responseBalance = response.data.balance; | ||
117 | messages.push('Balance: ' + responseBalance); | ||
118 | if (responseBalance) { | ||
119 | responseBalance = cleanBalance(responseBalance); | ||
120 | } | ||
121 | } | ||
122 | |||
123 | if (messages.length <= 0) { messages.push('Transaksi anda sedang diproses'); } | ||
124 | |||
125 | let coreReportData = { | ||
126 | trx_id: taskTrxId || responseRequestId, | ||
127 | rc: '68', | ||
128 | message: messages.join('. ') + '.', | ||
129 | sn: '', | ||
130 | handler: config.handler_name, | ||
131 | balance: responseBalance, | ||
132 | core_task: task, | ||
133 | raw: body | ||
134 | } | ||
135 | |||
136 | if (responseStatus == 'Error') { | ||
137 | if (responseInfo == 'insufficient balance') { | ||
138 | coreReportData.rc = '91'; | ||
139 | } | ||
140 | |||
141 | coreReportData.message = [responseStatus, responseInfo].join(': '); | ||
142 | |||
143 | partner.reportToCore(coreReportData); | ||
144 | return; | ||
145 | } | ||
146 | |||
147 | |||
148 | if (responseTrxStatus == 'P') { | ||
149 | logger.verbose('Got pending trx response', {response: response.data}); | ||
150 | coreReportData.rc = '68'; | ||
151 | } | ||
152 | else if (responseTrxStatus == 'S') { | ||
153 | logger.verbose('Got succcess trx response', {response: response.data}); | ||
154 | |||
155 | coreReportData.rc = '00' | ||
156 | |||
157 | coreReportData.sn = composeSn(response); | ||
158 | coreReportData.message += ' SN=' + coreReportData.sn + '.'; | ||
159 | } | ||
160 | else if (trxStatus == 'R') { | ||
161 | logger.verbose('Got rejected trx response', {response: response.data}); | ||
162 | |||
163 | const partnerRC = getPartnerRCFromDiagMessage(responseDiag); | ||
164 | if (partnerRC == '15') { | ||
165 | coreReportData.rc = '14'; | ||
166 | } | ||
167 | else { | ||
168 | coreReportData.rc = '40'; | ||
169 | } | ||
170 | } | ||
171 | |||
172 | partner.reportToCore(coreReportData); | ||
173 | } | ||
174 | |||
175 | function composeSn(response) { | ||
176 | if (!response && !response.data) { return; } | ||
177 | |||
178 | if (!response.data.info) { | ||
179 | return response.data.serial; | ||
180 | } | ||
181 | |||
182 | let token = response.data.serial; | ||
183 | let cust_name = response.data.info.cust_name; | ||
184 | let tariff = response.data.info.kelas; | ||
185 | let total_kwh = response.data.info.size; | ||
186 | |||
187 | if (tariff.indexOf('VA') < 0) { | ||
188 | tariff += 'VA'; | ||
189 | } | ||
190 | |||
191 | if (cust_name) { | ||
192 | cust_name = cust_name.replace(/\W+/g, ' ').trim().replace(/\W+/g, '-').toUpperCase(); | ||
193 | } | ||
194 | |||
195 | return [ token, cust_name, tariff, total_kwh ].join('/'); | ||
196 | } | ||
197 | |||
198 | function getPartnerRCFromDiagMessage(diag) { | ||
199 | let matches = diag.match(/^\s*\[(.*)\]/); | ||
200 | if (!matches || matches.length < 2) { | ||
201 | return; | ||
202 | } | ||
203 | |||
204 | return matches[1]; | ||
205 | } | ||
206 | |||
207 | |||
208 | exports.calculateSign = calculateSign; | ||
209 | exports.createRequestOptions = createRequestOptions; | ||
210 |
partner.js
File was created | 1 | "use strict"; | |
2 | |||
3 | const config = require('komodo-sdk/config'); | ||
4 | const logger = require('komodo-sdk/logger'); | ||
5 | const matrix = require('komodo-sdk/matrix'); | ||
6 | const pull = require('komodo-sdk/gateway/pull'); | ||
7 | |||
8 | const misc = require('./partner-misc'); | ||
9 | |||
10 | /** | ||
11 | * Pembelian ke partner | ||
12 | * | ||
13 | * Merupakan fungsi mandatory yang harus dimiliki oleh setiap gateway. | ||
14 | */ | ||
15 | function buy(task) { | ||
16 | _requestToPartner(task, false); | ||
17 | } | ||
18 | |||
19 | /** | ||
20 | * Pemeriksaan status transaksi ke partner | ||
21 | * Merupakan fungsi mandatory yang harus dimiliki oleh setiap gateway. | ||
22 | */ | ||
23 | function statusCheck(task) { | ||
24 | _requestToPartner(task, true); | ||
25 | } | ||
26 | |||
27 | /** | ||
28 | * Mengirim request ke partner. | ||
29 | * | ||
30 | * Untuk digunakan oleh buy dan checkStatus. | ||
31 | */ | ||
32 | function _requestToPartner(task, pendingOnError) { | ||
33 | |||
34 | const requestOptions = misc.createRequestOptions(task, config); | ||
35 | |||
36 | logger.verbose('Requeting to partner', {requestOptions: requestOptions}); | ||
37 | request.post(requestOptions, function(err, res, body) { | ||
38 | |||
39 | if (err) { | ||
40 | |||
41 | let rc = '68'; | ||
42 | if (!pendingOnError) { rc = '91'; } | ||
43 | |||
44 | logger.warn('Error requesting to partner', {task: task, err: err}) | ||
45 | |||
46 | _reportToCore({ | ||
47 | trx_id: task.trx_id, | ||
48 | rc: rc, | ||
49 | message: 'Error requesting to partner: ' + err, | ||
50 | handler: config.handler_name | ||
51 | }) | ||
52 | |||
53 | return; | ||
54 | } | ||
55 | |||
56 | if (res.statusCode != 200) { | ||
57 | let rc = '68'; | ||
58 | logger.warn('Partner returning non 200 HTTP STATUS CODE', {task: task, http_status_code: res.statusCode}) | ||
59 | |||
60 | _reportToCore({ | ||
61 | trx_id: task.trx_id, | ||
62 | rc: rc, | ||
63 | message: 'Partner returning HTTP STATUS CODE ' + res.statusCode + ' instead of 200'; | ||
64 | handler: config.handler_name | ||
65 | }) | ||
66 | |||
67 | return; | ||
68 | } | ||
69 | |||
70 | misc.processPartnerResponseBody(body, task); | ||
71 | }) | ||
72 | } | ||
73 | |||
74 | /** | ||
75 | * Mengirim report hasil transaksi ke CORE | ||
76 | */ | ||
77 | function reportToCore(data) { | ||
78 | pull.report(data); | ||
79 | } | ||
80 | |||
81 | exports.buy = buy; | ||
82 | exports.statusCheck = statusCheck; | ||
83 | exports.reportToCore = reportToCore; | ||
84 |