Build a sports club subscription system by creating Paystack Plans for each membership tier (junior/senior/family), subscribing members via the transaction initialize endpoint, issuing a QR membership card on subscription.create, and gating facility access and session bookings behind active subscription status. Handle grace periods on invoice.payment_failed before suspending access.
Tables: membership_plans (name, tier, monthly_fee, annual_registration_fee, max_dependants, paystack_plan_code), members (name, email, phone, dob, photo_url, membership_number, plan_id, paystack_subscription_code, status, card_qr_code, access_suspended_at), training_sessions (coach_id, name, sport, day_of_week, time, max_participants, location), session_bookings (member_id, session_id, date, status).
var QRCode = require('qrcode');
async function enrollMember(memberEmail, memberData, planName) {
var plan = await db.membershipPlans.findByName(planName);
// Collect annual registration fee first (one-time)
var regReference = 'REG-' + 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: memberEmail,
amount: plan.annual_registration_fee,
currency: 'NGN',
reference: regReference,
// After registration fee, redirect to monthly plan setup
callback_url: 'https://yourclub.com/enroll/subscription?email=' + memberEmail + '&plan=' + planName,
metadata: { step: 'registration', email: memberEmail, plan_name: planName, ...memberData },
}),
});
return (await res.json()).data.authorization_url;
}
// After registration fee paid, set up monthly subscription
if (event.event === 'charge.success' && event.data.metadata.step === 'registration') {
var meta = event.data.metadata;
var memberNumber = 'CLB-' + new Date().getFullYear() + '-' + Math.floor(Math.random() * 9000 + 1000);
var member = await db.members.create({
...meta, membership_number: memberNumber, plan_id: meta.plan_id, status: 'pending_subscription',
});
// Redirect to Paystack subscription setup is handled via callback_url
}
// Monthly subscription created
if (event.event === 'subscription.create') {
var meta = event.data.metadata;
var member = await db.members.findByEmail(meta.email);
var qrData = JSON.stringify({ member_id: member.id, membership_number: member.membership_number });
var qrCode = await QRCode.toDataURL(qrData);
await db.members.update(member.id, {
paystack_subscription_code: event.data.subscription_code,
status: 'active',
card_qr_code: qrCode,
});
await sendMembershipCard(member.email, member.name, member.membership_number, qrCode);
}
// Scan QR at facility entrance
async function validateMemberAccess(qrData) {
var data = JSON.parse(qrData);
var member = await db.members.findById(data.member_id);
if (!member || member.membership_number !== data.membership_number) {
throw { status: 403, message: 'Invalid membership card.' };
}
if (member.status !== 'active') {
throw { status: 403, message: 'Membership ' + member.status + '. Please pay outstanding dues.' };
}
await db.accessLog.create({ member_id: member.id, accessed_at: new Date() });
return { valid: true, name: member.name, plan: member.plan_name };
}
// Book a training session
async function bookSession(memberId, sessionId, date) {
var member = await db.members.findById(memberId);
if (member.status !== 'active') throw new Error('Active membership required to book sessions');
var session = await db.trainingSessions.findById(sessionId);
var booked = await db.sessionBookings.countBySessionAndDate(sessionId, date);
if (booked >= session.max_participants) throw new Error('Session is full');
await db.sessionBookings.create({ member_id: memberId, session_id: sessionId, date, status: 'booked' });
await sendBookingConfirmation(member.phone, session.name, date, session.time, session.location);
}
// Subscription payment failed — grace period
if (event.event === 'invoice.payment_failed') {
var code = event.data.subscription.subscription_code;
var member = await db.members.findBySubscription(code);
await db.members.update(member.id, { status: 'grace_period', access_suspended_at: addDays(new Date(), 5) });
await sendSMS(member.phone, 'Club subscription payment failed. Access continues for 5 days. Pay now to avoid suspension.');
}
if (event.event === 'subscription.disable') {
var code = event.data.subscription_code;
var member = await db.members.findBySubscription(code);
if (member) await db.members.update(member.id, { status: 'lapsed' });
}
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 gym membership app for a simpler single-tier membership model without session booking or family plans.
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