Build a Parking Payment App
Build a parking payment app by creating sessions on entry (with time-in recorded), calculating fees based on duration at exit, and collecting payment via Paystack before releasing the vehicle. Use a QR code or number plate lookup at entry to create the session. At exit, calculate the fee, initialize a Paystack transaction, and update the session to paid when the webhook fires.
Architecture and Session Flow
A parking payment app manages sessions between vehicle entry and exit. The core data is simple: when did the vehicle enter, when did it exit, how much does it owe, and has it paid?
The flow:
- Driver scans QR code (or attendant enters plate number) at entry
- System creates a parking session with entry timestamp and vehicle identifier
- At exit, driver requests payment — system calculates fee based on duration
- Driver pays via Paystack on their phone or a kiosk
- Webhook confirms payment — system updates session to paid and signals barrier open
- Vehicle exits within a grace period (say 5 minutes after payment)
Database tables: parking_lots (location, rate per hour, max daily rate), sessions (plate, lot_id, entry_at, exit_at, fee_amount, status, paystack_reference), rates (lot_id, period, rate — for peak/off-peak pricing).
Fee Calculation and Paystack Integration
function calculateParkingFee(entryAt, exitAt, ratePerHour, maxDailyRate) {
var minutes = Math.ceil((exitAt - entryAt) / 60000); // ms to minutes
var hours = Math.ceil(minutes / 60); // round up to nearest hour
var fee = hours * ratePerHour;
return Math.min(fee, maxDailyRate); // cap at daily max
}
async function initiateExitPayment(sessionId, driverEmail) {
var session = await db.sessions.findById(sessionId);
var lot = await db.parkingLots.findById(session.lot_id);
var now = Date.now();
var fee = calculateParkingFee(session.entry_at, now, lot.rate_per_hour, lot.max_daily_rate);
var reference = 'PARK-' + sessionId + '-' + Date.now();
await db.sessions.update(sessionId, { fee_amount: fee, paystack_reference: reference });
var response = 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: driverEmail,
amount: fee, // in kobo/cents
currency: 'KES',
reference: reference,
metadata: { session_id: sessionId, plate: session.plate, lot_name: lot.name },
}),
});
var data = await response.json();
return data.data.authorization_url; // send to driver's phone
}
The webhook handler verifies the signature, finds the session by reference, marks it paid, and signals the barrier controller (via GPIO, serial port, or REST API depending on your hardware).
if (event.event === 'charge.success') {
var sessionId = event.data.metadata.session_id;
await db.sessions.update(sessionId, { status: 'paid', exit_at: new Date(), paystack_txn: event.data.id });
await barrierController.open(sessionId); // trigger exit barrier
}
Learn More
See build a vehicle inspection booking and payment app for another government/transport sector Paystack integration.
Key Takeaways
- ✓Record entry time on arrival; calculate fee at exit based on elapsed time and your rate table.
- ✓Collect payment before opening the exit barrier — do not release the vehicle on a pending payment.
- ✓Use Paystack webhook to confirm payment, not the redirect, before triggering barrier release.
- ✓Support M-Pesa in Kenya and card/bank transfer in Nigeria — the same Paystack initialization handles both.
- ✓Overstay is a common edge case: define a grace period and an overstay rate per hour.
Frequently Asked Questions
- What if a driver does not have a smartphone to pay at the exit?
- Install a payment kiosk at the exit with a touchscreen that runs a simple web app connected to your backend. The driver enters their plate number; the kiosk shows the fee and a Paystack payment link they can scan with their phone, or they pay by card on a POS terminal connected to Paystack Terminal. For low-tech lots, an attendant with a tablet works too.
- How do I handle the grace period between payment and exit?
- When payment is confirmed, record the payment_confirmed_at timestamp. The barrier opens and stays openable for a grace period (5-10 minutes). If the vehicle does not exit in that window, the session resets to unpaid and the driver must pay again. This prevents someone paying for another person's car to get out free.
- Can I offer monthly parking subscriptions?
- Yes. Use Paystack Plans to create a monthly subscription. On entry, check if the plate number is linked to an active subscription — if so, create the session and mark it as subscription-covered without requiring payment at exit. A cron job checks for lapsed subscriptions daily and flags them inactive.
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