Build a vet clinic booking system by managing appointment slots per vet, requiring a deposit (NGN 500-2,000) at booking time via Paystack, confirming the slot only after the charge.success webhook fires, and maintaining a pet health record per patient with vaccination history and upcoming due dates for automated reminders.
Tables: vets (name, specialty, avatar_url), services (name, duration_mins, base_price), slots (vet_id, date, start_time, status, booking_id), bookings (owner_id, pet_id, vet_id, service_id, slot_id, deposit_amount, total_estimate, status, paystack_ref), pets (owner_id, name, species, breed, dob, weight_kg, vaccination_records[]).
async function bookAppointment(ownerEmail, ownerId, petId, vetId, serviceId, slotId) {
var slot = await db.slots.findById(slotId);
if (slot.status !== 'available') throw new Error('Slot not available');
var service = await db.services.findById(serviceId);
var deposit = 100000; // NGN 1,000 deposit
var reference = 'VET-' + 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({
owner_id: ownerId, pet_id: petId, vet_id: vetId, service_id: serviceId, slot_id: slotId,
deposit_amount: deposit, total_estimate: service.base_price,
status: 'pending_payment', 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: ownerEmail, amount: deposit, currency: 'NGN', reference,
metadata: { booking_id: booking.id, slot_id: slotId, pet_id: petId },
}),
});
return (await res.json()).data.authorization_url;
}
// Webhook: confirm booking on deposit payment
if (event.event === 'charge.success') {
var meta = event.data.metadata;
if (!meta.booking_id) return res.sendStatus(200);
await db.slots.update(meta.slot_id, { status: 'booked', held_until: null });
await db.bookings.update(meta.booking_id, { status: 'confirmed' });
var booking = await db.bookings.findById(meta.booking_id);
var pet = await db.pets.findById(meta.pet_id);
var slot = await db.slots.findById(meta.slot_id);
await sendConfirmationSMS(booking.owner_id, pet.name, slot.date, slot.start_time);
}
// Cron: release expired holds
async function releaseExpiredHolds() {
var expired = await db.slots.findAll({ status: 'held', held_until_lt: new Date() });
for (var slot of expired) {
await db.slots.update(slot.id, { status: 'available', held_until: null });
await db.bookings.update({ slot_id: slot.id, status: 'pending_payment' }, { status: 'expired' });
}
}
// Record vaccination at clinic visit
async function recordVaccination(petId, vaccineName, doseNumber, nextDueDate) {
await db.pets.addVaccinationRecord(petId, {
vaccine: vaccineName,
dose: doseNumber,
administered_at: new Date(),
next_due_date: nextDueDate,
});
}
// Cron: check upcoming vaccinations and send reminders
async function sendVaccinationReminders() {
var oneWeekAhead = new Date();
oneWeekAhead.setDate(oneWeekAhead.getDate() + 7);
var petsDue = await db.pets.findVaccinationsDueBefore(oneWeekAhead);
for (var record of petsDue) {
var pet = await db.pets.findById(record.pet_id);
var owner = await db.owners.findById(pet.owner_id);
await sendSMS(owner.phone,
pet.name + ''s ' + record.vaccine + ' (dose ' + record.dose + ') is due on ' +
record.next_due_date.toDateString() + '. Book: yourvet.com/book'
);
await db.pets.markReminderSent(record.id);
}
}
// Balance payment after clinic visit (via link or card swipe)
async function requestBalancePayment(bookingId, ownerEmail, actualAmount) {
var booking = await db.bookings.findById(bookingId);
var balanceDue = actualAmount - booking.deposit_amount;
if (balanceDue <= 0) {
await db.bookings.update(bookingId, { status: 'paid_in_full' });
return null; // deposit covered everything
}
var reference = 'VETBAL-' + bookingId + '-' + Date.now();
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: ownerEmail, amount: balanceDue, reference,
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 barbershop appointment and payment app for the same slot-holding deposit pattern applied to personal care 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