This commit is contained in:
2025-03-09 22:13:57 -04:00
parent 792a64fd13
commit 279d56481b
4 changed files with 126 additions and 76 deletions

View File

@@ -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();
@@ -269,6 +270,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;
const leader=req.body.leader;
@@ -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,29 +677,79 @@ 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<payforwhat.qty; i++) {
camps[payforwhat.camp].lastid++;
tickets[payforwhat.camp+"-"+camps[payforwhat.camp].lastid]={ owner:payforwhat.email, offered:"",paid:100.0*payforwhat.amounteach,status:"i" };
}
}
}
app.get("/buy",(req,res) => {
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 {
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;
console.log("paymentMethodId: ",paymentMethodId);
// Create a PaymentIntent on the server
const pennies=Math.round(parseFloat(req.body.amount) * 100);
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
@@ -725,8 +769,7 @@ app.post('/charge', requireLogin, async (req, res) => {
});
} else if (paymentIntent.status === 'succeeded') {
// Payment is complete
tickets[ticket].paid=pennies;
console.log("Paid ",pennies," for ticket ",ticket);
do_payforwhat(payforwhat);
res.json({ success: true, redirect_url: return_url });
} else {
res.json({ error: 'Invalid PaymentIntent status' });

View File

@@ -13,7 +13,7 @@
Number of Tickets:<br>
<input type="number" min="1" max="4" step="1" placeholder="1" name="qty"><br>
Pay how much for each ticket:<br>
<input type="number" min="1.00" max="1000.00" step="0.01" placeholder="50.00" name="amount"><br>
<input type="number" min="1.00" max="1000.00" step="0.01" placeholder="50.00" name="amounteach"><br>
<button id="Submit" type="submit">Pay</button>
</form>
</div>

View File

@@ -8,8 +8,9 @@
<%- include('partials/nav') %>
<div class="content">
<form id="payment-form">
Payment Here!!!<%=amount%>
<!-- Stripe Elements will inject the Card Element here -->
You are about to charge $<%=payforwhat.total.toFixed(2)%> for <%=payforwhat.qty%> tickets.<br>
<label for="card-number-element">Card Number</label>
<div id="card-number-element"></div>
@@ -23,7 +24,7 @@ Payment Here!!!<%=amount%>
<label for="card-postal-element">ZIP Code</label>
<div id="card-postal-element"></div>
<button id="submit-button" type="submit">Pay <%=amount%> Now</button>
<button id="submit-button" type="submit">Pay Now</button>
</form>
</div>
<!-- Stripe.js -->
@@ -61,10 +62,12 @@ Payment Here!!!<%=amount%>
}
// 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();
@@ -77,9 +80,7 @@ Payment Here!!!<%=amount%>
if (responseData.requiresAction) {
// 3. Handle Additional Action if required (e.g., 3DS)
const { error: confirmError } = await stripe.confirmCardPayment(
responseData.paymentIntentClientSecret
);
const { error: confirmError } = await stripe.confirmCardPayment(responseData.paymentIntentClientSecret);
if (confirmError) {
console.error(confirmError);

View File

@@ -23,7 +23,13 @@
<form action='/deserialize' method='post'>
<button type="submit" >Deserialize</button>
</form>
<form action='/killmagiclink' method='post'>
Deactivate Magic Links for:
<input type="email" name="email">
<button type="submit">Deactivate</button>
</form>
<form action="/importfb" method="post" enctype="multipart/form-data">
Import Tickets (Frostburn Format):
<input type="file" name="file">
<input type="submit" value="Upload">
</form>