Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,35 @@
.env
# Dependencies
node_modules/
jspm_packages/

# Debug logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# OS related files
.DS_Store
Thumbs.db

# IDEs and editors
.vscode/
.idea/
*.swp
*.swo

# Build outputs
dist/
build/
out/

# Other
.npm
.eslintcache
2 changes: 1 addition & 1 deletion config/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const mongoose = require("mongoose");

const connectDB = async () => {
try {
await mongoose.connect("mongodb+srv://Menna:T-J2gJB%25xRDqSHQ@cluster0.vnuyztt.mongodb.net/?appName=Cluster0");
await mongoose.connect(process.env.MONGODB_URI);
console.log("DB connected ✅");
} catch (error) {
console.log(error);
Expand Down
51 changes: 33 additions & 18 deletions controllers/paymentController.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,53 @@ const { generateTicketsForUser } = require("../services/ticketService");

const handleWebhook = async (req, res) => {
try {
const data = req.body.obj;
const success = data.success;
const orderId = data.order.id;
const { type, obj: data } = req.body;

// Only process transaction webhooks
if (type !== "TRANSACTION") {
return res.sendStatus(200);
}

const success = data.success === true || data.success === "true";
const orderId = data.order?.id || data.order; // Handle both object and primitive

console.log(`Webhook received: Type=${type}, Success=${success}, OrderID=${orderId}`);

const user = await User.findOne({ paymobOrderId: orderId });

if (!user || user.paymentStatus !== "pending") {
if (!user) {
console.log("User not found for orderId:", orderId);
return res.sendStatus(200);
}

if (!success) {
user.paymentStatus = "failed";
await user.save();
// If already paid, don't do anything
if (user.paymentStatus === "paid") {
return res.sendStatus(200);
}

if (user.paymentStatus === "failed") {
return res.sendStatus(401).message("Payment failed");
}
const tickets = await generateTicketsForUser(user);

user.tickets = tickets;
user.paymentStatus = "paid";

await user.save();
if (success) {
// Generate tickets and update status
const tickets = await generateTicketsForUser(user);
user.tickets = tickets;
user.paymentStatus = "paid";
await user.save();

console.log("USER PAID:", user.email);
console.log("✅ PAYMENT SUCCESS: User paid and tickets generated:", user.email);
} else {
// Don't mark as 'failed' immediately to allow for subsequent successful attempts
// Just log the failure for now
console.log("❌ PAYMENT ATTEMPT FAILED: OrderID:", orderId, "User:", user.email);

// Optional: you could update to 'failed' if you want to track it,
// but ensure 'paid' can still overwrite 'failed'.
// user.paymentStatus = "failed";
// await user.save();
}

return res.sendStatus(200);

} catch (err) {
console.log(err);
console.error("❌ WEBHOOK ERROR:", err);
return res.sendStatus(500);
}
};
Expand Down
10 changes: 6 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ const port = process.env.PORT || 3000;

app.use(cors({
origin: [
"http://localhost:3001",
process.env.FRONTEND_URL
],
credentials: true
"http://localhost:3001",
"http://localhost:5500", // VS Code Live Server
"http://127.0.0.1:5500",
process.env.FRONTEND_URL
],
credentials: true
}));

// Middleware to parse JSON bodies
Expand Down
16 changes: 0 additions & 16 deletions node_modules/.package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion services/getTicketService.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const getTickets = async (req, res) => {
let showTicketAloneButton = ""
for (let t of user.tickets) {
const qr = await QRCode.toDataURL(
`https://competition.ieeehsb.com/join?code=${t.code}`
`${process.env.BASE_URL}/join?code=${t.code}`
);

if (user.tickets.length > 1) {
Expand Down
2 changes: 1 addition & 1 deletion services/ticketPageService.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const getTicketPage = async (req, res) => {

const groupLink = competitionLinks[user.competitionId];

const qr = await QRCode.toDataURL(code);
const qr = await QRCode.toDataURL(`${process.env.BASE_URL}/join?code=${code}`);

res.send(`
<!DOCTYPE html>
Expand Down
7 changes: 0 additions & 7 deletions services/ticketService.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ const { generateQR } = require("./qrService");
const competitionLinks = require("../config/competitionLinks");

const generateTicketsForUser = async (user) => {
const groupLink = competitionLinks[user.competitionId];

let participants = [
{ name: user.name, phone: user.phone }
];
Expand All @@ -19,15 +17,10 @@ const generateTicketsForUser = async (user) => {
for (let p of participants) {
const code = generateCode();

const qr = await generateQR(
`${process.env.BASE_URL}/join?code=${code}`
);

tickets.push({
name: p.name,
phone: p.phone,
code,
qr,
used: false
});
}
Expand Down
173 changes: 173 additions & 0 deletions test-frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Competition Test Portal</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Rajdhani:wght@500;700&display=swap" rel="stylesheet">
<style>
body {
background: radial-gradient(circle at center, #1b2735 0%, #090a0f 100%);
font-family: 'Rajdhani', sans-serif;
min-height: 100vh;
}
.glass {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(15px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.neon-text {
text-shadow: 0 0 10px rgba(32, 210, 255, 0.5);
}
.cyber-input {
background: rgba(15, 23, 42, 0.8) !important;
border: 1px solid rgba(51, 65, 85, 1);
color: white;
transition: all 0.3s ease;
}
.cyber-input:focus {
border-color: #06b6d4;
box-shadow: 0 0 15px rgba(6, 182, 212, 0.2);
outline: none;
}
</style>
</head>
<body class="flex items-center justify-center p-4">

<div class="glass w-full max-w-4xl rounded-3xl p-8 md:p-12 shadow-2xl relative overflow-hidden">
<!-- Decoration -->
<div class="absolute -top-24 -right-24 w-48 h-48 bg-cyan-500 blur-[100px] opacity-20"></div>
<div class="absolute -bottom-24 -left-24 w-48 h-48 bg-blue-600 blur-[100px] opacity-20"></div>

<div class="text-center mb-10 relative z-10">
<h1 class="text-4xl font-bold text-cyan-400 uppercase tracking-[0.2em] mb-4" style="font-family: 'Orbitron';">System Test Portal</h1>
<div class="h-1 w-24 bg-cyan-500 mx-auto rounded-full mb-4"></div>
<p class="text-gray-400 tracking-widest text-sm uppercase">Competition Registration & Payment Integration</p>
</div>

<form id="registrationForm" class="grid grid-cols-1 md:grid-cols-2 gap-8 relative z-10">
<!-- Name -->
<div class="space-y-2">
<label class="text-xs uppercase tracking-widest text-cyan-500 font-bold">Full Name</label>
<input type="text" name="name" required class="cyber-input w-full rounded-xl p-4" placeholder="Enter your full name">
</div>

<!-- Email -->
<div class="space-y-2">
<label class="text-xs uppercase tracking-widest text-cyan-500 font-bold">Email Address</label>
<input type="email" name="email" required class="cyber-input w-full rounded-xl p-4" placeholder="email@example.com">
</div>

<!-- Phone -->
<div class="space-y-2">
<label class="text-xs uppercase tracking-widest text-cyan-500 font-bold">Mobile Number</label>
<input type="text" name="phone" required class="cyber-input w-full rounded-xl p-4" placeholder="01XXXXXXXXX">
</div>

<!-- Competition -->
<div class="space-y-2">
<label class="text-xs uppercase tracking-widest text-cyan-500 font-bold">Competition Branch</label>
<select name="competitionId" required class="cyber-input w-full rounded-xl p-4 appearance-none">
<option value="">Choose competition</option>
<option value="comp1">Texight 9.0 - Power</option>
<option value="comp2">Auxillio 9.0 - Robotics</option>
<option value="comp3">Modifier 10.0 - Communication</option>
<option value="comp4">Elevera 1.0 - Graphic Design</option>
</select>
</div>

<!-- Payment Method -->
<div class="space-y-2">
<label class="text-xs uppercase tracking-widest text-cyan-500 font-bold">Payment Method</label>
<div class="grid grid-cols-2 gap-4">
<label class="cursor-pointer group">
<input type="radio" name="paymentMethod" value="card" class="hidden peer" required>
<div class="p-4 border border-slate-700 rounded-xl bg-slate-900/50 text-center peer-checked:border-cyan-500 peer-checked:bg-cyan-500/10 transition">
<span class="text-sm font-bold uppercase tracking-wider">Credit Card</span>
</div>
</label>
<label class="cursor-pointer group">
<input type="radio" name="paymentMethod" value="wallet" class="hidden peer" required>
<div class="p-4 border border-slate-700 rounded-xl bg-slate-900/50 text-center peer-checked:border-cyan-500 peer-checked:bg-cyan-500/10 transition">
<span class="text-sm font-bold uppercase tracking-wider">E-Wallet</span>
</div>
</label>
</div>
</div>

<!-- Promo Code -->
<div class="space-y-2">
<label class="text-xs uppercase tracking-widest text-cyan-500 font-bold">Promo Code</label>
<input type="text" name="promoCode" class="cyber-input w-full rounded-xl p-4" placeholder="Optional code">
</div>

<!-- Submit Button -->
<div class="md:col-span-2 mt-6">
<button type="submit" id="submitBtn" class="w-full bg-cyan-600 hover:bg-cyan-500 text-white font-black py-5 rounded-2xl shadow-[0_0_20px_rgba(6,182,212,0.3)] transform transition hover:-translate-y-1 active:scale-95 uppercase tracking-[0.3em] text-lg">
Initialize Secure Payment
</button>
</div>
</form>

<div class="mt-8 text-center">
<p class="text-gray-500 text-[10px] uppercase tracking-[0.4em]">Powered by IEEE HSB | Payment secured by Paymob</p>
</div>
</div>

<script>
const API_BASE = "http://localhost:3000/api";

document.getElementById('registrationForm').addEventListener('submit', async (e) => {
e.preventDefault();
const btn = document.getElementById('submitBtn');
const formData = new FormData(e.target);
const data = Object.fromEntries(formData.entries());

// Map values to match backend requirements
data.bundle = false;
data.groupData = {};

// Basic UI Feedback
btn.disabled = true;
btn.innerText = "Connecting to Secure Gateway...";
btn.classList.add('opacity-50');

try {
const response = await axios.post(`${API_BASE}/users/register`, data);

Swal.fire({
title: '<span style="font-family: Orbitron; color: #22d3ee">INITIALIZED</span>',
html: '<p style="color: #94a3b8">Secure payment session created successfully.<br>Redirecting now...</p>',
background: '#0f172a',
icon: 'success',
iconColor: '#06b6d4',
timer: 2000,
showConfirmButton: false,
backdrop: `rgba(0,0,123,0.4)`
}).then(() => {
window.location.href = response.data.paymentUrl;
});

} catch (error) {
console.error(error);
const errorMsg = error.response?.data?.error || error.message || 'System Error';

Swal.fire({
title: '<span style="font-family: Orbitron; color: #ef4444">FAILED</span>',
html: `<p style="color: #94a3b8">${errorMsg}</p>`,
background: '#0f172a',
icon: 'error',
iconColor: '#ef4444'
});

btn.disabled = false;
btn.innerText = "Initialize Secure Payment";
btn.classList.remove('opacity-50');
}
});
</script>
</body>
</html>