First version with credit card integration and UI
This commit is contained in:
132
foftickets.js
132
foftickets.js
@@ -216,7 +216,7 @@ app.get('/emaillink',(req,res) => {
|
|||||||
app.get('/camps',requireSuperUser, (req,res) => {
|
app.get('/camps',requireSuperUser, (req,res) => {
|
||||||
const camplist={};
|
const camplist={};
|
||||||
for (const c in camps) {
|
for (const c in camps) {
|
||||||
camplist[c]={ leader:camps[c].leader, issued:0, claimed:0, used:0 };
|
camplist[c]={ leader:camps[c].leader, issued:0, claimed:0, used:0, npaid:0, paid:0 };
|
||||||
}
|
}
|
||||||
for (const t in tickets) {
|
for (const t in tickets) {
|
||||||
const parts=t.split("-");
|
const parts=t.split("-");
|
||||||
@@ -226,6 +226,8 @@ app.get('/camps',requireSuperUser, (req,res) => {
|
|||||||
camplist[campname].issued+=1;
|
camplist[campname].issued+=1;
|
||||||
if (tickets[t].owner!="") camplist[campname].claimed+=1;
|
if (tickets[t].owner!="") camplist[campname].claimed+=1;
|
||||||
if (tickets[t].status=="u") camplist[campname].used+=1;
|
if (tickets[t].status=="u") camplist[campname].used+=1;
|
||||||
|
if (tickets[t].paid>0) camplist[campname].npaid+=1;
|
||||||
|
camplist[campname].paid+=tickets[t].paid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res.render("camps",{ username:req.session.username, superuser:req.session.superuser, camps:camplist });
|
return res.render("camps",{ username:req.session.username, superuser:req.session.superuser, camps:camplist });
|
||||||
@@ -238,7 +240,7 @@ app.post("/camps",requireSuperUser,(req,res) => {
|
|||||||
camps[campname]??={ leader:leader, lastid:0 };
|
camps[campname]??={ leader:leader, lastid:0 };
|
||||||
for (let i=0; i<qty; i++) {
|
for (let i=0; i<qty; i++) {
|
||||||
camps[campname].lastid+=1; // LOG
|
camps[campname].lastid+=1; // LOG
|
||||||
tickets[campname+'-'+camps[campname].lastid]={ owner: "", offered: leader, status:"i" }; // LOG
|
tickets[campname+'-'+camps[campname].lastid]={ owner: "", offered: leader, status:"i", paid:0 }; // LOG
|
||||||
}
|
}
|
||||||
EmailTickets(leader);
|
EmailTickets(leader);
|
||||||
return res.redirect("/camps");
|
return res.redirect("/camps");
|
||||||
@@ -258,6 +260,7 @@ app.get('/editcamp', requireSuperUser, (req,res) => {
|
|||||||
edit.tickets[t].owner=tickets[t].owner;
|
edit.tickets[t].owner=tickets[t].owner;
|
||||||
edit.tickets[t].offered=tickets[t].offered;
|
edit.tickets[t].offered=tickets[t].offered;
|
||||||
edit.tickets[t].status=tickets[t].status;
|
edit.tickets[t].status=tickets[t].status;
|
||||||
|
edit.tickets[t].paid=tickets[t].paid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res.render("editcamp",edit);
|
return res.render("editcamp",edit);
|
||||||
@@ -282,6 +285,7 @@ app.get('/manytickets', requireLogin, (req,res) => {
|
|||||||
for (const t in tickets) if (tickets[t].owner==username && tickets[t].status=="i") {
|
for (const t in tickets) if (tickets[t].owner==username && tickets[t].status=="i") {
|
||||||
edit.tickets[t]={};
|
edit.tickets[t]={};
|
||||||
edit.tickets[t].offered=tickets[t].offered;
|
edit.tickets[t].offered=tickets[t].offered;
|
||||||
|
edit.tickets[t].paid=tickets[t].paid;
|
||||||
}
|
}
|
||||||
return res.render("manytickets",edit);
|
return res.render("manytickets",edit);
|
||||||
})
|
})
|
||||||
@@ -301,6 +305,7 @@ app.get('/mytickets',requireLogin, async (req,res)=> {
|
|||||||
edit.tickets[t]={};
|
edit.tickets[t]={};
|
||||||
edit.tickets[t].owner=tickets[t].owner;
|
edit.tickets[t].owner=tickets[t].owner;
|
||||||
edit.tickets[t].offered=tickets[t].offered;
|
edit.tickets[t].offered=tickets[t].offered;
|
||||||
|
edit.tickets[t].paid=tickets[t].paid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let message="";
|
let message="";
|
||||||
@@ -313,25 +318,47 @@ app.get('/mytickets',requireLogin, async (req,res)=> {
|
|||||||
const hash=hash1.digest("base64").slice(0,6);
|
const hash=hash1.digest("base64").slice(0,6);
|
||||||
const useurl=MainURL+'/useticket?t='+theticket+'&h='+hashQR(theticket,username);
|
const useurl=MainURL+'/useticket?t='+theticket+'&h='+hashQR(theticket,username);
|
||||||
const dataURL=await QRCode.toDataURL(useurl);
|
const dataURL=await QRCode.toDataURL(useurl);
|
||||||
return res.render("oneticket",{ username:username, superuser:req.session.superuser, message:message, ticket:theticket, offered:tickets[theticket].offered, qrcode:dataURL, useurl:useurl });
|
return res.render("oneticket",{ 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 });
|
||||||
}
|
}
|
||||||
else return res.render("manytickets",edit);
|
else return res.render("manytickets",edit);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/oneticket',requireLogin, async (req,res)=> {
|
app.get("/oneticket",requireLogin, async (req,res) => {
|
||||||
let username=req.session.username;
|
let username=req.session.username;
|
||||||
let theticket=req.body.ticket;
|
let ticket=req.query.t;
|
||||||
|
console.log("Ticket ",tickets);
|
||||||
|
if (tickets[ticket]) console.log("Onwer ",tickets[ticket].owner);
|
||||||
|
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=tickets[ticket].offered;
|
||||||
|
let message="";
|
||||||
|
const hash0=crypto.createHash('sha256');
|
||||||
|
const hash1=hash0.update(ticket+QRSalt);
|
||||||
|
const hash=hash1.digest("base64").slice(0,6);
|
||||||
|
const useurl=MainURL+'/useticket?t='+ticket+'&h='+hashQR(ticket,username);
|
||||||
|
const dataURL=await QRCode.toDataURL(MainURL+'/useticket?t='+ticket+'&h='+hashQR(ticket,username));
|
||||||
|
return res.render("oneticket", { username:username, superuser:req.session.superuser, message:message, magiclink:GetMagicLink(username),
|
||||||
|
ticket:ticket, offered:tickets[ticket].offered, paid:tickets[ticket].paid, qrcode:dataURL, useurl:useurl });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
app.post('/oneticket',requireLogin, async (req,res) => { // Make sure the ticket is owned by trhe logged in user!
|
||||||
|
let username=req.session.username;
|
||||||
|
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;
|
let offered=req.body.offered;
|
||||||
let message="";
|
let message="";
|
||||||
if (tickets[theticket].owner==username && tickets[theticket].status=="i") {
|
if (tickets[ticket].owner==username && tickets[ticket].status=="i") {
|
||||||
tickets[theticket].offered=offered; // LOG
|
tickets[ticket].offered=offered; // LOG
|
||||||
EmailTickets(offered);
|
EmailTickets(offered);
|
||||||
}
|
}
|
||||||
const hash0=crypto.createHash('sha256');
|
const hash0=crypto.createHash('sha256');
|
||||||
const hash1=hash0.update(theticket+QRSalt);
|
const hash1=hash0.update(ticket+QRSalt);
|
||||||
const hash=hash1.digest("base64").slice(0,6);
|
const hash=hash1.digest("base64").slice(0,6);
|
||||||
const dataURL=await QRCode.toDataURL(MainURL+'/useticket?t='+theticket+'&h='+hashQR(theticket,username));
|
const dataURL=await QRCode.toDataURL(MainURL+'/useticket?t='+ticket+'&h='+hashQR(ticket,username));
|
||||||
return res.render("oneticket", { username:username, superuser:req.session.superuser, message:message, magiclink:GetMagicLink(username), ticket:theticket, offered:tickets[theticket].offered, qrcode:dataURL });
|
const useurl=MainURL+'/useticket?t='+ticket+'&h='+hashQR(ticket,username);
|
||||||
|
return res.render("oneticket", { username:username, superuser:req.session.superuser, message:message, magiclink:GetMagicLink(username),
|
||||||
|
ticket:ticket, offered:tickets[ticket].offered, paid:tickets[ticket].paid, qrcode:dataURL, useurl:useurl });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -407,10 +434,12 @@ app.get("/useticket",(req,res) => {
|
|||||||
if (!tickets[ticket]) return res.status(500).send("Ticket "+ticket+" not found.");
|
if (!tickets[ticket]) return res.status(500).send("Ticket "+ticket+" not found.");
|
||||||
if (hashQR(ticket,tickets[ticket].owner)!=hash) return res.status(500).send("Ticket "+ticket+" was transferred to "+tickets[ticket].owner);
|
if (hashQR(ticket,tickets[ticket].owner)!=hash) return res.status(500).send("Ticket "+ticket+" was transferred to "+tickets[ticket].owner);
|
||||||
if (tickets[ticket].status!="i") return res.status(500).send("Ticket "+ticket+" has already been used.");
|
if (tickets[ticket].status!="i") return res.status(500).send("Ticket "+ticket+" has already been used.");
|
||||||
if (settings['enable-email']) {
|
let paid_message=tickets[ticket].paid;
|
||||||
|
if (tickets[ticket].paid==0) paid_message="This ticket has not been paid for. Encourage a donation at the gate.";
|
||||||
|
if (settings['enable-ticket-use']) {
|
||||||
tickets[ticket].status="u"; // LOG
|
tickets[ticket].status="u"; // LOG
|
||||||
return res.send("<h1>Welcome, "+tickets[ticket].owner+" to Falls on Fire! Ticket "+ticket+" has now been used.</h1>");
|
return res.send("<h1>Welcome, "+tickets[ticket].owner+" to Falls on Fire!<br>Ticket "+ticket+" has now been used.</h1><br>"+paid_message);
|
||||||
} else return res.send("<h1>Your ticket is good, "+tickets[ticket].owner+", but the server is not in Event Mode, so Ticket "+ticket+" is still valid.");
|
} else return res.send("<h1>Your ticket is good, "+tickets[ticket].owner+"<br>The server is not in Event Mode, so Ticket "+ticket+" is still valid.<br>"+paid_message);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -592,46 +621,51 @@ app.post('/update-setting', requireSuperUser, (req, res) => {
|
|||||||
res.json({ success: true, message: 'Checkbox state updated successfully' });
|
res.json({ success: true, message: 'Checkbox state updated successfully' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/pay0',requireLogin,(req,res) => {
|
||||||
app.get('/checkout', (req, res) => {
|
return res.render("pay",{ username:req.session.username, superuser:req.session.superuser, ticket:req.body.ticket, amount:req.body.amount });
|
||||||
// We’ll render a payment form here
|
|
||||||
res.render('checkout2');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/charge', async (req, res) => {
|
|
||||||
try {
|
|
||||||
// Token or Payment Method ID from the client
|
|
||||||
const paymentMethodId = req.body.paymentMethodId;
|
|
||||||
|
|
||||||
// Create a PaymentIntent on the server
|
app.post('/charge', requireLogin, async (req, res) => {
|
||||||
const return_url=base_url+'/mytickets';
|
const ticket=req.body.ticket;
|
||||||
const paymentIntent = await stripe.paymentIntents.create({
|
if (tickets[ticket].status=='r' || tickets[ticket].owner!=req.session.username) {
|
||||||
amount: 1999, // Amount in cents (e.g., $19.99)
|
res.json({ error: "Sanity check in /charge" });
|
||||||
currency: 'usd',
|
} else try {
|
||||||
payment_method: paymentMethodId,
|
// Token or Payment Method ID from the client
|
||||||
confirmation_method: 'automatic',
|
const paymentMethodId = req.body.paymentMethodId;
|
||||||
confirm: true, // Attempt to confirm the payment immediately
|
|
||||||
return_url: return_url,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check status of payment intent
|
// Create a PaymentIntent on the server
|
||||||
if (paymentIntent.status === 'requires_action') {
|
const pennies=Math.round(parseFloat(req.body.amount) * 100);
|
||||||
// Additional action is required (e.g. 3D Secure)
|
console.log("Pennies=",pennies);
|
||||||
res.json({
|
const return_url=base_url+'/mytickets';
|
||||||
requiresAction: true,
|
const paymentIntent = await stripe.paymentIntents.create({
|
||||||
paymentIntentClientSecret: paymentIntent.client_secret,
|
amount: pennies, // Amount in cents
|
||||||
});
|
currency: 'usd',
|
||||||
} else if (paymentIntent.status === 'succeeded') {
|
payment_method: paymentMethodId,
|
||||||
// Payment is complete
|
confirmation_method: 'automatic',
|
||||||
console.log("Returning json success: true");
|
confirm: true, // Attempt to confirm the payment immediately
|
||||||
res.json({ success: true, redirect_url: return_url });
|
return_url: return_url,
|
||||||
} else {
|
});
|
||||||
res.json({ error: 'Invalid PaymentIntent status' });
|
|
||||||
}
|
// Check status of payment intent
|
||||||
} catch (error) {
|
if (paymentIntent.status === 'requires_action') {
|
||||||
console.error('Payment error:', error);
|
// Additional action is required (e.g. 3D Secure)
|
||||||
res.json({ error: error.message });
|
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' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Payment error:', error);
|
||||||
|
res.json({ error: error.message });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,19 +10,23 @@
|
|||||||
<form id="editor" method="post">
|
<form id="editor" method="post">
|
||||||
<table border="1">
|
<table border="1">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Camp</th>
|
<th>Camp </th>
|
||||||
<th>Leader</th>
|
<th>Leader </th>
|
||||||
<th>#Issued</th>
|
<th>#Issued </th>
|
||||||
<th>#Claimed</th>
|
<th>#Claimed</th>
|
||||||
<th>#Used</th>
|
<th>#Used </th>
|
||||||
|
<th>#Paid </th>
|
||||||
|
<th>$Paid </th>
|
||||||
</tr>
|
</tr>
|
||||||
<% for (const c in camps) { %>
|
<% for (const c in camps) { %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/editcamp?campname=<%= c %>"><%=c%></td>
|
<td><a href="/editcamp?campname=<%= c %>"><%=c%></td>
|
||||||
<td><%=camps[c].leader%> </td>
|
<td><%=camps[c].leader%> </td>
|
||||||
<td><%=camps[c].issued%> </td>
|
<td><%=camps[c].issued%> </td>
|
||||||
<td><%=camps[c].claimed%> </td>
|
<td><%=camps[c].claimed%> </td>
|
||||||
<td><%=camps[c].used%> </td>
|
<td><%=camps[c].used%> </td>
|
||||||
|
<td><%=camps[c].npaid%> </td>
|
||||||
|
<td><%=(camps[c].paid/100).toFixed(2)%> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<% } %>
|
<% } %>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<label for="card-postal-element">ZIP Code</label>
|
<label for="card-postal-element">ZIP Code</label>
|
||||||
<div id="card-postal-element"></div>
|
<div id="card-postal-element"></div>
|
||||||
|
|
||||||
<button id="submit-button" type="submit">Pay $19.99</button>
|
<button id="submit-button" type="submit">Pay <%=amount%></button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<!-- Stripe.js -->
|
<!-- Stripe.js -->
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
// 1. Create a PaymentMethod
|
// 1. Create a PaymentMethod
|
||||||
const { paymentMethod, error } = await stripe.createPaymentMethod({
|
const { paymentMethod, error } = await stripe.createPaymentMethod({
|
||||||
type: "card",
|
type: "card",
|
||||||
card: cardElement,
|
card: cardNumberElement,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@@ -22,14 +22,16 @@
|
|||||||
<th>Ticket#</th>
|
<th>Ticket#</th>
|
||||||
<th>Owner</th>
|
<th>Owner</th>
|
||||||
<th>Offered To</th>
|
<th>Offered To</th>
|
||||||
|
<th>Paid?</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
<% for (const t in tickets) { %>
|
<% for (const t in tickets) { %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%=t%></td>
|
<td><a href="oneticket?t=<%=t%>"><%=t%></a></td>
|
||||||
<td><input type="text" class="owner" value="<%=tickets[t].owner%>" name="<%=t%>-owner"> </td>
|
<td><input type="text" class="owner" value="<%=tickets[t].owner%>" name="<%=t%>-owner"> </td>
|
||||||
<td><input type="text" class="offered" value="<%=tickets[t].offered%>" name="<%=t%>-offered"></td>
|
<td><input type="text" class="offered" value="<%=tickets[t].offered%>" name="<%=t%>-offered"></td>
|
||||||
|
<td><%=tickets[t].paid>0 ? (tickets[t].paid/100).toFixed(2) : "No"%>
|
||||||
<td>
|
<td>
|
||||||
<select class="status" name="<%=t%>-status">
|
<select class="status" name="<%=t%>-status">
|
||||||
<option value="i"<%=tickets[t].status=="i" ? " selected" : ""%>>Issued</option>
|
<option value="i"<%=tickets[t].status=="i" ? " selected" : ""%>>Issued</option>
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Error Page</title>
|
<title>Error</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Some kind of error</h1>
|
<%- include('partials/nav') %>
|
||||||
|
<div class="content">
|
||||||
|
An error has occurred.
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -20,12 +20,14 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Ticket#</th>
|
<th>Ticket#</th>
|
||||||
<th>Offered To</th>
|
<th>Offered To</th>
|
||||||
|
<th>Paid?</th>
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
<% for (const t in tickets) { %>
|
<% for (const t in tickets) { %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%=t%></td>
|
<td><a href="oneticket?t=<%=t%>"><%=t%></a></td>
|
||||||
<td><input type="text" class="offered" value="<%=tickets[t].offered%>" name="<%=t%>"></td>
|
<td><input type="text" class="offered" value="<%=tickets[t].offered%>" name="<%=t%>"></td>
|
||||||
|
<td><%=tickets[t].paid>0 ? (tickets[t].paid/100).toFixed(2) : "No"%>
|
||||||
<td><button id="<%=t%>-action" type="button"> QRCode </button></td>
|
<td><button id="<%=t%>-action" type="button"> QRCode </button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|||||||
@@ -7,11 +7,22 @@
|
|||||||
<body>
|
<body>
|
||||||
<%- include('partials/nav') %>
|
<%- include('partials/nav') %>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
To use <%=ticket%>, scan this QR Code:<br>
|
To use <%=ticket%>, visit this URL: <%=useurl%><br>
|
||||||
|
Or scan this QR Code:<br>
|
||||||
<img class="qrcode-image" width=300 height=300 src="<%=qrcode%>" alt="QR Code"><br>
|
<img class="qrcode-image" width=300 height=300 src="<%=qrcode%>" alt="QR Code"><br>
|
||||||
Or visit this URL: "<%=useurl%>"
|
<% if (paid==0) { %>
|
||||||
|
Tickets are pay-what-you-can, minimum $1, suggested $50.<br>
|
||||||
|
To pay for this ticket by credit card, enter an amount:
|
||||||
|
<form id="pay" method="POST" action="/pay0">
|
||||||
|
<input type="hidden" name="ticket" value="<%=ticket%>">
|
||||||
|
<input type="number" min="1.00" max="1000.00" step="0.01" placeholder="50.00" name="amount">
|
||||||
|
<button id="Submit" type="submit">Pay</button>
|
||||||
|
</form>
|
||||||
|
<% } else { %>
|
||||||
|
This ticket has already been paid for. ($<%=(paid/100).toFixed(2)%>)
|
||||||
|
<% } %>
|
||||||
<form id="editor" method="POST" action="/oneticket">
|
<form id="editor" method="POST" action="/oneticket">
|
||||||
Or transfer <%=ticket%> to:<br>
|
To transfer <%=ticket%>, enter the recipient's email address:<br>
|
||||||
<input type="hidden" name="ticket" value="<%=ticket%>">
|
<input type="hidden" name="ticket" value="<%=ticket%>">
|
||||||
<input type="email" placeholder="yourfriend@xyz.com" value="<%=offered%>" name="offered">
|
<input type="email" placeholder="yourfriend@xyz.com" value="<%=offered%>" name="offered">
|
||||||
<button id="Submit" type="submit">Transfer</button>
|
<button id="Submit" type="submit">Transfer</button>
|
||||||
|
|||||||
103
views/pay.ejs
Normal file
103
views/pay.ejs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Your Ticket</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include('partials/nav') %>
|
||||||
|
<div class="content">
|
||||||
|
<form id="payment-form">
|
||||||
|
Payment Here!!!<%=amount%>
|
||||||
|
<!-- Stripe Elements will inject the Card Element here -->
|
||||||
|
|
||||||
|
<label for="card-number-element">Card Number</label>
|
||||||
|
<div id="card-number-element"></div>
|
||||||
|
|
||||||
|
<label for="card-expiry-element">Expiration Date</label>
|
||||||
|
<div id="card-expiry-element"></div>
|
||||||
|
|
||||||
|
<label for="card-cvc-element">CVC</label>
|
||||||
|
<div id="card-cvc-element"></div>
|
||||||
|
|
||||||
|
<label for="card-postal-element">ZIP Code</label>
|
||||||
|
<div id="card-postal-element"></div>
|
||||||
|
|
||||||
|
<button id="submit-button" type="submit">Pay <%=amount%> Now</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- Stripe.js -->
|
||||||
|
<script src="https://js.stripe.com/v3/"></script>
|
||||||
|
<script>
|
||||||
|
// Initialize Stripe
|
||||||
|
const stripe = Stripe("pk_test_51QZlMSCHHjDgpHosSrxdAEHgUCBmyhXCihiELaXjxcXzGtIY9Vw1YgOeiJfNpn7P9WkcMj5FWk13cDRavez3HhyN0001mNBtXW");
|
||||||
|
|
||||||
|
// Set up Stripe.js and Elements
|
||||||
|
const elements = stripe.elements();
|
||||||
|
const cardNumberElement = elements.create('cardNumber');
|
||||||
|
const cardExpiryElement = elements.create('cardExpiry');
|
||||||
|
const cardCvcElement = elements.create('cardCvc');
|
||||||
|
const cardPostalElement = elements.create('postalCode');
|
||||||
|
|
||||||
|
cardNumberElement.mount('#card-number-element');
|
||||||
|
cardExpiryElement.mount('#card-expiry-element');
|
||||||
|
cardCvcElement .mount('#card-cvc-element');
|
||||||
|
cardPostalElement.mount('#card-postal-element');
|
||||||
|
|
||||||
|
const paymentForm = document.getElementById("payment-form");
|
||||||
|
paymentForm.addEventListener("submit", async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// 1. Create a PaymentMethod
|
||||||
|
const { paymentMethod, error } = await stripe.createPaymentMethod({
|
||||||
|
type: "card",
|
||||||
|
card: cardNumberElement,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
alert(error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Send PaymentMethod ID to server
|
||||||
|
const response = await fetch("/charge", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ paymentMethodId: paymentMethod.id, ticket:"<%=ticket%>", amount:"<%=amount%>" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const responseData = await response.json();
|
||||||
|
|
||||||
|
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 (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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user