diff --git a/foftickets.js b/foftickets.js index a504a7c..a927e00 100644 --- a/foftickets.js +++ b/foftickets.js @@ -59,9 +59,9 @@ const QRSalt ="!SaltyMagic5392370662"; // + Create Account (User) // + Change Password (User) // + Change most POST routes to end in redirect instead of render. -// Deactivate individual magic links (Admin) -// See how much each camp/ticket has paid (Admin) -// Purchase individual open camping tickets +// + See how much each camp/ticket has paid (Admin) +// + Purchase individual open camping tickets +// + Deactivate individual magic links (Admin) // Maybe: // Deactivate individual magic links (User) // Option to "Email me my QR Code" @@ -69,9 +69,9 @@ const QRSalt ="!SaltyMagic5392370662"; // ToDo, Later // + Use a templating engine // + Store password hashed and salted +// + Stripe Integration // Make all HTML look nice // Logging and Replay system(?) -// + Stripe Integration // More efficent data structure: TicketsByCamp, TicketsByOffered, TicketsByOwner // @@ -183,6 +183,7 @@ function InitDatabase() { for (const key in camps ) delete camps[key]; users["teppy@egenesis.com" ]= { password: hashPW("x6321872X.1"), superuser:true, linksalt:"" }; users["ginachen94@gmail.com" ]= { password: hashPW("indiantreeonfire"),superuser:true, linksalt:"" }; + camps["open"]={ leader:"", lastid:0 }; } InitDatabase(); @@ -268,6 +269,8 @@ app.get('/camps',requireSuperUser, (req,res) => { } return res.render("camps",{ username:req.session.username, superuser:req.session.superuser, camps:camplist }); }) + + app.post("/camps",requireSuperUser,(req,res) => { const campname=req.body.campname; @@ -333,7 +336,6 @@ app.get('/mytickets',requireLogin, async (req,res)=> { let owned=0; let theticket=""; const edit={ username:req.session.username, superuser:req.session.superuser, tickets: {}, settings:settings }; - console.log(edit); for (const t in tickets) { if (tickets[t].status=="i" && tickets[t].offered==username) { claimed++; tickets[t].owner=username; tickets[t].offered=""; } // LOG if (tickets[t].status=="i" && tickets[t].owner==username) { @@ -358,7 +360,6 @@ app.get('/mytickets',requireLogin, async (req,res)=> { const data={ username:username, superuser:req.session.superuser, message:message, magiclink:GetMagicLink(username), ticket:theticket, offered:tickets[theticket].offered, paid:tickets[theticket].paid, qrcode:dataURL, useurl:useurl, settings: settings } - console.log(data); return res.render("oneticket",data); } else return res.render("manytickets",edit); @@ -388,6 +389,7 @@ app.post('/oneticket',requireLogin, async (req,res) => { // Make sure the ticket let ticket=req.body.ticket; if (!tickets[ticket] || tickets[ticket].owner!=username) return res.render("error", { username:username, superuser:req.session.superuser, message: "You are not the owner of ticket "+ticket }); let offered=req.body.offered; + console.log("Trying to transfer ",ticket); let message=""; if (req.session.cantransfer && tickets[ticket].owner==username && tickets[ticket].status=="i") { tickets[ticket].offered=offered; // LOG @@ -626,14 +628,6 @@ app.post('/qrcodesu',requireSuperUser,async (req,res) => { return res.send({ owner:username, qrcode: URL, magiclink:GetMagicLink(username) }); }) -app.get("/buy",(req,res) => { - return res.render("buy",{ username:req.session.username, superuser:req.session.superuser, settings:settings, message: "" }); - }); - -app.post("/buy",(req,res) => { - // Stopped here - }); - app.get('/settings',requireSuperUser, (req,res) => { res.render('settings',{ username:req.session.username, superuser:req.session.superuser, settings:settings, message: "" }) }); @@ -683,58 +677,107 @@ app.post('/purge',requireSuperUser, (req,res) => { return res.redirect("/settings"); }); +app.post("/killmagiclink",requireSuperUser,(req,res) => { + if (!users[req.body.email]) users[req.body.email]={ }; + users[req.body.email].linksalt=generateSecureToken(); + req.session.message="Deactivated any previous Magic Links for "+req.body.email; + return res.redirect("/settings"); + }); + app.post('/update-setting', requireSuperUser, (req, res) => { settings[req.body.name]=req.body.checked; - console.log("setting got updated to ",settings[req.body.name]); res.json({ success: true, message: 'Checkbox state updated successfully' }); }); app.post('/pay0',requireLogin,(req,res) => { - return res.render("pay",{ username:req.session.username, superuser:req.session.superuser, ticket:req.body.ticket, amount:req.body.amount }); -}); + return res.render("pay",{ username:req.session.username, + superuser:req.session.superuser, + payforwhat: { kind:"existing", ticket:req.body.ticket, amount:+req.body.amount, total:+req.body.amount, qty:1 }, + }); + }); + + +function check_payforwhat(payforwhat,req) { + if (payforwhat.kind=="existing") { + let ticket=tickets[payforwhat.ticket]; + if (!ticket || ticket.status=="r" || ticket.owner!=req.session.username) return false; + } + else if (payforwhat.kind=="new") { + if (payforwhat.qty<=0 || payforwhat.qty>6 || payforwhat.amounteach<=0 || payforwhat.amounteach>100000) return false; + } + return true; + } + +function assure_camp_exists(camp) { + if (!camps[camp]) camps[camp]={ leader:"", lastid:0 }; + } + +function do_payforwhat(payforwhat) { + if (payforwhat.kind=="existing") { + tickets[payforwhat.ticket].paid=100.0*payforwhat.amount; + } + else if (payforwhat.kind=="new") { + assure_camp_exists(payforwhat.camp); + for (let i=0; i { + return res.render("buy",{ username:req.session.username, superuser:req.session.superuser, settings:settings, message: "" }); + }); + +app.post('/buy',requireLogin,(req,res) => { + return res.render("pay",{ username:req.session.username, + superuser:req.session.superuser, + payforwhat: { kind:"new", camp:"open", email:req.body.email, qty:req.body.qty, amounteach:req.body.amounteach, total:req.body.qty*req.body.amounteach }}); + }); + app.post('/charge', requireLogin, async (req, res) => { - const ticket=req.body.ticket; - if (tickets[ticket].status=='r' || tickets[ticket].owner!=req.session.username) { - res.json({ error: "Sanity check in /charge" }); - } else try { - // Token or Payment Method ID from the client - const paymentMethodId = req.body.paymentMethodId; - console.log("paymentMethodId: ",paymentMethodId); + const payforwhat=req.body.payforwhat; + if (!check_payforwhat(payforwhat,req)) return res.json({ error: 'Invalid PayForWhat' }); + try { + // Token or Payment Method ID from the client + const paymentMethodId = req.body.paymentMethodId; - // Create a PaymentIntent on the server - const pennies=Math.round(parseFloat(req.body.amount) * 100); - const return_url=base_url+'/mytickets'; - const paymentIntent = await stripe.paymentIntents.create({ - amount: pennies, // Amount in cents - currency: 'usd', - payment_method: paymentMethodId, - confirmation_method: 'automatic', - confirm: true, // Attempt to confirm the payment immediately - return_url: return_url, + // Create a PaymentIntent on the server + let pennies=0; + if (payforwhat.kind=="new") pennies=Math.round(parseFloat(payforwhat.amounteach))*parseInt(payforwhat.qty) * 100; + else if (payforwhat.kind=="existing") pennies=Math.round(parseFloat(payforwhat.amount))*100; + const return_url=base_url+'/mytickets'; + const paymentIntent = await stripe.paymentIntents.create({ + amount: pennies, // Amount in cents + currency: 'usd', + payment_method: paymentMethodId, + confirmation_method: 'automatic', + confirm: true, // Attempt to confirm the payment immediately + return_url: return_url, }); // Check status of payment intent - if (paymentIntent.status === 'requires_action') { + if (paymentIntent.status === 'requires_action') { // Additional action is required (e.g. 3D Secure) - res.json({ - requiresAction: true, - paymentIntentClientSecret: paymentIntent.client_secret, + res.json({ + requiresAction: true, + paymentIntentClientSecret: paymentIntent.client_secret, }); - } else if (paymentIntent.status === 'succeeded') { - // Payment is complete - tickets[ticket].paid=pennies; - console.log("Paid ",pennies," for ticket ",ticket); - res.json({ success: true, redirect_url: return_url }); - } else { - res.json({ error: 'Invalid PaymentIntent status' }); + } else if (paymentIntent.status === 'succeeded') { + // Payment is complete + do_payforwhat(payforwhat); + res.json({ success: true, redirect_url: return_url }); + } else { + res.json({ error: 'Invalid PaymentIntent status' }); } } catch (error) { console.error('Payment error:', error); res.json({ error: error.message }); - } + } }); diff --git a/views/buy.ejs b/views/buy.ejs index 90d2c9e..b2f5a07 100644 --- a/views/buy.ejs +++ b/views/buy.ejs @@ -13,7 +13,7 @@ Number of Tickets:

Pay how much for each ticket:
-
+
diff --git a/views/pay.ejs b/views/pay.ejs index bb97387..e3ab355 100644 --- a/views/pay.ejs +++ b/views/pay.ejs @@ -8,8 +8,9 @@ <%- include('partials/nav') %>
-Payment Here!!!<%=amount%> + + You are about to charge $<%=payforwhat.total.toFixed(2)%> for <%=payforwhat.qty%> tickets.
@@ -23,7 +24,7 @@ Payment Here!!!<%=amount%>
- +
@@ -46,54 +47,54 @@ Payment Here!!!<%=amount%> const paymentForm = document.getElementById("payment-form"); paymentForm.addEventListener("submit", async (e) => { - e.preventDefault(); + e.preventDefault(); - // 1. Create a PaymentMethod - const { paymentMethod, error } = await stripe.createPaymentMethod({ + // 1. Create a PaymentMethod + const { paymentMethod, error } = await stripe.createPaymentMethod({ type: "card", card: cardNumberElement, - }); + }); - if (error) { + if (error) { console.error(error); alert(error.message); return; - } + } - // 2. Send PaymentMethod ID to server - const response = await fetch("/charge", { + // 2. Send PaymentMethod ID to server + console.log("Payforwhat: ",<%-JSON.stringify(payforwhat)%>); + const pfw = <%-JSON.stringify(payforwhat)%>; + const response = await fetch("/charge", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ paymentMethodId: paymentMethod.id, ticket:"<%=ticket%>", amount:"<%=amount%>" }), - }); + body: JSON.stringify({ paymentMethodId: paymentMethod.id, payforwhat: pfw }), + }); - const responseData = await response.json(); + const responseData = await response.json(); - if (responseData.error) { + if (responseData.error) { console.error(responseData.error); alert(responseData.error); return; - } + } - if (responseData.requiresAction) { - // 3. Handle Additional Action if required (e.g., 3DS) - const { error: confirmError } = await stripe.confirmCardPayment( - responseData.paymentIntentClientSecret - ); + if (responseData.requiresAction) { + // 3. Handle Additional Action if required (e.g., 3DS) + const { error: confirmError } = await stripe.confirmCardPayment(responseData.paymentIntentClientSecret); - if (confirmError) { + if (confirmError) { console.error(confirmError); alert(confirmError.message); return; - } + } - alert("Payment succeeded"); - // redirect or show success message - window.location.href = responseData.redirect_url; - } else if (responseData.success) { - // Payment succeeded without additional action - alert("Payment succeeded."); - window.location.href = responseData.redirect_url; + alert("Payment succeeded"); + // redirect or show success message + window.location.href = responseData.redirect_url; + } else if (responseData.success) { + // Payment succeeded without additional action + alert("Payment succeeded."); + window.location.href = responseData.redirect_url; } }); diff --git a/views/settings.ejs b/views/settings.ejs index fe53a80..700fd3b 100644 --- a/views/settings.ejs +++ b/views/settings.ejs @@ -23,7 +23,13 @@
+
+ Deactivate Magic Links for: + + +
+ Import Tickets (Frostburn Format):