Problem
Drivers in Kenyan towns lose time circling for parking, and operators have no live picture of which bays are free. Most people pay with M-Pesa, so payment has to work on a phone, over an unreliable network.
What I built
- A live map of bays across Nairobi and other towns, with nearest-bay search from the driver's location (Haversine distance), an occupancy heatmap, and turn-by-turn routes from OSRM.
- Bookings that reserve a bay for 15 minutes, and sensor entry/exit events that keep each bay's status accurate.
- Live updates: every status change is broadcast over STOMP WebSockets, so every open map changes at once without refreshing.
- An admin console for occupancy, bookings and users.
Engineering decisions
- Payments that settle once. Safaricom can deliver the same M-Pesa callback more than once. A payment only moves from PENDING, so a repeat is ignored, and the amount paid is checked against the booking's price before it is accepted.
- An authenticated webhook. The callback has to be public, because Safaricom sends no token, so it is verified with a shared secret or Safaricom's IP ranges instead.
- Access control in two layers: stateless JWT with DRIVER and ADMIN roles in the security filter chain, plus @PreAuthorize on admin operations. The WebSocket channel checks the token too.
- A versioned schema: eight Flyway migrations own the database structure, so any environment can be rebuilt from scratch.
- Protection from abuse: a request rate-limiting filter, CORS rules, and every credential read from the environment.
Delivery
Dockerfiles and docker-compose for the whole stack, with Nginx proxying /api and /ws. Unit tests cover the distance calculations and the slot service.