# bookings — the post-payment engine, sessions and EVV > Client seam `client/src/services/bookings/` · `USE_BOOKINGS_MOCK = false` (**real**) · 16 server ops > Last verified: 2026-07-30 against commit `d3ec723` and swagger.v1.json (2026-07-29). Stage two: a paid booking, its per-visit sessions, and electronic visit verification. A `bookings` row is created **only on payment capture**, from an accepted [booking request](booking-requests.md). ## Endpoints | Method | Path | Caller | Verdict | | --- | --- | --- | --- | | GET | `/api/v1/bookings/list` | both | wired · paginated · `role=customer\|nurse\|all` | | GET | `/api/v1/bookings/get/{id}` | both | wired | | GET | `/api/v1/bookings/care_instructions/{id}` | nurse, admin | wired · **stage-2 disclosure gate** | | POST | `/api/v1/bookings/submit_care_instructions/{id}` | customer | **unwired** — the client has no care-instructions form | | POST | `/api/v1/bookings/convert` | — | **unwired** — Development-only capture simulator; the PSP webhook supersedes it | | POST | `/api/v1/bookings/transition/{id}` | admin | **unwired** — a raw state-machine escape hatch, no UI | | POST | `/api/v1/bookings/cancel/{id}` | — | **unwired, superseded** by `{id}/cancel` | | POST | `/api/v1/bookings/{id}/cancel` | customer | wired — by **[refunds](refunds.md)** (cancel *and* refund, REQ-019) | | GET | `/api/v1/bookings/{id}/cancellation_policy` | customer | wired — by **[refunds](refunds.md)** (REQ-020) | | GET | `/api/v1/booking_sessions/today` | nurse | wired · paginated | | GET | `/api/v1/booking_sessions/evv/{id}` | nurse | wired | | POST | `/api/v1/booking_sessions/check_in/{id}` | nurse | wired | | POST | `/api/v1/booking_sessions/check_out/{id}` | nurse | wired · `sensitive` 20/min — **this is what releases the payout clock** | | POST | `/api/v1/booking_sessions/cancel/{id}` | nurse | **unwired** — no per-session cancel in the UI | | GET | `/api/v1/admin_evv/list` | admin | **unwired** — no console screen; the mock demonstrates it | | POST | `/api/v1/admin_evv/detect_no_shows` | admin | **unwired** — an ops one-shot; the scheduler runs it | All `[Authorize]`; the two `admin_evv` routes are `DynamicPermission` + `sensitive`. No phantoms. > **Two cancel routes exist on the same controller.** `POST bookings/cancel/{id}` (action-style, b9) and > `POST bookings/{id}/cancel` (REST-style, b11 cancel-and-refund). Only the second is wired. They are > not aliases — the second also drives the refund. Treat the first as legacy. ## The lifecycle ``` pending_payment ──capture──▸ confirmed ──first check-in──▸ in_progress │ │ │ all sessions out ▼ ▼ cancelled ◂──cancel── completed ──dispute window──▸ closed └──dispute──▸ disputed ``` Forward-only, through the transition table. `status` has a private setter; only cohesive domain methods mutate it and the handler pre-checks, returning a clean `409`. Sessions run their own machine: `scheduled → in_progress → completed`, or `missed` / `cancelled`. ## Shape rules the JSON does not express - **Two-stage clinical disclosure is enforced server-side.** `care_instructions/{id}` decrypts and returns the care plan **only** post-confirmation and **only** to the assigned nurse or an admin. It is never projected into a list and never logged. The client's UI gate mirrors this; it does not create it. - **EVV is advisory, and `checkInAddressMatch` is a tri-state**: `true` (inside tolerance), `false` (outside), `null` (**no reading** — permission denied or unavailable). Null is not a failure. A mismatch does not block check-in; it raises a support alert (`evv_location_mismatch`, see [admin.md](admin.md)). Tolerance is `evv_location_tolerance_meters` config. REQ-015 confirmed the tri-state. - **`BookingDetailDto.variantSnapshotJson` and `addressSnapshotJson` are strings containing JSON**, not typed objects — the point of a snapshot is that later edits to the variant or address cannot rewrite history. The client parses defensively across multiple key spellings (REQ-045 open). - **The three-amount split is guaranteed by a DB CHECK**: `grossPriceIrr = balinyaarCommissionIrr + nursePayoutAmount`. `platformFeeRate` is **snapshotted onto the row** at compute time, so a later rate change is not retroactive. Never recompute any of these. - **`payoutEligibleAt` per session** is the payout clock, started by check-out and resolved against the holiday calendar server-side. See [payouts.md](payouts.md). - **`disputeWindowEndsAt`** gates `completed → closed`; `dispute_window_hours` is config. - `BookingListItemDto` is deliberately thin: `id, status, counterpartyName, scheduledDate, sessionCount, amountIrr, disputeWindowEndsAt, createdAt`. **No `patientId`** — which is what blocks REQ-057's care teaser. - `NEXT_PUBLIC_EVV_MOCK_GPS` overrides the GPS reading for local testing (`off` = real capture). See [../config-matrix.md](../config-matrix.md). ## Enums | Vocabulary | Values | | --- | --- | | `BookingStatus` | `pending_payment` `confirmed` `in_progress` `completed` `disputed` `closed` `cancelled` | | `BookingSessionStatus` | `scheduled` `in_progress` `completed` `missed` `cancelled` | | `VisitVerificationStatus` (`evvStatus`) | `pending` `checked_in` `completed` | | `BookingListRole` *(query param)* | `customer` `nurse` `all` | | `EvvGpsMode` *(client test knob)* | `off` `in_range` `out_of_range` `denied` | Verified identical to `Baya.Domain/Entities/Booking/*.cs`. ## Open REQs | REQ | Status | Effect | | --- | --- | --- | | REQ-045 | open | `variantSnapshot`/`addressSnapshot` are untyped JSON strings. The client keeps a defensive multi-key parse; no user-facing defect | | REQ-051 | open | The nurse view of a confirmed+ booking still masks the address. The nurse sees a quiet fallback note, not a crash | | REQ-052 | open | The today feed carries no service label — it renders patient name + visit index only | | REQ-054 | deferred | No web push for new requests; a 15 s poll remains the only signal | | REQ-057 | open | `BookingListItemDto` has no `patientId` and the patient read has no `lastVisitAt`, so the card renders no care teaser. See [patients.md](patients.md) |