|
const express = require('express'); |
|
const crypto = require('crypto'); |
|
const bodyParser = require('body-parser'); |
|
const nodemailer = require('nodemailer'); |
|
|
|
const app = express(); |
|
app.use(bodyParser.json()); |
|
|
|
|
|
const orderTracking = new Map(); |
|
|
|
|
|
const transporter = nodemailer.createTransport({ |
|
host: 'smtp.gmail.com', |
|
port: 465, |
|
secure: true, |
|
auth: { |
|
user: 'skelltechofficial@gmail.com', |
|
pass: 'rpymwovddmzfitdp' |
|
} |
|
}); |
|
|
|
|
|
async function sendSuccessEmail(email, orderId, amount, redemptionCode, retryCount = 0) { |
|
const maxRetries = 5; |
|
const retryDelay = 3000; |
|
|
|
try { |
|
console.log(`Attempt ${retryCount + 1} to send email to:`, email); |
|
console.log('Email details:', { |
|
orderId, |
|
amount, |
|
redemptionCode |
|
}); |
|
|
|
|
|
await transporter.verify(); |
|
console.log('SMTP connection verified successfully'); |
|
|
|
const result = await transporter.sendMail({ |
|
from: '"TypeGPT" <noreply@typegpt.net>', |
|
to: email, |
|
subject: 'Your Recharge Code Is Ready! 🎉', |
|
text: `Your payment of $${amount} for order ${orderId} has been processed successfully.\nRedemption Code: ${redemptionCode}`, |
|
html: ` |
|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<style> |
|
body { |
|
font-family: Arial, sans-serif; |
|
line-height: 1.6; |
|
color: #333333; |
|
margin: 0; |
|
padding: 0; |
|
} |
|
.container { |
|
max-width: 600px; |
|
margin: 0 auto; |
|
padding: 20px; |
|
} |
|
.header { |
|
background-color: #f8f9fa; |
|
padding: 20px; |
|
text-align: center; |
|
border-radius: 8px 8px 0 0; |
|
} |
|
.content { |
|
background-color: #ffffff; |
|
padding: 30px; |
|
border-radius: 0 0 8px 8px; |
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
|
} |
|
.code-box { |
|
background-color: #f8f9fa; |
|
border: 2px dashed #dee2e6; |
|
border-radius: 6px; |
|
padding: 15px; |
|
margin: 20px 0; |
|
text-align: center; |
|
} |
|
.code { |
|
font-family: monospace; |
|
font-size: 24px; |
|
color: #28a745; |
|
font-weight: bold; |
|
letter-spacing: 2px; |
|
} |
|
.details { |
|
margin: 20px 0; |
|
padding: 15px; |
|
background-color: #f8f9fa; |
|
border-radius: 6px; |
|
} |
|
.footer { |
|
text-align: center; |
|
margin-top: 20px; |
|
font-size: 12px; |
|
color: #6c757d; |
|
} |
|
.button { |
|
display: inline-block; |
|
padding: 10px 20px; |
|
background-color: #28a745; |
|
color: #ffffff; |
|
text-decoration: none; |
|
border-radius: 5px; |
|
margin: 20px 0; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<div class="header"> |
|
<h1 style="color: #28a745; margin: 0;">Payment Successful! 🎉</h1> |
|
</div> |
|
<div class="content"> |
|
<p>Dear Customer,</p> |
|
<p>Thank you for your purchase! Your payment has been successfully processed.</p> |
|
|
|
<div class="code-box"> |
|
<p style="margin: 0; font-weight: bold;">Your Redemption Code:</p> |
|
<div class="code">${redemptionCode}</div> |
|
</div> |
|
|
|
<div class="details"> |
|
<h3 style="margin-top: 0;">Transaction Details:</h3> |
|
<p><strong>Order ID:</strong> ${orderId}</p> |
|
<p><strong>Amount Paid:</strong> $${amount}</p> |
|
<p><strong>API Credits:</strong> $${amount * 25}</p> |
|
</div> |
|
|
|
<p><strong>Important:</strong></p> |
|
<ul> |
|
<li>Keep this code safe - it's your access to the API credits</li> |
|
<li>The code can only be used once</li> |
|
<li>If you have any issues, please contact our support team</li> |
|
</ul> |
|
|
|
<div style="text-align: center;"> |
|
<a href="https://fast.typegpt.net/topup" class="button">Top Up Balance</a> |
|
</div> |
|
|
|
<div class="footer"> |
|
<p>This is an automated message, please do not reply to this email.</p> |
|
<p>© ${new Date().getFullYear()} NiansuhAI. All rights reserved.</p> |
|
</div> |
|
</div> |
|
</div> |
|
</body> |
|
</html> |
|
` |
|
}); |
|
|
|
console.log('Email sent successfully:', result); |
|
return result; |
|
} catch (error) { |
|
console.error('Failed to send email:', error); |
|
console.error('Error details:', { |
|
code: error.code, |
|
command: error.command, |
|
response: error.response |
|
}); |
|
|
|
if (retryCount < maxRetries) { |
|
console.log(`Retrying in ${retryDelay/1000} seconds... (Attempt ${retryCount + 2}/${maxRetries + 1})`); |
|
await new Promise(resolve => setTimeout(resolve, retryDelay)); |
|
return sendSuccessEmail(email, orderId, amount, redemptionCode, retryCount + 1); |
|
} |
|
|
|
throw new Error(`Failed to send email after ${maxRetries + 1} attempts`); |
|
} |
|
} |
|
|
|
|
|
function generateOrderId() { |
|
const randomNum = Math.random().toString().slice(2, 18); |
|
return `hrmn-${randomNum}`; |
|
} |
|
|
|
|
|
const MERCHANT_KEY = '3H896K-W3LZEF-H4H957-ZDF5S4'; |
|
|
|
|
|
async function createRedemptionCode(orderId, amount) { |
|
console.log('Creating redemption code...'); |
|
const quota = 25 * amount * 500000; |
|
|
|
try { |
|
const response = await fetch('https://niansuh-redeem.hf.space/api/create', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
'x-api-key': 'sk-niansuhaiburewala521' |
|
}, |
|
body: JSON.stringify({ |
|
username: 'admin', |
|
name: Math.random().toString(36).substring(2, 12), |
|
quota: quota, |
|
count: 1 |
|
}) |
|
}); |
|
|
|
const result = await response.json(); |
|
console.log('Redemption code created'); |
|
if (result.success && result.data?.data?.[0]) { |
|
return result.data.data[0]; |
|
} else { |
|
throw new Error('Invalid response format from redemption code API'); |
|
} |
|
} catch (error) { |
|
console.log('Failed to create redemption code:', error); |
|
throw error; |
|
} |
|
} |
|
|
|
|
|
const webhookHandler = { |
|
validateHmacSignature(postData, hmacHeader, type) { |
|
const apiKey = MERCHANT_KEY; |
|
|
|
const calculatedHmac = crypto |
|
.createHmac('sha512', apiKey) |
|
.update(postData) |
|
.digest('hex'); |
|
|
|
return calculatedHmac === hmacHeader; |
|
}, |
|
|
|
handleWaitingPayment(data) { |
|
console.log('Payment waiting'); |
|
}, |
|
|
|
handleConfirmingPayment(data) { |
|
console.log('Payment confirming'); |
|
}, |
|
|
|
async handlePaidPayment(data) { |
|
console.log('Payment received for order:', data.orderId); |
|
const orderData = orderTracking.get(data.orderId); |
|
if (orderData) { |
|
const { email, amount } = orderData; |
|
console.log(`Processing payment for email: ${email}, amount: $${amount}`); |
|
|
|
try { |
|
|
|
const redemptionCode = await createRedemptionCode(data.orderId, amount); |
|
console.log(`Generated redemption code for order ${data.orderId}: ${redemptionCode}`); |
|
|
|
try { |
|
await sendSuccessEmail(email, data.orderId, amount, redemptionCode); |
|
console.log(`Successfully sent email to ${email} with code: ${redemptionCode}`); |
|
} catch (emailError) { |
|
|
|
console.error(`⚠️ FAILED EMAIL SEND - IMPORTANT DATA:`); |
|
console.error(`Email: ${email}`); |
|
console.error(`Order ID: ${data.orderId}`); |
|
console.error(`Amount: $${amount}`); |
|
console.error(`Redemption Code: ${redemptionCode}`); |
|
console.error(`Error:`, emailError); |
|
} |
|
|
|
console.log('Payment process completed'); |
|
} catch (error) { |
|
console.error(`❌ CRITICAL ERROR processing order ${data.orderId}:`, error); |
|
} finally { |
|
|
|
orderTracking.delete(data.orderId); |
|
} |
|
} else { |
|
console.error(`❌ No order data found for order ID: ${data.orderId}`); |
|
} |
|
}, |
|
|
|
handleExpiredPayment(data) { |
|
console.log('Payment expired'); |
|
}, |
|
|
|
handleCompletePayout(data) { |
|
console.log('Payout complete'); |
|
} |
|
}; |
|
|
|
|
|
app.get('/', (req, res) => { |
|
const html = ` |
|
<html> |
|
<head> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<script src="https://registry.npmmirror.com/vue/3.3.11/files/dist/vue.global.js"></script> |
|
<meta charset="UTF-8"> |
|
</head> |
|
<body class="flex items-center justify-center min-h-screen bg-gray-100"> |
|
<div id="app" class="bg-white p-8 rounded-lg shadow-md w-80"> |
|
<h1 class="text-2xl font-bold mb-2">Recharge Balance</h1> |
|
<p class="text-gray-500 mb-4">Add funds to your account</p> |
|
|
|
<div v-if="amount" class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mb-4"> |
|
<p>You will receive \${{ amount * 25 }} in API credits</p> |
|
</div> |
|
|
|
<div v-if="showWarning" class="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4 mb-4"> |
|
<p>A redemption code will be sent to your email after payment.</p> |
|
<p class="mt-2 text-sm font-bold">Please make sure to enter a valid email address!</p> |
|
</div> |
|
|
|
<label class="block text-gray-700 mb-2">Email</label> |
|
<input v-model="email" type="email" placeholder="Enter your email" class="w-full p-2 border rounded mb-4"> |
|
|
|
<label class="block text-gray-700 mb-2">Amount</label> |
|
<input v-model="amount" type="text" placeholder="Enter amount" class="w-full p-2 border rounded mb-4"> |
|
|
|
<div class="flex justify-between mb-4"> |
|
<button @click="setAmount(10)" class="bg-gray-200 text-gray-700 py-2 px-4 rounded">$10</button> |
|
<button @click="setAmount(20)" class="bg-gray-200 text-gray-700 py-2 px-4 rounded">$20</button> |
|
<button @click="setAmount(50)" class="bg-gray-200 text-gray-700 py-2 px-4 rounded">$50</button> |
|
<button @click="setAmount(100)" class="bg-gray-200 text-gray-700 py-2 px-4 rounded">$100</button> |
|
</div> |
|
|
|
<button @click="handleButtonClick" class="bg-black text-white py-2 px-4 w-full rounded"> |
|
{{ buttonText }} |
|
</button> |
|
</div> |
|
|
|
<script> |
|
const { createApp } = Vue; |
|
createApp({ |
|
data() { |
|
return { |
|
email: '', |
|
amount: '', |
|
showWarning: false, |
|
isConfirmed: false |
|
} |
|
}, |
|
computed: { |
|
buttonText() { |
|
return this.isConfirmed ? 'Confirm' : 'Recharge'; |
|
} |
|
}, |
|
methods: { |
|
setAmount(value) { |
|
this.amount = value; |
|
}, |
|
handleButtonClick() { |
|
if (!this.email || !this.amount) { |
|
alert('Please enter both email and amount'); |
|
return; |
|
} |
|
|
|
if (!this.isConfirmed) { |
|
this.showWarning = true; |
|
this.isConfirmed = true; |
|
return; |
|
} |
|
|
|
this.submitRecharge(); |
|
}, |
|
async submitRecharge() { |
|
try { |
|
const response = await fetch('/create-payment', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
}, |
|
body: JSON.stringify({ |
|
email: this.email, |
|
amount: parseFloat(this.amount) |
|
}) |
|
}); |
|
|
|
const data = await response.json(); |
|
if (data.result === 100) { |
|
window.location.href = data.payLink; |
|
} else { |
|
alert('Payment creation failed: ' + data.message); |
|
} |
|
} catch (error) { |
|
alert('Error creating payment: ' + error.message); |
|
} |
|
} |
|
} |
|
}).mount('#app'); |
|
</script> |
|
</body> |
|
</html>`; |
|
|
|
res.send(html); |
|
}); |
|
|
|
|
|
app.post('/create-payment', async (req, res) => { |
|
const { email, amount } = req.body; |
|
|
|
if (!email || !amount) { |
|
return res.status(400).json({ error: 'Email and amount are required' }); |
|
} |
|
|
|
const baseUrl = `${req.protocol}://${req.get('host')}`; |
|
const orderId = generateOrderId(); |
|
|
|
|
|
orderTracking.set(orderId, { |
|
email, |
|
amount, |
|
timestamp: Date.now() |
|
}); |
|
|
|
const paymentData = { |
|
merchant: MERCHANT_KEY, |
|
amount: amount, |
|
currency: "USD", |
|
lifeTime: 15, |
|
feePaidByPayer: 1, |
|
underPaidCover: "", |
|
callbackUrl: `${baseUrl}/callback`, |
|
returnUrl: "https://harmon.tr", |
|
description: "", |
|
orderId: orderId, |
|
email: "" |
|
}; |
|
|
|
try { |
|
const response = await fetch('https://harmon.com.tr/cp.php?endpoint=/merchants/request', { |
|
method: 'POST', |
|
headers: { |
|
'accept': '*/*', |
|
'content-type': 'application/json', |
|
'origin': 'https://harmon.com.tr', |
|
'referer': 'https://harmon.com.tr/payment?id=create', |
|
'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 Edg/131.0.0.0' |
|
}, |
|
body: JSON.stringify(paymentData) |
|
}); |
|
|
|
const responseData = await response.json(); |
|
res.json(responseData); |
|
} catch (error) { |
|
console.error('Error creating payment:', error); |
|
orderTracking.delete(orderId); |
|
res.status(500).json({ error: 'Failed to create payment' }); |
|
} |
|
}); |
|
|
|
|
|
app.post('/callback', (req, res) => { |
|
console.log('Callback received:', { |
|
headers: req.headers, |
|
body: req.body, |
|
method: req.method |
|
}); |
|
|
|
if (req.method !== 'POST') { |
|
console.log('Invalid request method:', req.method); |
|
return res.status(200).send('ok'); |
|
} |
|
|
|
const hmacHeader = req.headers['hmac']; |
|
if (!hmacHeader) { |
|
console.log('Missing HMAC signature'); |
|
return res.status(200).send('ok'); |
|
} |
|
|
|
const postData = JSON.stringify(req.body); |
|
let data; |
|
|
|
try { |
|
data = JSON.parse(postData); |
|
} catch (error) { |
|
console.log('Invalid JSON data:', error); |
|
return res.status(200).send('ok'); |
|
} |
|
|
|
|
|
if (!webhookHandler.validateHmacSignature(postData, hmacHeader, data.type)) { |
|
console.log('Invalid HMAC signature:', { |
|
received: hmacHeader, |
|
data: postData |
|
}); |
|
return res.status(200).send('ok'); |
|
} |
|
|
|
|
|
switch (data.status) { |
|
case 'Waiting': |
|
webhookHandler.handleWaitingPayment(data); |
|
break; |
|
case 'Confirming': |
|
webhookHandler.handleConfirmingPayment(data); |
|
break; |
|
case 'Paid': |
|
webhookHandler.handlePaidPayment(data); |
|
break; |
|
case 'Expired': |
|
webhookHandler.handleExpiredPayment(data); |
|
break; |
|
case 'Complete': |
|
webhookHandler.handleCompletePayout(data); |
|
break; |
|
default: |
|
console.log('Unhandled status:', data.status, data); |
|
} |
|
|
|
return res.status(200).send('ok'); |
|
}); |
|
|
|
|
|
app.get('/health', (req, res) => { |
|
res.status(200).json({ status: 'healthy' }); |
|
}); |
|
|
|
|
|
const PORT = process.env.PORT || 3002; |
|
app.listen(PORT, () => { |
|
console.log(`Payment server listening on port ${PORT}`); |
|
}); |
|
|
|
|
|
app.use((req, res, next) => { |
|
|
|
res.redirect('/'); |
|
}); |
|
|
|
|
|
app.use((err, req, res, next) => { |
|
console.error('Server error:', err); |
|
res.status(500).json({ error: 'Internal server error' }); |
|
}); |
|
|
|
|
|
setInterval(() => { |
|
const oneHourAgo = Date.now() - (60 * 60 * 1000); |
|
for (const [orderId, data] of orderTracking.entries()) { |
|
if (data.timestamp < oneHourAgo) { |
|
orderTracking.delete(orderId); |
|
} |
|
} |
|
}, 60 * 60 * 1000); |
|
|