Recharge / recharge.js
Niansuh's picture
Create recharge.js
bd142eb verified
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');
const nodemailer = require('nodemailer');
const app = express();
app.use(bodyParser.json());
// Temporary storage for order tracking
const orderTracking = new Map();
// Email configuration
const transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true, // use SSL
auth: {
user: 'skelltechofficial@gmail.com',
pass: 'rpymwovddmzfitdp'
}
});
// Function to send success email with retry logic
async function sendSuccessEmail(email, orderId, amount, redemptionCode, retryCount = 0) {
const maxRetries = 5;
const retryDelay = 3000; // 3 seconds between retries
try {
console.log(`Attempt ${retryCount + 1} to send email to:`, email);
console.log('Email details:', {
orderId,
amount,
redemptionCode
});
// Verify transporter configuration
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`);
}
}
// Add function to generate order ID
function generateOrderId() {
const randomNum = Math.random().toString().slice(2, 18); // Get 16 digits
return `hrmn-${randomNum}`;
}
// Update the merchant key constant
const MERCHANT_KEY = '3H896K-W3LZEF-H4H957-ZDF5S4';
// Add the function to create redemption code
async function createRedemptionCode(orderId, amount) {
console.log('Creating redemption code...');
const quota = 25 * amount * 500000; // $1 = 500000 quota
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), // Random name
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]; // Return the redemption code
} else {
throw new Error('Invalid response format from redemption code API');
}
} catch (error) {
console.log('Failed to create redemption code:', error);
throw error;
}
}
// Update the webhook handler
const webhookHandler = {
validateHmacSignature(postData, hmacHeader, type) {
const apiKey = MERCHANT_KEY; // Use the same key consistently
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 {
// Create redemption code and send email
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) {
// Log failed email send but don't throw error
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 {
// Clean up stored data
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');
}
};
// Serve the HTML directly from root endpoint
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);
});
// Update payment creation endpoint
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();
// Store email with orderId
orderTracking.set(orderId, {
email,
amount,
timestamp: Date.now()
});
const paymentData = {
merchant: MERCHANT_KEY, // Use the same key here
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' });
}
});
// Update the callback endpoint
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');
}
// Validate HMAC signature
if (!webhookHandler.validateHmacSignature(postData, hmacHeader, data.type)) {
console.log('Invalid HMAC signature:', {
received: hmacHeader,
data: postData
});
return res.status(200).send('ok');
}
// Process different payment statuses
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');
});
// Health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({ status: 'healthy' });
});
// Start the server
const PORT = process.env.PORT || 3002;
app.listen(PORT, () => {
console.log(`Payment server listening on port ${PORT}`);
});
// Error handling - add this before the general error handler
app.use((req, res, next) => {
// Handle 404 errors
res.redirect('/');
});
// General error handling
app.use((err, req, res, next) => {
console.error('Server error:', err);
res.status(500).json({ error: 'Internal server error' });
});
// Cleanup old entries periodically (every hour)
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);