Core flows
Booking engine
The heart of Parkezy: turning a driver's request into a guaranteed slot. Three ideas make it safe: a strict state machine, a database constraint that makes double booking impossible, and idempotent requests that make retries harmless.
The booking lifecycle#
A booking can only move along the arrows below. The rules live in one place (domain/booking_state.py), so the API, the background jobs and the admin tools can never disagree about what is allowed.
Swipe sideways to see the whole figure.
TRANSITIONS: dict[S, frozenset[S]] = { S.PENDING_APPROVAL: frozenset({S.PENDING_PAYMENT, S.CONFIRMED, S.REJECTED, S.CANCELLED, S.EXPIRED}), S.PENDING_PAYMENT: frozenset({S.CONFIRMED, S.CANCELLED, S.EXPIRED}), S.CONFIRMED: frozenset({S.CHECKED_IN, S.CANCELLED, S.EXPIRED}), S.CHECKED_IN: frozenset({S.COMPLETED}), S.COMPLETED: frozenset(), # terminal states accept nothing ...}def ensure_transition(current, target) -> None: if not can_transition(current, target): raise Conflict(f"Booking cannot move from {current} to {target}", code="invalid_booking_transition")From tap to confirmed#
Swipe sideways to see the whole figure.
Zero double booking, enforced by Postgres#
Checking availability in application code and then inserting is a race: two requests can both see the slot as free. Instead, the database refuses the second row. Each booking stores its time window as a range, and an exclusion constraint forbids two live bookings on the same slot whose ranges overlap.
-- The core integrity guarantee: one slot can never hold two overlapping live bookings.CONSTRAINT ex_bookings_no_overlap EXCLUDE USING gist (slot_id WITH =, period WITH &&) WHERE (status IN ('pending_approval', 'pending_payment', 'confirmed', 'checked_in'))Cancelled, rejected and expired bookings fall outside the WHERE clause, so their time frees up immediately. The service turns the violation into a clean 409; when the driver did not choose a specific slot, it quietly retries with the next free one.
Measured: 100 drivers, one slot, the same instant
Safe retries with Idempotency-Key#
Phones lose connections mid-request. If the app retries a booking after a timeout, the driver must not end up with two bookings. Every POST /v1/bookings carries an Idempotency-Key header, and a unique constraint on (driver_id, idempotency_key) means a repeat returns the original booking.
| Scenario | Result |
|---|---|
| 20 parallel retries with one Idempotency-Key | 1 booking, every response points at it |
| 20 parallel sign-ups with one email | 1 account, 19 × 409 |
| Same key reused for a different booking | 409 idempotency_mismatch: a key can never be recycled by accident |
Pricing, server-side#
- Times align to 15-minute steps; bookings run from 30 minutes to 7 days, up to 30 days ahead.
- Price is pro-rata by the minute, with an optional daily cap per 24-hour block. All money is
Decimal, rounded half-up to the paisa. - GST (18%) is added on the subtotal; the platform commission (15% private, 12% commercial) comes out of the host's share, not the driver's price.
- Free cancellation until 60 minutes before the start; after that 25% of the subtotal is kept.
- Overstay: 5 minutes of grace, then ₹20 (plus GST) for every started 15-minute block.