Build a hotel booking system by managing room availability in a calendar data model, collecting a deposit via Paystack at booking confirmation, tracking the outstanding balance, and collecting the remainder on checkout. Use Paystack webhooks to confirm deposit payment before reserving the room, and the Refund API for cancellations.
Tables: rooms (hotel_id, type, price_per_night, capacity, amenities), bookings (room_id, guest_email, check_in, check_out, nights, total_amount, deposit_amount, balance_due, status, paystack_deposit_ref, paystack_balance_ref), blocked_dates (room_id, date — one row per blocked night).
async function checkAvailability(roomId, checkIn, checkOut) {
var nights = Math.ceil((new Date(checkOut) - new Date(checkIn)) / 86400000);
var dates = [];
for (var i = 0; i < nights; i++) {
var d = new Date(checkIn);
d.setDate(d.getDate() + i);
dates.push(d.toISOString().split('T')[0]);
}
var blocked = await db.query(
'SELECT date FROM blocked_dates WHERE room_id = $1 AND date = ANY($2::date[])',
[roomId, dates]
);
return blocked.length === 0;
}
async function createBooking(guestEmail, roomId, checkIn, checkOut) {
var available = await checkAvailability(roomId, checkIn, checkOut);
if (!available) throw new Error('Room not available for those dates');
var room = await db.rooms.findById(roomId);
var nights = Math.ceil((new Date(checkOut) - new Date(checkIn)) / 86400000);
var total = room.price_per_night * nights;
var deposit = Math.ceil(total * 0.3); // 30% deposit
var booking = await db.bookings.create({
room_id: roomId, guest_email: guestEmail,
check_in: checkIn, check_out: checkOut, nights,
total_amount: total, deposit_amount: deposit,
balance_due: total - deposit, status: 'pending',
});
// Initialize deposit payment
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: guestEmail, amount: deposit, currency: 'NGN',
reference: 'DEPOSIT-' + booking.id + '-' + Date.now(),
metadata: { booking_id: booking.id, payment_type: 'deposit' },
}),
});
var data = await res.json();
return { booking, authorizationUrl: data.data.authorization_url };
}
// Webhook: confirm deposit and block dates
if (event.event === 'charge.success') {
var meta = event.data.metadata;
var booking = await db.bookings.findById(meta.booking_id);
if (meta.payment_type === 'deposit' && booking.status === 'pending') {
await db.bookings.update(booking.id, { status: 'confirmed', paystack_deposit_ref: event.data.reference });
// Block each night of the booking
var dates = getDatesInRange(booking.check_in, booking.check_out);
for (var date of dates) {
await db.blockedDates.create({ room_id: booking.room_id, date });
}
}
if (meta.payment_type === 'balance') {
await db.bookings.update(booking.id, { status: 'fully_paid', paystack_balance_ref: event.data.reference });
}
}
// Collect balance at checkout
async function collectCheckoutBalance(bookingId) {
var booking = await db.bookings.findById(bookingId);
if (booking.status !== 'confirmed') throw new Error('Booking not confirmed');
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: booking.guest_email, amount: booking.balance_due, currency: 'NGN',
metadata: { booking_id: bookingId, payment_type: 'balance' },
}),
});
return (await res.json()).data.authorization_url;
}
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 photography booking and deposit flow for the same deposit pattern applied to service bookings.
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