quickpos
Version:
<div align="center"> <h1>💳 QuickPos 🚀</h1> <p><strong>A powerful, multi-gateway payment integration module for Node.js</strong></p> <p>Seamlessly integrate with 50+ payment providers worldwide</p>
316 lines (273 loc) • 11.7 kB
JavaScript
const crypto = require('crypto');
const axios = require('axios');
class PayTR {
constructor(config) {
this.config = config || {};
const requiredFields = ['merchantId', 'merchantKey', 'merchantSalt'];
for (let field of requiredFields) {
if (!this.config[field]) throw new Error(`Missing required field: ${field}`);
}
this.merchantId = this.config.merchantId;
this.merchantKey = this.config.merchantKey;
this.merchantSalt = this.config.merchantSalt;
this.testMode = this.config.testMode ? '1' : '0';
this.debugOn = this.config.debugOn ? '1' : '0';
this.iframe = {
createToken: this.createIframeToken.bind(this)
};
this.direct = {
createPayment: this.createDirectPayment.bind(this)
};
this.link = {
createPayment: this.createLinkPayment.bind(this)
};
}
async createLinkPayment(paymentDetails) {
try {
const requiredFields = ['name', 'amount', 'currency', 'maxInstallment', 'expiry_date'];
for (let field of requiredFields) {
if (!paymentDetails[field]) throw new Error(`Missing required data: ${field}`);
}
const price = Math.round(paymentDetails.amount * 100).toString();
const linkType = paymentDetails.linkType || 'product';
const currency = paymentDetails.currency || 'TL';
const lang = paymentDetails.lang || 'tr';
const minCount = paymentDetails.min_count || '1';
const required = paymentDetails.name + price + currency + paymentDetails.maxInstallment + linkType + lang + minCount;
const paytrToken = this.generateToken(required);
const formData = {
merchant_id: this.merchantId,
name: paymentDetails.name,
price: price,
currency: currency,
max_installment: paymentDetails.maxInstallment,
link_type: linkType,
lang: lang,
min_count: minCount,
paytr_token: paytrToken,
expiry_date: paymentDetails.expiry_date,
get_qr: '1',
max_count: Number(paymentDetails.max_count) || '1',
debug_on: this.debugOn
};
const optionalData = ['email', 'callback_link', 'callback_id'];
for (let data of optionalData) {
if(paymentDetails[data]) formData[data] = paymentDetails[data];
}
const response = await axios({
method: 'POST',
url: 'https://www.paytr.com/odeme/api/link/create',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: new URLSearchParams(formData).toString()
});
const responseData = response.data;
if (responseData.status === 'success') {
return {
status: 'success',
data: {
transactionId: responseData.id,
url: responseData.link,
id: responseData.id,
qr: responseData.base64_qr
}
};
} else {
throw new Error(responseData.reason || 'Unknown error occurred');
}
} catch (error) {
if (error.response) {
throw new Error(`PayTR API error: ${error.response.data.reason || error.response.statusText}`);
}
throw error;
}
}
// Direkt API için Otomatik Submit Eden HTML Formu Üretir
createDirectPayment(params) {
try {
// 1. Gerekli Parametrelerin Hazırlanması
const merchant_id = this.merchantId;
const user_ip = params.userIp;
const merchant_oid = params.merchantOid;
const email = params.email;
const payment_amount = params.paymentAmount.toString();
const payment_type = 'card';
const installment_count = params.installmentCount || '0';
const currency = params.currency || 'TL';
const test_mode = this.testMode;
const non_3d = params.non3D || '0';
// Sepet verisi JSON string olmalı
const user_basket = JSON.stringify(params.userBasket);
// 2. Token Oluşturma (Sıralama: merchant_id + user_ip + merchant_oid + email + payment_amount + payment_type + installment_count + currency + test_mode + non_3d)
const rawString = `${merchant_id}${user_ip}${merchant_oid}${email}${payment_amount}${payment_type}${installment_count}${currency}${test_mode}${non_3d}`;
const paytr_token = this.generateToken(rawString);
// 3. Form Verilerinin Hazırlanması
const formFields = {
merchant_id,
user_ip,
merchant_oid,
email,
payment_type,
payment_amount: Number(payment_amount),
currency,
test_mode,
non_3d,
merchant_ok_url: params.merchantOkUrl,
merchant_fail_url: params.merchantFailUrl,
user_name: params.userName,
user_address: params.userAddress,
user_phone: params.userPhone,
user_basket,
debug_on: this.debugOn,
client_lang: params.clientLang || 'tr',
paytr_token,
installment_count: installment_count,
card_type: params.cardType,
non3d_test_failed: params.non3dTestFailed || '0',
no_installment: params.noInstallment || '0',
max_installment: params.maxInstallment || '0',
lang: params.lang || 'tr',
// Kart Bilgileri (Form otomatik post edileceği için buraya eklenmeli)
cc_owner: params.cardOwner,
card_number: params.cardNumber,
expiry_month: params.cardExpireMonth,
expiry_year: params.cardExpireYear,
cvv: params.cardCvc
};
// 4. HTML Formunun Oluşturulması
// Form oluşturulur, inputlar gizlenir ve script ile otomatik submit edilir.
let html = `
<!DOCTYPE html>
<html>
<head><title>Redirecting to Payment...</title></head>
<body>
<form id="paytr_auto_form" action="https://www.paytr.com/odeme" method="post">
`;
for (const [key, value] of Object.entries(formFields)) {
// Değer undefined veya null ise boş string gönderelim, güvenlik için escape yapalım
const safeValue = value ? String(value).replace(/"/g, '"') : '';
html += `<input type="hidden" name="${key}" value="${safeValue}">\n`;
}
html += `
</form>
<script>
document.getElementById("paytr_auto_form").submit();
</script>
</body>
</html>
`;
return html;
} catch (error) {
throw new Error(`PayTR HTML oluşturma hatası: ${error.message}`);
}
}
async createIframeToken(params) {
try {
const userBasket = JSON.stringify(params.userBasket);
const userIp = params.userIp;
const merchantOid = params.merchantOid;
const email = params.email;
const paymentAmount = params.paymentAmount.toString();
const currency = params.currency || 'TL';
const noInstallment = params.noInstallment ? '1' : '0';
const maxInstallment = params.maxInstallment || '0';
const paymentType = params.paymentType || 'card';
let rawString;
// DÜZELTME BURADA YAPILDI:
if (paymentType === 'eft') {
// Python örneğindeki EFT Hash yapısı:
// merchant_id + user_ip + merchant_oid + email + payment_amount + payment_type + test_mode
rawString = `${this.merchantId}${userIp}${merchantOid}${email}${paymentAmount}${paymentType}${this.testMode}`;
} else {
// Standart Kredi Kartı Hash yapısı:
// merchant_id + user_ip + merchant_oid + email + payment_amount + user_basket + no_installment + max_installment + currency + test_mode
rawString = `${this.merchantId}${userIp}${merchantOid}${email}${paymentAmount}${userBasket}${noInstallment}${maxInstallment}${currency}${this.testMode}`;
}
// generateToken fonksiyonu sonuna merchantSalt ekleyip şifreler
const paytrToken = this.generateToken(rawString);
const formData = new URLSearchParams();
formData.append('merchant_id', this.merchantId);
formData.append('user_ip', userIp);
formData.append('merchant_oid', merchantOid);
formData.append('email', email);
formData.append('payment_amount', paymentAmount);
formData.append('paytr_token', paytrToken);
formData.append('debug_on', this.debugOn);
formData.append('test_mode', this.testMode);
formData.append('payment_type', paymentType);
// timeout_limit parametresi (Python kodunda vardı, ekleyelim)
formData.append('timeout_limit', params.timeoutLimit || '30');
// EFT değilse diğer parametreleri de ekleyelim
if (paymentType !== 'eft') {
formData.append('user_basket', userBasket);
formData.append('no_installment', noInstallment);
formData.append('max_installment', maxInstallment);
formData.append('user_name', params.userName);
formData.append('user_address', params.userAddress);
formData.append('user_phone', params.userPhone);
formData.append('merchant_ok_url', params.merchantOkUrl);
formData.append('merchant_fail_url', params.merchantFailUrl);
formData.append('currency', currency);
if (params.lang) formData.append('lang', params.lang);
} else {
// EFT ise banka parametresi varsa ekle
if (params.bank) formData.append('bank', params.bank);
}
const response = await axios({
method: 'POST',
url: 'https://www.paytr.com/odeme/api/get-token',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: formData
});
const result = response.data;
if (result.status === 'success') {
return { status: 'success', token: result.token };
} else {
// Hata detayını daha net görelim
throw new Error(result.reason || 'PayTR Token alma hatası');
}
} catch (error) {
if (error.response) throw new Error(`PayTR API Error: ${error.response.data.reason || error.response.statusText}`);
throw error;
}
}
handleCallback(callbackData) {
let { merchant_oid, status, total_amount, hash, type = 'iframe', callback_id=false } = callbackData;
if (!hash) throw new Error('Hash param missing');
let token;
if (callback_id) type = "link";
if (type == 'iframe') {
token = `${merchant_oid}${this.merchantSalt}${status}${total_amount}`;
} else if (type == 'link') {
token = `${callback_id}${merchant_oid}${this.merchantSalt}${status}${total_amount}`;
} else if (type == 'eft') {
token = `${merchant_oid}${this.merchantSalt}${status}${total_amount}`;
}
const generatedHash = crypto
.createHmac('sha256', this.merchantKey)
.update(token)
.digest('base64');
if (generatedHash !== hash) {
throw new Error("PAYTR notification failed: bad hash type: " + type);
}
if (status === 'success') {
return {
status: 'success',
orderId: callback_id || merchant_oid,
merchant_oid: merchant_oid,
amount: parseFloat(total_amount) / 100,
currency: callbackData.currency,
paymentType: callbackData.payment_type
};
} else {
throw new Error("Payment failed");
}
}
generateToken(data) {
return crypto.createHmac('sha256', this.merchantKey)
.update(data + this.merchantSalt)
.digest('base64');
}
}
module.exports = PayTR;