Build a coworking booking system by managing resource calendars (desks and meeting rooms with capacity and hourly/daily rates), requiring Paystack payment before issuing an access code or QR pass, and offering both day passes (one-time payment) and monthly memberships (Paystack Plans). Webhook confirmation triggers the access credential generation.
Tables: resources (name, type, capacity, hourly_rate, daily_rate, amenities[]), bookings (member_id, resource_id, start_time, end_time, duration_hours, total_amount, status, access_code, paystack_ref), memberships (member_id, plan_type, status, meeting_room_hours_remaining, paystack_subscription_code).
async function bookResource(memberEmail, memberId, resourceId, startTime, endTime) {
var resource = await db.resources.findById(resourceId);
var hours = Math.ceil((new Date(endTime) - new Date(startTime)) / (1000 * 60 * 60));
// Check availability
var conflicts = await db.bookings.findConflicts(resourceId, startTime, endTime);
if (conflicts.length > 0) throw new Error('Resource not available for selected time');
// Check if member has a monthly bundle with remaining hours
var membership = await db.memberships.findActiveByMember(memberId);
if (membership && resource.type === 'meeting_room' && membership.meeting_room_hours_remaining >= hours) {
// Deduct from bundle — no payment needed
await db.memberships.decrement(memberId, 'meeting_room_hours_remaining', hours);
return await createBookingWithAccess(memberId, resourceId, startTime, endTime, hours, 0, null);
}
var amount = resource.type === 'hot_desk' ?
(hours >= 8 ? resource.daily_rate : hours * resource.hourly_rate) :
hours * resource.hourly_rate;
var reference = 'CWK-' + resourceId + '-' + Date.now();
var booking = await db.bookings.create({
member_id: memberId, resource_id: resourceId, start_time: startTime, end_time: endTime,
duration_hours: hours, total_amount: amount, 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: memberEmail, amount, currency: 'NGN', reference,
metadata: { booking_id: booking.id, resource_id: resourceId, start_time: startTime },
}),
});
return (await res.json()).data.authorization_url;
}
// Webhook: confirm booking and issue access
if (event.event === 'charge.success') {
var meta = event.data.metadata;
if (!meta.booking_id) return res.sendStatus(200);
var accessCode = Math.random().toString(36).slice(2, 8).toUpperCase(); // 6-char code
await db.bookings.update(meta.booking_id, { status: 'confirmed', access_code: accessCode });
await sendAccessCode(meta.booking_id, accessCode, meta.start_time);
}
// Monthly membership subscription
async function subscribeToMembership(memberEmail, memberId, planName) {
var PLANS = {
hot_desk_monthly: { price: 2500000, meeting_room_hours: 0, paystack_plan_code: process.env.HOT_DESK_PLAN },
pro_monthly: { price: 4500000, meeting_room_hours: 10, paystack_plan_code: process.env.PRO_PLAN },
};
var plan = PLANS[planName];
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: memberEmail, amount: plan.price,
plan: plan.paystack_plan_code,
metadata: { member_id: memberId, plan_name: planName, meeting_room_hours: plan.meeting_room_hours },
}),
});
return (await res.json()).data.authorization_url;
}
if (event.event === 'subscription.create') {
var meta = event.data.metadata;
await db.memberships.upsert({
member_id: meta.member_id,
plan_type: meta.plan_name,
status: 'active',
meeting_room_hours_remaining: meta.meeting_room_hours,
paystack_subscription_code: event.data.subscription_code,
});
}
// Renew meeting room hours each billing cycle
if (event.event === 'invoice.create' && event.data.status === 'success') {
var code = event.data.subscription.subscription_code;
var membership = await db.memberships.findBySubscription(code);
if (membership.plan_type === 'pro_monthly') {
await db.memberships.update(membership.id, { meeting_room_hours_remaining: 10 }); // reset monthly
}
}
// Validate access at the door (QR scan or code entry)
async function validateEntry(accessCode, resourceId) {
var booking = await db.bookings.findByAccessCode(accessCode);
if (!booking || booking.resource_id !== parseInt(resourceId)) throw { status: 403, message: 'Invalid access code.' };
var now = new Date();
var start = new Date(booking.start_time);
var end = new Date(booking.end_time);
if (now < new Date(start.getTime() - 15 * 60000)) throw { status: 403, message: 'Too early — entry allowed 15 min before booking.' };
if (now > end) throw { status: 403, message: 'Booking has expired.' };
return { valid: true, member_id: booking.member_id, resource_name: booking.resource.name };
}
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 hotel booking and deposit system for a similar resource booking pattern with deposit collection and balance at checkout.
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