McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Build a Hotel Booking and Deposit System
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Build a Hotel Booking and Deposit System

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.

In this article

  1. 1. Room Availability and Booking Flow
  2. 2. Deposit Confirmation and Checkout Balance
  3. 3. Learn More

Room Availability and Booking Flow

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 };
}

Deposit Confirmation and Checkout Balance

// 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.

Learn More

See build a photography booking and deposit flow for the same deposit pattern applied to service bookings.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Collect a deposit (30-50%) at booking to secure the reservation; balance on checkout.
  • ✓Only confirm the reservation after the deposit webhook fires — do not block rooms for unpaid bookings.
  • ✓Store check-in and check-out dates with availability logic to prevent double-booking.
  • ✓Implement a cancellation policy: free cancellation 48+ hours out, 50% deposit forfeit 24-48 hours, no refund within 24 hours.
  • ✓Use Paystack subaccounts if you run a multi-property platform where each hotel receives their own settlement.

Frequently Asked Questions

How do I prevent double-bookings between two concurrent requests?
Use a database transaction with a row-level lock on the blocked_dates table. When inserting blocked dates, wrap in a transaction and use SELECT FOR UPDATE on the room row. If two requests try to book the same dates simultaneously, one will wait for the lock and then see the dates are already blocked. PostgreSQL handles this naturally with proper transaction isolation.
How do I handle the cancellation refund based on timing?
On cancellation, calculate how far in advance the guest is cancelling (hours until check-in). Apply your policy: more than 48 hours = full deposit refund, 24-48 hours = 50% deposit refund, less than 24 hours = no refund. Use the Paystack Refund API with the deposit transaction reference and the refund amount. Log the refund in your bookings table with reason and amount.
Can I let multiple hotels use the same platform with their own bank accounts?
Yes. Create a Paystack subaccount for each hotel. When initializing the deposit or balance payment, include the hotel's subaccount code. Paystack routes the hotel's share to their bank account at settlement and your platform keeps the commission. The guest pays one total amount; Paystack handles the split.

Learn Paystack Integration

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

Not ready? Create Free Account

Keep reading

Things You Can Build with Paystack: Project Guides

A complete map of real-world projects you can build using the Paystack API. From online store checkouts and subscription platforms to savings apps and solar pay-as-you-go systems, each category links to step-by-step build guides with working code.

Read more

Build a Laundry Pickup and Payment App

A build guide for a laundry pickup and delivery app that uses Paystack to collect payments. Covers order pricing by weight or item count, pickup scheduling, status tracking through the wash cycle, payment collection at order creation or delivery, and customer notifications.

Read more

Build an Affiliate Commission Engine

A build guide for an affiliate commission engine where affiliates share tracking links, earn commissions when their referrals convert (pay via Paystack), and withdraw their balance via bank transfer. Covers click tracking, last-click attribution, commission ledger, and Paystack Transfers payout.

Read more

Build a Farmers Market Ordering Platform

A build guide for a farmers market ordering platform where customers order fresh produce from multiple farmers in one cart, the platform collects the combined payment via Paystack, and each farmer receives their share via Paystack Split Payments or Transfers. Covers inventory management, order routing to individual farmers, and delivery coordination.

Read more

M-Pesa STK Push Timeout Handling Compared Across Providers

The STK push timeout is the single most common M-Pesa failure mode in Kenya. Every payment provider handles it differently. This article compares how Paystack, direct Daraja, IntaSend, and Pesapal deal with the 30-second window, what happens when customers ignore the prompt, how money in limbo gets resolved, and what retry strategies actually work in production.

Read more

Subaccounts on Paystack: Complete Guide

Everything you need to know about Paystack subaccounts: creating them via the API, validating bank details before creation, setting default split ratios, updating bank details when vendors change banks, listing and filtering subaccounts, and managing them at scale in a production marketplace.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.