| 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); |
|
|