Build a cinema ticketing system by managing seats with a reserved state that times out unpaid reservations, collecting payment via Paystack before confirming the ticket, and generating a QR code with the booking reference for door scanning. Use webhooks to confirm payment before marking tickets as confirmed.
Tables: movies, showtimes (movie_id, hall, date, time, seat_map), seats (showtime_id, row, number, category, price, status, reserved_by, reserved_until), bookings (customer_email, showtime_id, seat_ids[], total, status, paystack_ref), tickets (booking_id, seat_id, qr_code).
async function reserveSeats(customerId, showtimeId, seatIds) {
// Check all seats are available
var seats = await db.seats.findMany(seatIds);
var unavailable = seats.filter(s => s.status !== 'available');
if (unavailable.length > 0) throw new Error('Some seats are no longer available');
var reservedUntil = new Date(Date.now() + 10 * 60 * 1000); // 10 min
// Atomic update using transaction
await db.transaction(async (trx) => {
for (var seatId of seatIds) {
await trx.seats.update(seatId, {
status: 'reserved',
reserved_by: customerId,
reserved_until: reservedUntil,
});
}
});
var total = seats.reduce((sum, s) => sum + s.price, 0);
return { total, reservedUntil };
}
// Cron job: release expired reservations every minute
async function releaseExpiredReservations() {
await db.seats.updateWhere(
{ status: 'reserved', reserved_until_lt: new Date() },
{ status: 'available', reserved_by: null, reserved_until: null }
);
}
var QRCode = require('qrcode');
async function initiatePayment(customerId, customerEmail, showtimeId, seatIds) {
var seats = await db.seats.findMany(seatIds);
var total = seats.reduce((sum, s) => sum + s.price, 0);
var reference = 'TKT-' + Date.now();
var booking = await db.bookings.create({
customer_email: customerEmail, showtime_id: showtimeId,
seat_ids: seatIds, total, status: 'pending', paystack_ref: reference,
});
var res = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
email: customerEmail, amount: total, currency: 'NGN',
reference, metadata: { booking_id: booking.id },
}),
});
return (await res.json()).data.authorization_url;
}
// Webhook: confirm booking and generate tickets
if (event.event === 'charge.success') {
var bookingId = event.data.metadata.booking_id;
var booking = await db.bookings.findById(bookingId);
await db.bookings.update(bookingId, { status: 'confirmed' });
await db.seats.updateMany(booking.seat_ids, { status: 'sold' });
// Generate one QR code per seat
for (var seatId of booking.seat_ids) {
var ticketRef = bookingId + '-' + seatId;
var qrCode = await QRCode.toDataURL(ticketRef); // base64 PNG
await db.tickets.create({ booking_id: bookingId, seat_id: seatId, qr_code: qrCode, ref: ticketRef });
}
await sendTicketEmail(booking.customer_email, bookingId);
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
See build a ticketed webinar platform for the same payment-before-access pattern applied to online events.
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course