Build a Barbershop Appointment and Payment App
Build a barbershop appointment app by managing time slots per barber, requiring a deposit (NGN 500-1,000) to confirm the booking via Paystack, and releasing the slot back to available if payment does not complete within 15 minutes. The deposit is applied toward the final bill; clients forfeit it if they no-show without 2 hours notice.
Slot Management and Booking
Tables: barbers (name, specialty, avatar_url), services (name, duration_mins, price), slots (barber_id, date, start_time, end_time, status, booking_id), bookings (client_name, client_phone, barber_id, service_id, slot_id, deposit_paid, status, paystack_ref).
async function getAvailableSlots(barberId, date, serviceDurationMins) {
return db.slots.findAll({
barber_id: barberId, date, status: 'available',
duration_gte: serviceDurationMins, // only slots long enough for the service
});
}
async function holdAndPay(clientData, barberId, serviceId, slotId) {
var service = await db.services.findById(serviceId);
var slot = await db.slots.findById(slotId);
if (slot.status !== 'available') throw new Error('Slot already booked');
var deposit = 50000; // NGN 500 in kobo
var reference = 'BARBER-' + Date.now();
// Hold the slot
await db.slots.update(slotId, { status: 'held', held_until: new Date(Date.now() + 15 * 60000) });
var booking = await db.bookings.create({
...clientData, barber_id: barberId, service_id: serviceId, slot_id: slotId,
deposit_paid: deposit, 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: clientData.email, amount: deposit, currency: 'NGN', reference,
metadata: { booking_id: booking.id, slot_id: slotId, service: service.name },
}),
});
return (await res.json()).data.authorization_url;
}
// Cron: release held slots where payment did not complete
async function releaseExpiredHolds() {
var expiredSlots = await db.slots.findAll({ status: 'held', held_until_lt: new Date() });
for (var slot of expiredSlots) {
await db.slots.update(slot.id, { status: 'available', held_until: null });
await db.bookings.update({ slot_id: slot.id, status: 'pending' }, { status: 'expired' });
}
}
Confirmation and Appointment Reminders
if (event.event === 'charge.success') {
var bookingId = event.data.metadata.booking_id;
var slotId = event.data.metadata.slot_id;
await db.bookings.update(bookingId, { status: 'confirmed' });
await db.slots.update(slotId, { status: 'booked', booking_id: bookingId, held_until: null });
var booking = await db.bookings.findById(bookingId);
var slot = await db.slots.findById(slotId);
// Send confirmation SMS
await sendSMS(booking.client_phone, 'Booking confirmed! ' + slot.date + ' at ' + slot.start_time + '. Deposit: NGN ' + (event.data.amount / 100) + '. See you soon!');
// Schedule reminder SMS 2 hours before appointment
await scheduleReminder(booking.client_phone, slot.date, slot.start_time);
}
For no-show handling: if the client does not arrive within 30 minutes of their slot, mark the booking as "no_show". The deposit is forfeited (it stays in your account). Offer the barber a small compensation from the forfeited deposit as an incentive for holding the slot.
Learn More
See build a photography booking and deposit flow for the deposit pattern applied to creative service bookings.
Key Takeaways
- ✓Require a small deposit (NGN 500-1,000) at booking to dramatically reduce no-shows.
- ✓Slot management per barber: each barber has their own calendar of available slots.
- ✓Auto-release booked slots after 15 minutes if payment does not complete.
- ✓Apply the deposit toward the final bill — clients pay only the balance at the shop.
- ✓For multi-branch barbershops, use Paystack subaccounts to route each branch's deposits to their account.
Frequently Asked Questions
- Should the deposit be refundable if the client cancels in advance?
- Offer free cancellation up to 2 hours before the appointment — full deposit refund via Paystack Refund API. Within 2 hours, the deposit is forfeited. This policy protects the barber's time while being fair to clients who have genuine reasons to cancel with enough notice.
- How do I handle walk-in clients who do not book online?
- Walk-in clients pay at the counter (cash or POS). Create a simple admin panel for barbers to manually mark slots as "walk-in" with cash payment noted. This keeps your slot calendar accurate for online bookings and lets you track revenue from both channels.
- Can I build this for a multi-branch barbershop?
- Yes. Add a branches table and link barbers and slots to branches. On the booking page, let clients select a branch first, then choose a barber at that branch. If you want each branch to receive their own payouts, create a Paystack subaccount per branch and route deposits accordingly.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon