quickpos 1.0.910 → 1.0.912
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROVIDERS-DETAILS.md +1544 -0
- package/examples/example-2checkout.js +78 -0
- package/examples/example-bitpay.js +83 -0
- package/examples/example-cardcom.js +80 -0
- package/examples/example-cashfree.js +109 -0
- package/examples/example-checkout.js +85 -0
- package/examples/example-coingate.js +101 -0
- package/examples/example-coinpayments.js +89 -0
- package/examples/example-doku.js +27 -0
- package/examples/example-epay.js +64 -0
- package/examples/example-epoint.js +91 -0
- package/examples/example-freekassa.js +26 -0
- package/examples/example-heleket.js +139 -0
- package/examples/example-konnect.js +227 -0
- package/examples/example-midtrans.js +80 -0
- package/examples/example-noonpayments.js +297 -0
- package/examples/example-nowpayments.js +289 -0
- package/examples/example-omise.js +27 -0
- package/examples/example-paycom.js +82 -0
- package/{example-paydisini.js → examples/example-paydisini.js} +1 -1
- package/examples/example-payid19.js +87 -0
- package/examples/example-paykun.js +29 -0
- package/examples/example-payme.js +202 -0
- package/examples/example-paymentwall.js +201 -0
- package/examples/example-paynet.js +104 -0
- package/examples/example-paynettr.js +18 -0
- package/examples/example-payoneer.js +74 -0
- package/examples/example-payop.js +351 -0
- package/examples/example-paypal.js +200 -0
- package/examples/example-payriff.js +89 -0
- package/examples/example-paysend.js +81 -0
- package/examples/example-payspace.js +103 -0
- package/examples/example-payssion.js +27 -0
- package/examples/example-paytabs.js +28 -0
- package/examples/example-paytm.js +78 -0
- package/examples/example-payuindia.js +108 -0
- package/examples/example-payulatam.js +75 -0
- package/examples/example-phonepe.js +27 -0
- package/examples/example-picpay.js +27 -0
- package/examples/example-plisio.js +84 -0
- package/examples/example-portwallet.js +90 -0
- package/examples/example-primepayments.js +250 -0
- package/examples/example-razorpay.js +30 -0
- package/examples/example-senangpay.js +28 -0
- package/examples/example-shurjopay.js +94 -0
- package/examples/example-toyyibpay.js +80 -0
- package/examples/example-tripay.js +89 -0
- package/examples/example-unitpay.js +26 -0
- package/examples/example-urway.js +28 -0
- package/examples/example-volet.js +80 -0
- package/examples/example-xendit.js +28 -0
- package/examples/example-yallapay.js +253 -0
- package/examples/example-yookassa.js +27 -0
- package/examples/example-youcanpay.js +28 -0
- package/examples/example-zarinpal.js +98 -0
- package/{example.js → examples/example.js} +1 -1
- package/lib/2checkout.js +165 -0
- package/lib/amazonpay.js +161 -0
- package/lib/bitpay.js +122 -0
- package/lib/cardcom.js +193 -0
- package/lib/cashfree.js +184 -0
- package/lib/checkout.js +248 -0
- package/lib/coinbase.js +150 -0
- package/lib/coingate.js +137 -0
- package/lib/coinpayments.js +245 -0
- package/lib/doku.js +173 -0
- package/lib/epay.js +175 -0
- package/lib/epoint.js +162 -0
- package/lib/freekassa.js +128 -0
- package/lib/heleket.js +67 -1
- package/lib/instamojo.js +158 -0
- package/lib/konnect.js +211 -0
- package/lib/midtrans.js +227 -0
- package/lib/noonpayments.js +650 -0
- package/lib/nowpayments.js +311 -0
- package/lib/omise.js +150 -0
- package/lib/paddle.js +180 -0
- package/lib/paycom.js +216 -0
- package/lib/payid19.js +211 -0
- package/lib/paykun.js +144 -0
- package/lib/payme.js +302 -0
- package/lib/paymentwall.js +205 -0
- package/lib/paynet.js +186 -0
- package/lib/paynettr.js +165 -0
- package/lib/payoneer.js +128 -0
- package/lib/payop.js +256 -0
- package/lib/paypal.js +542 -0
- package/lib/payriff.js +148 -0
- package/lib/paysend.js +189 -0
- package/lib/payspace.js +168 -0
- package/lib/payssion.js +177 -0
- package/lib/paytabs.js +145 -0
- package/lib/paytm.js +253 -0
- package/lib/payuindia.js +162 -0
- package/lib/payulatam.js +179 -0
- package/lib/perfectmoney.js +143 -0
- package/lib/phonepe.js +174 -0
- package/lib/picpay.js +119 -0
- package/lib/plisio.js +234 -0
- package/lib/portwallet.js +152 -0
- package/lib/primepayments.js +256 -0
- package/lib/razorpay.js +205 -0
- package/lib/senangpay.js +130 -0
- package/lib/shurjopay.js +159 -0
- package/lib/toyyibpay.js +151 -0
- package/lib/tripay.js +220 -0
- package/lib/unitpay.js +223 -0
- package/lib/urway.js +182 -0
- package/lib/volet.js +147 -0
- package/lib/xendit.js +206 -0
- package/lib/yallapay.js +279 -0
- package/lib/yookassa.js +193 -0
- package/lib/youcanpay.js +124 -0
- package/lib/zarinpal.js +157 -0
- package/package.json +138 -64
- package/readme.md +348 -105
- package/test.js +492 -0
- package/example-heleket.js +0 -83
- package/lib/vallet.js +0 -22
- /package/{example-anypay.js → examples/example-anypay.js} +0 -0
- /package/{example-bufpay.js → examples/example-bufpay.js} +0 -0
- /package/{example-cryptomus.js → examples/example-cryptomus.js} +0 -0
- /package/{example-esnekpos.js → examples/example-esnekpos.js} +0 -0
- /package/{example-fedapay.js → examples/example-fedapay.js} +0 -0
- /package/{example-iyzico.js → examples/example-iyzico.js} +0 -0
- /package/{example-papara.js → examples/example-papara.js} +0 -0
- /package/{example-payeer.js → examples/example-payeer.js} +0 -0
- /package/{example-paymaya.js → examples/example-paymaya.js} +0 -0
- /package/{example-shopier.js → examples/example-shopier.js} +0 -0
- /package/{ipaymu.js → examples/ipaymu.js} +0 -0
- /package/{oderopay.js → examples/oderopay.js} +0 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const crypto = require('crypto');
|
|
3
|
+
|
|
4
|
+
class CoinPaymentsClient {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
const requiredFields = ['publicKey', 'privateKey'];
|
|
7
|
+
for (let field of requiredFields) {
|
|
8
|
+
if (!config[field]) throw new Error(`Missing required field: ${field}`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
this.publicKey = config.publicKey;
|
|
12
|
+
this.privateKey = config.privateKey;
|
|
13
|
+
this.URL = 'https://www.coinpayments.net/api.php';
|
|
14
|
+
this.ipnSecret = config.ipnSecret || '';
|
|
15
|
+
|
|
16
|
+
this.client = axios.create({
|
|
17
|
+
baseURL: this.URL,
|
|
18
|
+
headers: {
|
|
19
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
this.client.interceptors.response.use(response => {
|
|
24
|
+
return response;
|
|
25
|
+
}, error => {
|
|
26
|
+
if (error.response) {
|
|
27
|
+
throw new Error(`CoinPayments API error: ${error.response.data.error || error.message}`);
|
|
28
|
+
}
|
|
29
|
+
throw new Error(`CoinPayments API error: ${error.message}`);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
generateHMAC(params) {
|
|
34
|
+
const paramString = new URLSearchParams(params).toString();
|
|
35
|
+
return crypto
|
|
36
|
+
.createHmac('sha512', this.privateKey)
|
|
37
|
+
.update(paramString)
|
|
38
|
+
.digest('hex');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async apiCall(cmd, params = {}) {
|
|
42
|
+
try {
|
|
43
|
+
const requestParams = {
|
|
44
|
+
version: 1,
|
|
45
|
+
cmd: cmd,
|
|
46
|
+
key: this.publicKey,
|
|
47
|
+
format: 'json',
|
|
48
|
+
...params
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const hmac = this.generateHMAC(requestParams);
|
|
52
|
+
const formData = new URLSearchParams(requestParams);
|
|
53
|
+
|
|
54
|
+
const response = await axios.post(this.URL, formData, {
|
|
55
|
+
headers: {
|
|
56
|
+
'HMAC': hmac,
|
|
57
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (response.data.error !== 'ok') {
|
|
62
|
+
throw new Error(response.data.error || 'API call failed');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return response.data.result;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
throw new Error(`API call error: ${error.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async createPayment(options) {
|
|
72
|
+
try {
|
|
73
|
+
const params = {
|
|
74
|
+
amount: parseFloat(options.amount),
|
|
75
|
+
currency1: options.currency1 || options.currency || 'USD',
|
|
76
|
+
currency2: options.currency2 || 'BTC',
|
|
77
|
+
buyer_email: options.buyerEmail || options.email,
|
|
78
|
+
buyer_name: options.buyerName || options.name || 'Customer',
|
|
79
|
+
item_name: options.itemName || options.name || 'Product',
|
|
80
|
+
item_number: options.itemNumber || options.orderId || `ORDER-${Date.now()}`,
|
|
81
|
+
invoice: options.invoice || options.orderId || `INV-${Date.now()}`,
|
|
82
|
+
custom: options.custom || '',
|
|
83
|
+
ipn_url: options.ipnUrl || options.callback_link
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const result = await this.apiCall('create_transaction', params);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
status: 'success',
|
|
90
|
+
data: {
|
|
91
|
+
txnId: result.txn_id,
|
|
92
|
+
address: result.address,
|
|
93
|
+
amount: result.amount,
|
|
94
|
+
confirmsNeeded: result.confirms_needed,
|
|
95
|
+
timeout: result.timeout,
|
|
96
|
+
statusUrl: result.status_url,
|
|
97
|
+
qrcodeUrl: result.qrcode_url,
|
|
98
|
+
url: result.checkout_url || result.status_url
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
throw new Error(`Payment creation error: ${error.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async getTransactionInfo(txnId) {
|
|
107
|
+
try {
|
|
108
|
+
const result = await this.apiCall('get_tx_info', { txid: txnId });
|
|
109
|
+
return result;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw new Error(`Transaction info error: ${error.message}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async getCallbackAddress(currency) {
|
|
116
|
+
try {
|
|
117
|
+
const result = await this.apiCall('get_callback_address', {
|
|
118
|
+
currency: currency,
|
|
119
|
+
ipn_url: this.ipnUrl
|
|
120
|
+
});
|
|
121
|
+
return result;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
throw new Error(`Callback address error: ${error.message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async handleCallback(callbackData) {
|
|
128
|
+
try {
|
|
129
|
+
const verification = await this.verifyIPNCallback(callbackData);
|
|
130
|
+
|
|
131
|
+
if (!verification.status) {
|
|
132
|
+
throw new Error(verification.error.message);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const data = verification.data;
|
|
136
|
+
|
|
137
|
+
// Status mapping
|
|
138
|
+
// Status >= 100 or status == 2 means payment is complete
|
|
139
|
+
const status = parseInt(data.status);
|
|
140
|
+
let paymentStatus = 'pending';
|
|
141
|
+
|
|
142
|
+
if (status >= 100 || status === 2) {
|
|
143
|
+
paymentStatus = 'success';
|
|
144
|
+
} else if (status < 0) {
|
|
145
|
+
paymentStatus = 'failed';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
status: paymentStatus,
|
|
150
|
+
txnId: data.txn_id,
|
|
151
|
+
orderId: data.item_number || data.invoice,
|
|
152
|
+
amount: parseFloat(data.amount1 || data.amount),
|
|
153
|
+
currency: data.currency1,
|
|
154
|
+
receivedAmount: parseFloat(data.amount2 || 0),
|
|
155
|
+
receivedCurrency: data.currency2,
|
|
156
|
+
fee: parseFloat(data.fee || 0),
|
|
157
|
+
statusCode: status,
|
|
158
|
+
statusText: data.status_text
|
|
159
|
+
};
|
|
160
|
+
} catch (error) {
|
|
161
|
+
throw new Error(`Error in CoinPayments callback handling: ${error.message}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async verifyIPNCallback(data) {
|
|
166
|
+
try {
|
|
167
|
+
// Verify HMAC signature if available
|
|
168
|
+
if (this.ipnSecret && data.hmac) {
|
|
169
|
+
const merchant = data.merchant;
|
|
170
|
+
delete data.hmac;
|
|
171
|
+
delete data.merchant;
|
|
172
|
+
|
|
173
|
+
const paramString = new URLSearchParams(
|
|
174
|
+
Object.keys(data)
|
|
175
|
+
.sort()
|
|
176
|
+
.reduce((obj, key) => {
|
|
177
|
+
obj[key] = data[key];
|
|
178
|
+
return obj;
|
|
179
|
+
}, {})
|
|
180
|
+
).toString();
|
|
181
|
+
|
|
182
|
+
const expectedHmac = crypto
|
|
183
|
+
.createHmac('sha512', this.ipnSecret)
|
|
184
|
+
.update(paramString)
|
|
185
|
+
.digest('hex');
|
|
186
|
+
|
|
187
|
+
if (data.hmac !== expectedHmac) {
|
|
188
|
+
return {
|
|
189
|
+
status: false,
|
|
190
|
+
error: {
|
|
191
|
+
code: 401,
|
|
192
|
+
message: 'Invalid IPN HMAC signature'
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
data.merchant = merchant;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const status = parseInt(data.status);
|
|
201
|
+
if (status < 0) {
|
|
202
|
+
return {
|
|
203
|
+
status: false,
|
|
204
|
+
error: {
|
|
205
|
+
code: 400,
|
|
206
|
+
message: `Payment failed: ${data.status_text}`
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
status: true,
|
|
213
|
+
data: data
|
|
214
|
+
};
|
|
215
|
+
} catch (error) {
|
|
216
|
+
return {
|
|
217
|
+
status: false,
|
|
218
|
+
error: {
|
|
219
|
+
code: 500,
|
|
220
|
+
message: error.message
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async getRates(short = 1, accepted = 1) {
|
|
227
|
+
try {
|
|
228
|
+
const result = await this.apiCall('rates', { short, accepted });
|
|
229
|
+
return result;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
throw new Error(`Get rates error: ${error.message}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async getBasicInfo() {
|
|
236
|
+
try {
|
|
237
|
+
const result = await this.apiCall('get_basic_info');
|
|
238
|
+
return result;
|
|
239
|
+
} catch (error) {
|
|
240
|
+
throw new Error(`Get basic info error: ${error.message}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
module.exports = CoinPaymentsClient;
|
package/lib/doku.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const crypto = require('crypto');
|
|
3
|
+
|
|
4
|
+
class DokuClient {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
const requiredFields = ['clientId', 'secretKey'];
|
|
7
|
+
for (let field of requiredFields) {
|
|
8
|
+
if (!config[field]) throw new Error(`Missing required field: ${field}`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
this.clientId = config.clientId;
|
|
12
|
+
this.secretKey = config.secretKey;
|
|
13
|
+
this.sharedKey = config.sharedKey || this.secretKey;
|
|
14
|
+
this.baseURL = config.sandbox
|
|
15
|
+
? 'https://sandbox.doku.com'
|
|
16
|
+
: 'https://api.doku.com';
|
|
17
|
+
|
|
18
|
+
this.client = axios.create({
|
|
19
|
+
baseURL: this.baseURL,
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
'Client-Id': this.clientId
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
generateSignature(data, timestamp) {
|
|
28
|
+
const digest = crypto.createHash('sha256')
|
|
29
|
+
.update(JSON.stringify(data))
|
|
30
|
+
.digest('hex');
|
|
31
|
+
|
|
32
|
+
const signatureComponents = `${this.clientId}:${timestamp}:${digest}`;
|
|
33
|
+
|
|
34
|
+
return crypto
|
|
35
|
+
.createHmac('sha256', this.secretKey)
|
|
36
|
+
.update(signatureComponents)
|
|
37
|
+
.digest('base64');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async createPayment(options) {
|
|
41
|
+
try {
|
|
42
|
+
const orderId = options.orderId || `ORDER-${Date.now()}`;
|
|
43
|
+
const timestamp = new Date().toISOString();
|
|
44
|
+
|
|
45
|
+
const paymentData = {
|
|
46
|
+
order: {
|
|
47
|
+
invoice_number: orderId,
|
|
48
|
+
amount: parseFloat(options.amount)
|
|
49
|
+
},
|
|
50
|
+
payment: {
|
|
51
|
+
payment_due_date: options.dueDate || new Date(Date.now() + 24*60*60*1000).toISOString().split('T')[0]
|
|
52
|
+
},
|
|
53
|
+
customer: {
|
|
54
|
+
name: options.name || options.customerName || '',
|
|
55
|
+
email: options.email || '',
|
|
56
|
+
phone: options.phone || ''
|
|
57
|
+
},
|
|
58
|
+
callback_url: options.callbackUrl || options.callback_link,
|
|
59
|
+
return_url: options.successUrl || options.callback_link
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const signature = this.generateSignature(paymentData, timestamp);
|
|
63
|
+
|
|
64
|
+
const response = await this.client.post('/v1/payment-code', paymentData, {
|
|
65
|
+
headers: {
|
|
66
|
+
'Request-Timestamp': timestamp,
|
|
67
|
+
'Signature': signature
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
status: 'success',
|
|
73
|
+
data: {
|
|
74
|
+
orderId: orderId,
|
|
75
|
+
paymentCode: response.data.payment_code,
|
|
76
|
+
amount: paymentData.order.amount,
|
|
77
|
+
expiredDate: response.data.expired_date,
|
|
78
|
+
virtualAccount: response.data.virtual_account_info
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
} catch (error) {
|
|
82
|
+
throw new Error(`Payment creation error: ${error.response?.data?.error?.message || error.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async handleCallback(callbackData) {
|
|
87
|
+
try {
|
|
88
|
+
const verification = await this.verifyCallback(callbackData);
|
|
89
|
+
|
|
90
|
+
if (!verification.status) {
|
|
91
|
+
throw new Error(verification.error.message);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Status mapping
|
|
95
|
+
const statusMapping = {
|
|
96
|
+
'SUCCESS': 'success',
|
|
97
|
+
'PAID': 'success',
|
|
98
|
+
'PENDING': 'pending',
|
|
99
|
+
'FAILED': 'failed',
|
|
100
|
+
'EXPIRED': 'failed'
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
status: statusMapping[callbackData.transaction.status] || 'unknown',
|
|
105
|
+
orderId: callbackData.order.invoice_number,
|
|
106
|
+
transactionId: callbackData.transaction.id,
|
|
107
|
+
amount: parseFloat(callbackData.order.amount),
|
|
108
|
+
currency: 'IDR',
|
|
109
|
+
paymentStatus: callbackData.transaction.status,
|
|
110
|
+
paymentChannel: callbackData.payment_channel
|
|
111
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
throw new Error(`Error in Doku callback handling: ${error.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async verifyCallback(data) {
|
|
118
|
+
try {
|
|
119
|
+
const receivedSignature = data.signature;
|
|
120
|
+
const timestamp = data.timestamp;
|
|
121
|
+
|
|
122
|
+
const callbackData = { ...data };
|
|
123
|
+
delete callbackData.signature;
|
|
124
|
+
delete callbackData.timestamp;
|
|
125
|
+
|
|
126
|
+
const expectedSignature = this.generateSignature(callbackData, timestamp);
|
|
127
|
+
|
|
128
|
+
if (receivedSignature !== expectedSignature) {
|
|
129
|
+
return {
|
|
130
|
+
status: false,
|
|
131
|
+
error: {
|
|
132
|
+
code: 401,
|
|
133
|
+
message: 'Invalid signature'
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
status: true,
|
|
140
|
+
data: data
|
|
141
|
+
};
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return {
|
|
144
|
+
status: false,
|
|
145
|
+
error: {
|
|
146
|
+
code: 500,
|
|
147
|
+
message: error.message
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async checkPaymentStatus(invoiceNumber) {
|
|
154
|
+
try {
|
|
155
|
+
const timestamp = new Date().toISOString();
|
|
156
|
+
const requestData = { invoice_number: invoiceNumber };
|
|
157
|
+
const signature = this.generateSignature(requestData, timestamp);
|
|
158
|
+
|
|
159
|
+
const response = await this.client.get(`/v1/payment-code/${invoiceNumber}`, {
|
|
160
|
+
headers: {
|
|
161
|
+
'Request-Timestamp': timestamp,
|
|
162
|
+
'Signature': signature
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return response.data;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
throw new Error(`Error checking payment status: ${error.response?.data?.error?.message || error.message}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = DokuClient;
|
package/lib/epay.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const crypto = require('crypto');
|
|
3
|
+
|
|
4
|
+
class EpayClient {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
const requiredFields = ['merchantId', 'secretKey'];
|
|
7
|
+
for (let field of requiredFields) {
|
|
8
|
+
if (!config[field]) throw new Error(`Missing required field: ${field}`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
this.merchantId = config.merchantId;
|
|
12
|
+
this.secretKey = config.secretKey;
|
|
13
|
+
this.baseURL = 'https://epay.bg';
|
|
14
|
+
|
|
15
|
+
this.client = axios.create({
|
|
16
|
+
baseURL: this.baseURL,
|
|
17
|
+
headers: {
|
|
18
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
generateChecksum(data) {
|
|
24
|
+
const fields = [
|
|
25
|
+
data.MIN,
|
|
26
|
+
data.INVOICE,
|
|
27
|
+
data.AMOUNT,
|
|
28
|
+
data.EXP_TIME,
|
|
29
|
+
data.DESCR
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const dataString = fields.join('');
|
|
33
|
+
const hmac = crypto.createHmac('sha1', this.secretKey);
|
|
34
|
+
hmac.update(dataString);
|
|
35
|
+
return hmac.digest('hex');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async createPayment(options) {
|
|
39
|
+
try {
|
|
40
|
+
const orderId = options.orderId || `ORDER-${Date.now()}`;
|
|
41
|
+
const amount = parseFloat(options.amount).toFixed(2);
|
|
42
|
+
const expirationTime = options.expirationTime || this.getExpirationTime(24); // 24 hours
|
|
43
|
+
|
|
44
|
+
const paymentData = {
|
|
45
|
+
MIN: this.merchantId,
|
|
46
|
+
INVOICE: orderId,
|
|
47
|
+
AMOUNT: amount,
|
|
48
|
+
EXP_TIME: expirationTime,
|
|
49
|
+
DESCR: options.description || options.name || 'Payment',
|
|
50
|
+
ENCODING: 'UTF-8'
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
paymentData.CHECKSUM = this.generateChecksum(paymentData);
|
|
54
|
+
|
|
55
|
+
// Create payment URL
|
|
56
|
+
const params = new URLSearchParams(paymentData);
|
|
57
|
+
const paymentUrl = `${this.baseURL}/ezp/reg_bill.php?${params.toString()}`;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
status: 'success',
|
|
61
|
+
data: {
|
|
62
|
+
url: paymentUrl,
|
|
63
|
+
orderId: orderId,
|
|
64
|
+
amount: amount,
|
|
65
|
+
expirationTime: expirationTime
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw new Error(`Payment creation error: ${error.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async handleCallback(callbackData) {
|
|
74
|
+
try {
|
|
75
|
+
const verification = await this.verifyCallback(callbackData);
|
|
76
|
+
|
|
77
|
+
if (!verification.status) {
|
|
78
|
+
throw new Error(verification.error.message);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Status mapping
|
|
82
|
+
const statusMapping = {
|
|
83
|
+
'PAID': 'success',
|
|
84
|
+
'DENIED': 'failed',
|
|
85
|
+
'EXPIRED': 'failed'
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
status: statusMapping[callbackData.STATUS] || 'unknown',
|
|
90
|
+
orderId: callbackData.INVOICE,
|
|
91
|
+
transactionId: callbackData.STAN,
|
|
92
|
+
amount: parseFloat(callbackData.AMOUNT),
|
|
93
|
+
currency: 'BGN',
|
|
94
|
+
paymentStatus: callbackData.STATUS,
|
|
95
|
+
paymentDate: callbackData.PAY_TIME,
|
|
96
|
+
cardBrand: callbackData.CARD
|
|
97
|
+
};
|
|
98
|
+
} catch (error) {
|
|
99
|
+
throw new Error(`Error in ePay callback handling: ${error.message}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async verifyCallback(data) {
|
|
104
|
+
try {
|
|
105
|
+
const encoded = data.encoded || data.ENCODED;
|
|
106
|
+
|
|
107
|
+
if (!encoded) {
|
|
108
|
+
return {
|
|
109
|
+
status: false,
|
|
110
|
+
error: {
|
|
111
|
+
code: 400,
|
|
112
|
+
message: 'Encoded data not found'
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Decode base64
|
|
118
|
+
const decoded = Buffer.from(encoded, 'base64').toString('utf-8');
|
|
119
|
+
|
|
120
|
+
// Parse data
|
|
121
|
+
const params = {};
|
|
122
|
+
decoded.split(':').forEach(pair => {
|
|
123
|
+
const [key, value] = pair.split('=');
|
|
124
|
+
if (key && value) {
|
|
125
|
+
params[key] = value;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Verify checksum
|
|
130
|
+
const receivedChecksum = params.CHECKSUM;
|
|
131
|
+
delete params.CHECKSUM;
|
|
132
|
+
|
|
133
|
+
const calculatedChecksum = this.generateChecksum(params);
|
|
134
|
+
|
|
135
|
+
if (receivedChecksum !== calculatedChecksum) {
|
|
136
|
+
return {
|
|
137
|
+
status: false,
|
|
138
|
+
error: {
|
|
139
|
+
code: 401,
|
|
140
|
+
message: 'Invalid checksum'
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
status: true,
|
|
147
|
+
data: params
|
|
148
|
+
};
|
|
149
|
+
} catch (error) {
|
|
150
|
+
return {
|
|
151
|
+
status: false,
|
|
152
|
+
error: {
|
|
153
|
+
code: 500,
|
|
154
|
+
message: error.message
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
getExpirationTime(hours = 24) {
|
|
161
|
+
const date = new Date();
|
|
162
|
+
date.setHours(date.getHours() + hours);
|
|
163
|
+
|
|
164
|
+
const year = date.getFullYear();
|
|
165
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
166
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
167
|
+
const hour = String(date.getHours()).padStart(2, '0');
|
|
168
|
+
const minute = String(date.getMinutes()).padStart(2, '0');
|
|
169
|
+
const second = String(date.getSeconds()).padStart(2, '0');
|
|
170
|
+
|
|
171
|
+
return `${year}${month}${day}${hour}${minute}${second}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = EpayClient;
|