Booking Workspace & 2-Phase Holds

Distributed mutex locking, optimistic concurrency, and 15-minute checkout reservation holds.

The booking workspace manages the real-time reservation workflow, acquiring atomic holds to eliminate race conditions.

Two-Phase Hold Lifecycle

Temporary 15-minute reservations reserve slots during payment without race conditions.

Hold Expiration State TransitionsText
[User Selects Slot]
        │
        ▼ (POST /api/v1/venue/booking-holds)
[Hold Active (10-minute Lock Acquired)] ──(No payment after 10m)──► [Expired / Slot Released]
        │
        ▼ (User Submits Sealed Price Quote & Payment)
[Hold Committed -> Reservation Confirmed]
        │
        ▼ (Customer Arrives)
[Checked-In] ────► [Completed]

Safe Retries and Reservation Receipts

Keep the same idempotency key for retries of one booking intent. A matching successful request replays its stored result only after the caller's current tenant and venue operational scope are checked; it does not confirm the reservation or consume quota again. Reusing a key for a different reservation, quote, target, time range, or cancellation/no-show reason returns a conflict. In Booking 360, an unchanged retry retains its key; editing the cancellation or no-show reason starts a new intent with a new key, and a refreshed price quote starts a new confirmation intent. Older receipts without request fingerprints remain readable with narrower comparison guarantees. Venue mutations send the same key in the request body and `Idempotency-Key` header; the shared Axios service makes up to three retries for network/time-out failures and HTTP 500, 502, 503, or 504 responses, using a fresh anti-replay nonce on each attempt.

Client-Side State Reducer

Predictive UI state updates keeping slot displays synchronized across multiple active sessions.

src/modules/venue/booking/src/presentation/viewmodels/bookingWorkspaceState.tsTypeScript
export function reduceBookingWorkspace(
  state: BookingWorkspaceState,
  action: BookingWorkspaceAction
): BookingWorkspaceState {
  switch (action.type) {
    case "SET_CRITERIA":
      return { ...state, criteria: action.payload, candidates: [] };
    case "SET_HOLD_ACQUIRED":
      return {
        ...state,
        activeHold: action.payload,
        holdExpiryUtc: action.payload.expiryUtc,
        stage: "held",
      };
    case "SET_CONFIRMED":
      return { ...state, reservation: action.payload, stage: "confirmed" };
    default:
      return state;
  }
}

Booking Hold APIs

MethodEndpointDescriptionAuth
POST/api/v1/venue/booking-holdsAcquire atomic 15-minute hold on a resource slot🔒 Required
POST/api/v1/venue/reservations/{id}/confirmConfirm reservation upon successful checkout🔒 Required