quickpos 1.0.916 → 1.0.918

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/lib/2checkout.js CHANGED
@@ -1,165 +1,121 @@
1
- const axios = require('axios');
2
1
  const crypto = require('crypto');
3
2
 
4
3
  class TwoCheckoutClient {
5
4
  constructor(config) {
6
- const requiredFields = ['merchantCode', 'secretKey'];
5
+ const requiredFields = ['merchantCode', 'secretWord'];
7
6
  for (let field of requiredFields) {
8
7
  if (!config[field]) throw new Error(`Missing required field: ${field}`);
9
8
  }
10
9
 
11
10
  this.merchantCode = config.merchantCode;
12
- this.secretKey = config.secretKey;
13
- this.baseURL = config.sandbox
14
- ? 'https://api.sandbox.2checkout.com'
15
- : 'https://api.2checkout.com';
16
-
17
- this.client = axios.create({
18
- baseURL: this.baseURL,
19
- headers: {
20
- 'Content-Type': 'application/json'
11
+ this.secretWord = config.secretWord;
12
+ this.sandbox = config.sandbox || false;
13
+ }
14
+
15
+ generateHash(params, algo = 'md5') {
16
+ let signatureString = '';
17
+
18
+ Object.keys(params).forEach(key => {
19
+ const value = String(params[key]); // Değer string olmalı
20
+ if (value.length > 0) { // Boş değerler bazen hash'e katılmaz
21
+ signatureString += value.length + value;
21
22
  }
22
23
  });
23
- }
24
24
 
25
- generateSignature(params) {
26
- const signatureString = Object.keys(params)
27
- .sort()
28
- .map(key => params[key].length + params[key])
29
- .join('');
30
-
31
25
  return crypto
32
- .createHmac('sha256', this.secretKey)
26
+ .createHmac(algo, this.secretWord)
33
27
  .update(signatureString)
34
28
  .digest('hex');
35
29
  }
36
30
 
37
- async createPayment(options) {
38
- try {
39
- const orderId = options.orderId || `ORDER-${Date.now()}`;
40
- const timestamp = new Date().toISOString();
41
-
42
- const params = {
43
- 'merchant': this.merchantCode,
44
- 'dynamic': '1',
45
- 'return-url': options.successUrl || options.callback_link,
46
- 'return-type': 'redirect',
47
- 'expiration': timestamp,
48
- 'order-ext-ref': orderId,
49
- 'item-ext-ref': orderId,
50
- 'customer-ext-ref': options.customerId || '',
51
- 'currency': options.currency || 'USD',
52
- 'language': options.language || 'en',
53
- 'test': options.sandbox ? '1' : '0',
54
- 'prod-name[0]': options.name || 'Payment',
55
- 'prod-type[0]': 'PRODUCT',
56
- 'prod-price[0]': parseFloat(options.amount).toString(),
57
- 'prod-qty[0]': '1',
58
- 'prod-description[0]': options.description || '',
59
- 'customer-email': options.email || '',
60
- 'customer-name': options.customerName || ''
61
- };
62
-
63
- const signature = this.generateSignature(params);
64
- params.signature = signature;
65
-
66
- // Create form URL
67
- const formParams = new URLSearchParams(params);
68
- const paymentUrl = `https://secure.2checkout.com/order/checkout.php?${formParams.toString()}`;
69
-
70
- return {
71
- status: 'success',
72
- data: {
73
- url: paymentUrl,
74
- orderId: orderId,
75
- params: params
76
- }
77
- };
78
- } catch (error) {
79
- throw new Error(`Payment creation error: ${error.message}`);
80
- }
81
- }
31
+ createPaymentUrl(options) {
32
+ const orderId = options.orderId || `ORDER-${Date.now()}`;
33
+
34
+ const params = {
35
+ 'merchant': this.merchantCode,
36
+ 'dynamic': '1', // Dinamik ürün modu
37
+ 'prod-name[]': options.name || 'Payment',
38
+ 'prod-type[]': 'GLOBAL_SERVICE', // Ürün tipi
39
+ 'prod-price[]': parseFloat(options.amount).toFixed(2),
40
+ 'prod-qty[]': '1',
41
+ 'currency': options.currency || 'USD',
42
+ 'return-type': 'redirect',
43
+ 'return-url': options.callbackUrl,
44
+ 'expiration': Math.floor(Date.now() / 1000) + 3600, // 1 saat geçerli
45
+ 'order-ext-ref': orderId,
46
+ 'customer-ref': options.customerId || '',
47
+ 'customer-email': options.email || ''
48
+ };
49
+
50
+ let signatureData = '';
51
+
52
+ const keysToSign = [
53
+ 'merchant', 'dynamic', 'prod-name[]', 'prod-type[]', 'prod-price[]',
54
+ 'prod-qty[]', 'currency', 'return-type', 'return-url', 'expiration',
55
+ 'order-ext-ref', 'customer-ref', 'customer-email'
56
+ ];
57
+
58
+ keysToSign.forEach(key => {
59
+ const val = String(params[key] || '');
60
+ if(val) signatureData += val.length + val;
61
+ });
82
62
 
83
- async handleCallback(callbackData) {
84
- try {
85
- const verification = await this.verifyCallback(callbackData);
86
-
87
- if (!verification.status) {
88
- throw new Error(verification.error.message);
89
- }
63
+ const signature = crypto.createHmac('md5', this.secretWord).update(signatureData).digest('hex');
64
+ params['signature'] = signature;
90
65
 
91
- const data = verification.data;
92
-
93
- // Status mapping
94
- const statusMapping = {
95
- 'COMPLETE': 'success',
96
- 'PENDING': 'pending',
97
- 'REFUND': 'refunded',
98
- 'REVERSED': 'failed'
99
- };
100
-
101
- return {
102
- status: statusMapping[data.order_status] || 'unknown',
103
- orderId: data['order-ext-ref'] || data.order_number,
104
- transactionId: data.order_number,
105
- amount: parseFloat(data.invoice_list_amount || data.order_amount),
106
- currency: data.list_currency,
107
- paymentStatus: data.order_status,
108
- invoiceId: data.invoice_id
109
- };
110
- } catch (error) {
111
- throw new Error(`Error in 2Checkout callback handling: ${error.message}`);
112
- }
66
+ // URL Parametrelerini oluştur
67
+ const queryParams = new URLSearchParams();
68
+ Object.keys(params).forEach(key => queryParams.append(key, params[key]));
69
+
70
+ return `https://secure.2checkout.com/order/checkout.php?${queryParams.toString()}`;
113
71
  }
114
72
 
115
- async verifyCallback(data) {
73
+ /**
74
+ * IPN (Callback) Doğrulama
75
+ * Bu kısım, sunucunuza gelen POST isteğini doğrular.
76
+ */
77
+ verifyCallback(postData) {
116
78
  try {
117
- const hash = data.hash || data.HASH;
118
-
119
- if (!hash) {
120
- return {
121
- status: false,
122
- error: {
123
- code: 400,
124
- message: 'Hash not found in callback data'
125
- }
126
- };
79
+ // Gelen veriden HASH'i ayıralım
80
+ const receivedHash = postData['HASH'];
81
+ if (!receivedHash) {
82
+ return { status: false, message: 'No hash found' };
127
83
  }
128
84
 
129
- // Verify IPN hash
130
- const params = { ...data };
131
- delete params.hash;
132
- delete params.HASH;
85
+ let hashString = '';
133
86
 
87
+ // 2Checkout, HASH parametresi hariç tüm parametreleri gönderdiği sırayla kullanır.
88
+ for (let key in postData) {
89
+ if (key !== 'HASH') {
90
+ const val = String(postData[key]);
91
+ hashString += val.length + val;
92
+ }
93
+ }
94
+
134
95
  const calculatedHash = crypto
135
- .createHmac('md5', this.secretKey)
136
- .update(JSON.stringify(params))
96
+ .createHmac('md5', this.secretWord)
97
+ .update(hashString)
137
98
  .digest('hex');
138
99
 
139
- if (hash !== calculatedHash) {
140
- return {
141
- status: false,
142
- error: {
143
- code: 401,
144
- message: 'Invalid hash'
145
- }
100
+ if (calculatedHash === receivedHash) {
101
+ const date = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
102
+ const responseHash = crypto.createHmac('md5', this.secretWord).update(postData.IPN_PID[0].length + postData.IPN_PID[0] + postData.IPN_PNAME[0].length + postData.IPN_PNAME[0] + postData.IPN_DATE.length + postData.IPN_DATE + date.length + date).digest('hex');
103
+
104
+ return {
105
+ status: true,
106
+ transactionId: postData.REFNO,
107
+ orderStatus: postData.ORDERSTATUS,
108
+ raw: postData
146
109
  };
110
+ } else {
111
+ console.error("Hash Mismatch. Calculated:", calculatedHash, "Received:", receivedHash);
112
+ return { status: false, message: 'Invalid hash signature' };
147
113
  }
148
114
 
149
- return {
150
- status: true,
151
- data: data
152
- };
153
115
  } catch (error) {
154
- return {
155
- status: false,
156
- error: {
157
- code: 500,
158
- message: error.message
159
- }
160
- };
116
+ return { status: false, message: error.message };
161
117
  }
162
118
  }
163
119
  }
164
120
 
165
- module.exports = TwoCheckoutClient;
121
+ module.exports = TwoCheckoutClient;
package/lib/cryptomus.js CHANGED
@@ -98,7 +98,7 @@ class Cryptomus {
98
98
  throw new Error("Cryptomus notification failed: invalid signature");
99
99
  }
100
100
 
101
- if (callbackData.status === 'paid') {
101
+ if (callbackData.status === 'paid' || callbackData.status === 'paid_over') {
102
102
  return {
103
103
  status: 'success',
104
104
  orderId: callbackData.order_id,
@@ -0,0 +1,121 @@
1
+ const crypto = require('crypto');
2
+
3
+ class TwoCheckoutClient {
4
+ constructor(config) {
5
+ const requiredFields = ['merchantCode', 'secretWord'];
6
+ for (let field of requiredFields) {
7
+ if (!config[field]) throw new Error(`Missing required field: ${field}`);
8
+ }
9
+
10
+ this.merchantCode = config.merchantCode;
11
+ this.secretWord = config.secretWord;
12
+ this.sandbox = config.sandbox || false;
13
+ }
14
+
15
+ generateHash(params, algo = 'md5') {
16
+ let signatureString = '';
17
+
18
+ Object.keys(params).forEach(key => {
19
+ const value = String(params[key]); // Değer string olmalı
20
+ if (value.length > 0) { // Boş değerler bazen hash'e katılmaz
21
+ signatureString += value.length + value;
22
+ }
23
+ });
24
+
25
+ return crypto
26
+ .createHmac(algo, this.secretWord)
27
+ .update(signatureString)
28
+ .digest('hex');
29
+ }
30
+
31
+ createPaymentUrl(options) {
32
+ const orderId = options.orderId || `ORDER-${Date.now()}`;
33
+
34
+ const params = {
35
+ 'merchant': this.merchantCode,
36
+ 'dynamic': '1', // Dinamik ürün modu
37
+ 'prod-name[]': options.name || 'Payment',
38
+ 'prod-type[]': 'GLOBAL_SERVICE', // Ürün tipi
39
+ 'prod-price[]': parseFloat(options.amount).toFixed(2),
40
+ 'prod-qty[]': '1',
41
+ 'currency': options.currency || 'USD',
42
+ 'return-type': 'redirect',
43
+ 'return-url': options.callbackUrl,
44
+ 'expiration': Math.floor(Date.now() / 1000) + 3600, // 1 saat geçerli
45
+ 'order-ext-ref': orderId,
46
+ 'customer-ref': options.customerId || '',
47
+ 'customer-email': options.email || ''
48
+ };
49
+
50
+ let signatureData = '';
51
+
52
+ const keysToSign = [
53
+ 'merchant', 'dynamic', 'prod-name[]', 'prod-type[]', 'prod-price[]',
54
+ 'prod-qty[]', 'currency', 'return-type', 'return-url', 'expiration',
55
+ 'order-ext-ref', 'customer-ref', 'customer-email'
56
+ ];
57
+
58
+ keysToSign.forEach(key => {
59
+ const val = String(params[key] || '');
60
+ if(val) signatureData += val.length + val;
61
+ });
62
+
63
+ const signature = crypto.createHmac('md5', this.secretWord).update(signatureData).digest('hex');
64
+ params['signature'] = signature;
65
+
66
+ // URL Parametrelerini oluştur
67
+ const queryParams = new URLSearchParams();
68
+ Object.keys(params).forEach(key => queryParams.append(key, params[key]));
69
+
70
+ return `https://secure.2checkout.com/order/checkout.php?${queryParams.toString()}`;
71
+ }
72
+
73
+ /**
74
+ * IPN (Callback) Doğrulama
75
+ * Bu kısım, sunucunuza gelen POST isteğini doğrular.
76
+ */
77
+ verifyCallback(postData) {
78
+ try {
79
+ // Gelen veriden HASH'i ayıralım
80
+ const receivedHash = postData['HASH'];
81
+ if (!receivedHash) {
82
+ return { status: false, message: 'No hash found' };
83
+ }
84
+
85
+ let hashString = '';
86
+
87
+ // 2Checkout, HASH parametresi hariç tüm parametreleri gönderdiği sırayla kullanır.
88
+ for (let key in postData) {
89
+ if (key !== 'HASH') {
90
+ const val = String(postData[key]);
91
+ hashString += val.length + val;
92
+ }
93
+ }
94
+
95
+ const calculatedHash = crypto
96
+ .createHmac('md5', this.secretWord)
97
+ .update(hashString)
98
+ .digest('hex');
99
+
100
+ if (calculatedHash === receivedHash) {
101
+ const date = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
102
+ const responseHash = crypto.createHmac('md5', this.secretWord).update(postData.IPN_PID[0].length + postData.IPN_PID[0] + postData.IPN_PNAME[0].length + postData.IPN_PNAME[0] + postData.IPN_DATE.length + postData.IPN_DATE + date.length + date).digest('hex');
103
+
104
+ return {
105
+ status: true,
106
+ transactionId: postData.REFNO,
107
+ orderStatus: postData.ORDERSTATUS,
108
+ raw: postData
109
+ };
110
+ } else {
111
+ console.error("Hash Mismatch. Calculated:", calculatedHash, "Received:", receivedHash);
112
+ return { status: false, message: 'Invalid hash signature' };
113
+ }
114
+
115
+ } catch (error) {
116
+ return { status: false, message: error.message };
117
+ }
118
+ }
119
+ }
120
+
121
+ module.exports = TwoCheckoutClient;
@@ -0,0 +1,193 @@
1
+ const crypto = require('crypto');
2
+ const axios = require('axios');
3
+
4
+ class Paynkolay {
5
+ /**
6
+ * @param {Object} config
7
+ * @param {string} config.sx - SX parametresi (Panelden alınır)
8
+ * @param {string} config.merchantSecretKey - Gizli Anahtar
9
+ * @param {string} config.merchantNo - Mağaza Numarası (Callback doğrulama için)
10
+ * @param {boolean} [config.isTest=true] - Test ortamı mı?
11
+ */
12
+ constructor(config) {
13
+ this.sx = config.sx;
14
+ this.merchantSecretKey = config.merchantSecretKey;
15
+ this.merchantNo = config.merchantNo;
16
+
17
+ // Postman'deki host bilgisine göre
18
+ this.baseUrl = config.isTest
19
+ ? 'https://paynkolaytest.nkolayislem.com.tr'
20
+ : 'https://paynkolay.nkolayislem.com.tr';
21
+ }
22
+
23
+ /**
24
+ * Link ile Ödeme Oluşturma (/Vpos/by-link-create)
25
+ * Postman'deki 'Link Oluştur' script'ine göre hazırlanmıştır.
26
+ */
27
+ async createPaymentLink(params) {
28
+ try {
29
+ const endpoint = '/Vpos/by-link-create';
30
+
31
+ // Zorunlu alanlar için varsayılan değerler ve kontroller
32
+ const rnd = params.rnd || Date.now().toString();
33
+ const clientRefCode = params.clientRefCode || ('REF-' + rnd);
34
+ const customerKey = params.customerKey || ""; // Kart saklama yoksa boş
35
+ const amount = parseFloat(params.amount).toFixed(2); // 1000.00 formatı
36
+
37
+ // 1. HASH OLUŞTURMA (Postman Script'ine göre)
38
+ // Sıralama: sx|clientRefCode|amount|successUrl|failUrl|rnd|customerKey|merchantSecretKey
39
+ const hashString = [
40
+ this.sx,
41
+ clientRefCode,
42
+ amount,
43
+ params.successUrl,
44
+ params.failUrl,
45
+ rnd,
46
+ customerKey,
47
+ this.merchantSecretKey
48
+ ].join("|");
49
+
50
+ const hashDatav2 = this.generateHash(hashString);
51
+
52
+ // 2. FORM DATA HAZIRLAMA
53
+ const data = new URLSearchParams();
54
+ data.append('sx', this.sx);
55
+ data.append('clientRefCode', clientRefCode);
56
+ data.append('amount', amount);
57
+ data.append('successUrl', params.successUrl);
58
+ data.append('failUrl', params.failUrl);
59
+ data.append('rnd', rnd);
60
+ data.append('hashDatav2', hashDatav2);
61
+ data.append('use3D', params.use3D || 'true');
62
+ data.append('currencyCode', params.currencyCode || '949'); // 949 = TL
63
+ data.append('transactionType', 'SALES');
64
+
65
+ // Opsiyonel (Link Detayları)
66
+ if (params.instalments) data.append('instalments', params.instalments);
67
+ if (params.second) data.append('second', params.second); // Link süresi (saniye)
68
+ if (params.cardHolderIP) data.append('cardHolderIP', params.cardHolderIP);
69
+
70
+ // Link Sayfası Görünen Bilgiler
71
+ data.append('detail', 'true');
72
+ data.append('inputNamesurname', params.inputNamesurname || '');
73
+ data.append('inputDescription', params.inputDescription || '');
74
+ data.append('inputEmail', params.inputEmail || '');
75
+ data.append('inputAddress', params.inputAddress || '');
76
+ data.append('inputTckn', params.inputTckn || '');
77
+ data.append('inputPhone', params.inputPhone || '');
78
+
79
+ // 3. İSTEK GÖNDERME
80
+ const response = await axios.post(`${this.baseUrl}${endpoint}`, data, {
81
+ headers: {
82
+ 'Content-Type': 'application/x-www-form-urlencoded'
83
+ }
84
+ });
85
+
86
+ return response.data;
87
+
88
+ } catch (error) {
89
+ throw new Error(`Paynkolay Link Oluşturma Hatası: ${error.message}`);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Link Silme (/Vpos/by-link-url-remove)
95
+ */
96
+ async removePaymentLink(qParam) {
97
+ try {
98
+ const endpoint = '/Vpos/by-link-url-remove';
99
+
100
+ // HASH OLUŞTURMA (Link Sil Script'ine göre)
101
+ // Sıralama: sx|q|merchantSecretKey
102
+ const hashString = [
103
+ this.sx,
104
+ qParam, // Bu değer genelde referans kodudur
105
+ this.merchantSecretKey
106
+ ].join("|");
107
+
108
+ const hashDatav2 = this.generateHash(hashString);
109
+
110
+ const data = new URLSearchParams();
111
+ data.append('sx', this.sx);
112
+ data.append('q', qParam);
113
+ data.append('hashDatav2', hashDatav2);
114
+
115
+ const response = await axios.post(`${this.baseUrl}${endpoint}`, data, {
116
+ headers: {
117
+ 'Content-Type': 'application/x-www-form-urlencoded'
118
+ }
119
+ });
120
+
121
+ return response.data;
122
+
123
+ } catch (error) {
124
+ throw new Error(`Paynkolay Link Silme Hatası: ${error.message}`);
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Callback (Bildirim) Doğrulama
130
+ * Ödeme sonrası Paynkolay'ın successUrl'e gönderdiği veriyi doğrular.
131
+ */
132
+ handleCallback(params) {
133
+ const {
134
+ RESPONSE_CODE,
135
+ MERCHANT_NO,
136
+ REFERENCE_CODE,
137
+ AUTH_CODE,
138
+ USE_3D,
139
+ RND,
140
+ INSTALLMENT,
141
+ AUTHORIZATION_AMOUNT,
142
+ CURRENCY_CODE,
143
+ hashDataV2
144
+ } = params;
145
+
146
+ // 1. İşlem Başarılı mı kontrolü
147
+ if (RESPONSE_CODE !== "2" && RESPONSE_CODE !== 2) {
148
+ return { status: false, message: 'İşlem başarısız (RESPONSE_CODE != 2)' };
149
+ }
150
+
151
+ // 2. Hash Hesaplama (Dokümantasyondaki 'Response Hash Testi' sırası)
152
+ // MERCHANT_NO | REFERENCE_CODE | AUTH_CODE | RESPONSE_CODE | USE_3D | RND | INSTALLMENT | AUTHORIZATION_AMOUNT | CURRENCY_CODE | MERCHANT_SECRET_KEY
153
+
154
+ const hashString = [
155
+ MERCHANT_NO,
156
+ REFERENCE_CODE,
157
+ AUTH_CODE,
158
+ RESPONSE_CODE,
159
+ USE_3D, // Gelen değer string "true" veya "false" olabilir, olduğu gibi kullanılmalı
160
+ RND,
161
+ INSTALLMENT,
162
+ AUTHORIZATION_AMOUNT,
163
+ CURRENCY_CODE,
164
+ this.merchantSecretKey
165
+ ].join("|");
166
+
167
+ const calculatedHash = this.generateHash(hashString);
168
+
169
+ if (calculatedHash === hashDataV2) {
170
+ return {
171
+ status: 'success',
172
+ message: 'Doğrulama Başarılı',
173
+ amount: AUTHORIZATION_AMOUNT,
174
+ orderId: params.CLIENT_REFERENCE_CODE || REFERENCE_CODE
175
+ };
176
+ } else {
177
+ console.error("Hash String (Local):", hashString);
178
+ console.error("Calculated:", calculatedHash);
179
+ console.error("Received:", hashDataV2);
180
+ return { status: false, message: 'Hash uyuşmazlığı (Güvenlik Hatası)' };
181
+ }
182
+ }
183
+
184
+ /**
185
+ * SHA-512 Base64 Helper
186
+ */
187
+ generateHash(str) {
188
+ // UTF-8 encoding önemlidir (CryptoJS.SHA512 davranışı)
189
+ return crypto.createHash('sha512').update(str, 'utf8').digest('base64');
190
+ }
191
+ }
192
+
193
+ module.exports = Paynkolay;
package/lib/paytr.js CHANGED
@@ -98,63 +98,91 @@ class PayTR {
98
98
  }
99
99
  }
100
100
 
101
- async createDirectPayment(params) {
101
+ // Direkt API için Otomatik Submit Eden HTML Formu Üretir
102
+ createDirectPayment(params) {
102
103
  try {
103
- const userBasket = JSON.stringify(params.userBasket);
104
- const userIp = params.userIp;
105
- const merchantOid = params.merchantOid;
104
+ // 1. Gerekli Parametrelerin Hazırlanması
105
+ const merchant_id = this.merchantId;
106
+ const user_ip = params.userIp;
107
+ const merchant_oid = params.merchantOid;
106
108
  const email = params.email;
107
- const paymentAmount = params.paymentAmount.toString();
109
+ const payment_amount = params.paymentAmount.toString();
110
+ const payment_type = 'card';
111
+ const installment_count = params.installmentCount || '0';
108
112
  const currency = params.currency || 'TL';
109
- const installmentCount = params.installmentCount || '0';
110
- const paymentType = 'card';
111
- const non3D = params.non3D ? params.non3D : '0';
112
-
113
- const rawString = `${this.merchantId}${userIp}${merchantOid}${email}${paymentAmount}${paymentType}${installmentCount}${currency}${this.testMode}${non3D}`;
114
- const paytrToken = this.generateToken(rawString);
113
+ const test_mode = this.testMode;
114
+ const non_3d = params.non3D || '0';
115
+
116
+ // Sepet verisi JSON string olmalı
117
+ const user_basket = JSON.stringify(params.userBasket);
118
+
119
+ // 2. Token Oluşturma (Sıralama: merchant_id + user_ip + merchant_oid + email + payment_amount + payment_type + installment_count + currency + test_mode + non_3d)
120
+ const rawString = `${merchant_id}${user_ip}${merchant_oid}${email}${payment_amount}${payment_type}${installment_count}${currency}${test_mode}${non_3d}`;
121
+ const paytr_token = this.generateToken(rawString);
122
+
123
+ // 3. Form Verilerinin Hazırlanması
124
+ const formFields = {
125
+ merchant_id,
126
+ user_ip,
127
+ merchant_oid,
128
+ email,
129
+ payment_type,
130
+ payment_amount: Number(payment_amount),
131
+ currency,
132
+ test_mode,
133
+ non_3d,
134
+ merchant_ok_url: params.merchantOkUrl,
135
+ merchant_fail_url: params.merchantFailUrl,
136
+ user_name: params.userName,
137
+ user_address: params.userAddress,
138
+ user_phone: params.userPhone,
139
+ user_basket,
140
+ debug_on: this.debugOn,
141
+ client_lang: params.clientLang || 'tr',
142
+ paytr_token,
143
+ installment_count: installment_count,
144
+ card_type: params.cardType,
145
+ non3d_test_failed: params.non3dTestFailed || '0',
146
+ no_installment: params.noInstallment || '0',
147
+ max_installment: params.maxInstallment || '0',
148
+ lang: params.lang || 'tr',
149
+ // Kart Bilgileri (Form otomatik post edileceği için buraya eklenmeli)
150
+ cc_owner: params.cardOwner,
151
+ card_number: params.cardNumber,
152
+ expiry_month: params.cardExpireMonth,
153
+ expiry_year: params.cardExpireYear,
154
+ cvv: params.cardCvc
155
+ };
115
156
 
116
- const formData = new URLSearchParams();
117
- formData.append('merchant_id', this.merchantId);
118
- formData.append('user_ip', userIp);
119
- formData.append('merchant_oid', merchantOid);
120
- formData.append('email', email);
121
- formData.append('payment_amount', paymentAmount);
122
- formData.append('paytr_token', paytrToken);
123
- formData.append('user_basket', userBasket);
124
- formData.append('debug_on', this.debugOn);
125
- formData.append('test_mode', this.testMode);
126
- formData.append('payment_type', paymentType);
127
- formData.append('installment_count', installmentCount);
128
- formData.append('currency', currency);
129
- formData.append('non_3d', non3D);
130
- formData.append('cc_owner', params.cardOwner);
131
- formData.append('card_number', params.cardNumber);
132
- formData.append('expiry_month', params.cardExpireMonth);
133
- formData.append('expiry_year', params.cardExpireYear);
134
- formData.append('cvc', params.cardCvc);
135
- formData.append('user_name', params.userName);
136
- formData.append('user_address', params.userAddress);
137
- formData.append('user_phone', params.userPhone);
138
- formData.append('merchant_ok_url', params.merchantOkUrl);
139
- formData.append('merchant_fail_url', params.merchantFailUrl);
157
+ // 4. HTML Formunun Oluşturulması
158
+ // Form oluşturulur, inputlar gizlenir ve script ile otomatik submit edilir.
159
+ let html = `
160
+ <!DOCTYPE html>
161
+ <html>
162
+ <head><title>Redirecting to Payment...</title></head>
163
+ <body>
164
+ <form id="paytr_auto_form" action="https://www.paytr.com/odeme" method="post">
165
+ `;
166
+
167
+ for (const [key, value] of Object.entries(formFields)) {
168
+ // Değer undefined veya null ise boş string gönderelim, güvenlik için escape yapalım
169
+ const safeValue = value ? String(value).replace(/"/g, '&quot;') : '';
170
+ html += `<input type="hidden" name="${key}" value="${safeValue}">\n`;
171
+ }
140
172
 
141
- const response = await axios({
142
- method: 'POST',
143
- url: 'https://www.paytr.com/odeme/api/direct',
144
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
145
- data: formData
146
- });
173
+ html += `
174
+ </form>
175
+ <script>
176
+ document.getElementById("paytr_auto_form").submit();
177
+ </script>
178
+ </body>
179
+ </html>
180
+ `;
147
181
 
148
- const result = response.data;
182
+ return html;
149
183
 
150
- if (result.status === 'success') {
151
- return result;
152
- } else {
153
- throw new Error(result.reason || 'PayTR Direct API Hatası');
154
- }
155
184
  } catch (error) {
156
- if (error.response) throw new Error(`PayTR API Error: ${error.response.data.reason || error.response.statusText}`);
157
- throw error;
185
+ throw new Error(`PayTR HTML oluşturma hatası: ${error.message}`);
158
186
  }
159
187
  }
160
188
 
package/lib/paywant.js ADDED
@@ -0,0 +1,137 @@
1
+ const crypto = require('crypto');
2
+ const axios = require('axios');
3
+
4
+ class Paywant {
5
+ /**
6
+ * Paywant Kurucu Metodu
7
+ * @param {Object} config
8
+ * @param {string} config.apiKey - Paywant API Anahtarı
9
+ * @param {string} config.apiSecret - Paywant Gizli Anahtar
10
+ */
11
+ constructor(config) {
12
+ if (!config.apiKey || !config.apiSecret) {
13
+ throw new Error('Paywant: apiKey ve apiSecret zorunludur.');
14
+ }
15
+ this.apiKey = config.apiKey;
16
+ this.apiSecret = config.apiSecret;
17
+ this.baseUrl = 'https://secure.paywant.com';
18
+ }
19
+
20
+ /**
21
+ * Ödeme Linki Oluşturma (/payment/token)
22
+ * @param {Object} params
23
+ */
24
+ async createPayment(params) {
25
+ try {
26
+ // Zorunlu alan kontrolü
27
+ const required = ['userID', 'userEmail', 'userAccountName', 'userIp', 'productData'];
28
+ for (const field of required) {
29
+ if (!params[field]) throw new Error(`Paywant: ${field} alanı zorunludur.`);
30
+ }
31
+
32
+ // 1. Hash Oluşturma (Dokümantasyona Göre)
33
+ // Sıralama: userName|userEmail|userID + ApiKey
34
+ const hashString = `${params.userAccountName}|${params.userEmail}|${params.userID}${this.apiKey}`;
35
+ const hash = this.generateHash(hashString);
36
+
37
+ // 2. API'ye Gönderilecek Veri
38
+ const payload = {
39
+ apiKey: this.apiKey,
40
+ userAccountName: params.userAccountName,
41
+ userEmail: params.userEmail,
42
+ userID: parseInt(params.userID), // int64 olmalı
43
+ userIPAddress: params.userIp,
44
+ hash: hash,
45
+ proApi: true, // Ürün verisini biz gönderiyoruz
46
+ productData: {
47
+ name: params.productData.name, // Ürün adı
48
+ amount: parseFloat(params.productData.amount), // Tutar (100.00 gibi)
49
+ extraData: params.productData.extraData || '', // Sipariş No vb.
50
+ paymentChannel: params.productData.paymentChannel || '0', // 0 = Hepsi
51
+ commissionType: params.productData.commissionType || '1' // 1: Müşteri, 2: Mağaza öder
52
+ }
53
+ };
54
+
55
+ // 3. İsteği Gönder
56
+ const response = await axios.post(`${this.baseUrl}/payment/token`, payload, {
57
+ headers: {
58
+ 'Content-Type': 'application/json'
59
+ }
60
+ });
61
+
62
+ const result = response.data;
63
+
64
+ if (result.status === true) {
65
+ // Başarılı ise ödeme linkini döndür
66
+ return {
67
+ status: 'success',
68
+ link: result.message // Örn: https://secure.paywant.com/common/TOKEN
69
+ };
70
+ } else {
71
+ // Paywant'tan dönen hata
72
+ throw new Error(result.message || 'Paywant ödeme oluşturulamadı.');
73
+ }
74
+
75
+ } catch (error) {
76
+ if (error.response) {
77
+ throw new Error(`Paywant API Hatası: ${JSON.stringify(error.response.data)}`);
78
+ }
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * IPN (Bildirim) Doğrulama
85
+ * Paywant'tan gelen POST isteğini doğrular.
86
+ * @param {Object} reqBody - Express request body
87
+ */
88
+ handleCallback(reqBody) {
89
+ const {
90
+ transactionID,
91
+ extraData,
92
+ userID,
93
+ userAccountName,
94
+ status,
95
+ paymentChannel,
96
+ paymentTotal,
97
+ netProfit,
98
+ hash
99
+ } = reqBody;
100
+
101
+ if (!hash) throw new Error('Hash parametresi eksik.');
102
+
103
+ // Dokümantasyondaki IPN Hash Formülü:
104
+ // transactionID|extraData|userID|userAccountName|status|paymentChannel|paymentTotal|netProfit + ApiKey
105
+ const hashString = `${transactionID}|${extraData}|${userID}|${userAccountName}|${status}|${paymentChannel}|${paymentTotal}|${netProfit}${this.apiKey}`;
106
+
107
+ const generatedHash = this.generateHash(hashString);
108
+
109
+ if (hash === generatedHash) {
110
+ // Status 100 = Başarılı
111
+ if (parseInt(status) === 100) {
112
+ return {
113
+ status: 'success',
114
+ orderId: extraData, // Genelde sipariş no extraData'ya konur
115
+ transactionId: transactionID,
116
+ amount: parseFloat(paymentTotal),
117
+ netAmount: parseFloat(netProfit)
118
+ };
119
+ } else {
120
+ return { success: false, message: 'Ödeme başarısız veya beklemede.' };
121
+ }
122
+ } else {
123
+ throw new Error('Hash uyuşmazlığı (Bad Hash)');
124
+ }
125
+ }
126
+
127
+ /**
128
+ * HMAC-SHA256 Hash Üretici (Base64)
129
+ */
130
+ generateHash(data) {
131
+ return crypto.createHmac('sha256', this.apiSecret)
132
+ .update(data)
133
+ .digest('base64');
134
+ }
135
+ }
136
+
137
+ module.exports = Paywant;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quickpos",
3
- "version": "1.0.916",
3
+ "version": "1.0.918",
4
4
  "main": "app.js",
5
5
  "scripts": {
6
6
  "test": "node test.js"
package/readme.md CHANGED
@@ -75,7 +75,11 @@
75
75
  | **2Checkout** | 🌐 Global | ✅ Active |
76
76
  | **İyzico** | 🇹🇷 Turkey | ✅ Active |
77
77
  | **PayTR** | 🇹🇷 Turkey | ✅ Active |
78
+ | **PayTR EFT** | 🇹🇷 Turkey | ✅ Active |
79
+ | **PayWant** | 🇹🇷 Turkey | ✅ Active |
80
+ | **PaynKolay** | 🇹🇷 Turkey | ✅ Active |
78
81
  | **Shopier** | 🇹🇷 Turkey | ✅ Active |
82
+ | **Shopier Card** | 🇹🇷 Turkey | ✅ Active |
79
83
  | **Papara** | 🇹🇷 Turkey | ✅ Active |
80
84
  | **EsnekPos** | 🇹🇷 Turkey | ✅ Active |
81
85
  | **Paydisini** | 🇹🇷 Turkey | ✅ Active |