Match-Based Reservation Slots
05/02/2026
Problem Statement
FoodFight is looking to sell guaranteed spots for high-demand matches through our platform. For popular games (e.g. Bears vs Hawks), venues that want to offer paid reservations or capped attendance have to handle it off-platform — or not at all. We want to give venue partners a built-in option to sell space in their bar tied to specific matches, opening a new revenue stream for them and a better guest experience for fans who want a guaranteed spot.
Proposed Solution
Introduce a match-tied reservation system that lets guests reserve and pay for a spot at a specific bar for a specific match. Venues configure availability per match in the Partner Dashboard; guests book through the FoodFight app on venue pages.
Key design decisions:
- Reservations are scoped to a match timeslot (start → expected end), not an open-ended table booking.
- Capacity is headcount-based —
slotsrepresents the total number of guests a venue is willing to accept for a match. Each booking consumesparty_sizeslots (a party of 4 takes 4); the app disables booking when remaining capacity can’t fit the next request. - Payment is handled via Stripe. A PaymentIntent is created before a reservation is confirmed; only a successful payment activates a slot. If
cost_per_head = 0, the payment flow is skipped entirely. - Venue pages and the reservation flow are auth-gated — unauthenticated users are routed to login and returned to their intended destination afterward.
- No bookings are accepted after match kickoff (
close_reservations_atdefaults tomatch.start_time).
Cancellation & Refund Policy
- Guest-initiated cancellations are refunded if made at least 24 hours before
start_time. Cancellations within the 24-hour window are non-refundable. - Match rescheduled to another day triggers an automatic refund for all confirmed reservations on that match.
- Same-day postponements leave reservation
start_timeunchanged, but affected guests are notified of the new match time. - Partner-initiated cancellations trigger a refund only when the cancellation is made through the Partner Dashboard. Cancellations a partner handles off-platform (e.g. directly with the guest) are not eligible for a FoodFight-issued refund.
Architectural & Technical Details
Data Models
Reservation (venue-level config per match)
| Field | Type | Notes |
|---|---|---|
id | int PK | |
restaurant_id | int FK | Venue this config belongs to |
game_id | int FK | Match this config applies to |
name | string | Formatted matchup name (e.g. “Bears vs Hawks”) |
slots | int | Total headcount available across all bookings for this match |
cost_per_head | int | Price per person in cents. 0 = free, skip Stripe |
reservations_enabled | bool | Enable/disable bookings for this match |
open_reservations_at | datetime UTC | When guests can start booking |
close_reservations_at | datetime UTC | Defaults to match start time |
featured | bool | Highlights match prominently in the guest app |
UserReservation (per guest booking)
| Field | Type | Notes |
|---|---|---|
id | int PK | |
user_id | str FK | Authenticated guest |
reservation_id | int FK | Links to Reservation config |
party_size | int | Number of people in the booking; consumes this many slots from capacity |
reservation_code | str | Unique code shown to guest on confirmation |
request_table | bool | Whether the guest requested a table |
status | enum | pending · confirmed · cancelled · completed · no_show |
payment_status | enum | pending · succeeded · failed · refunded |
stripe_payment_intent_id | str | Link to Stripe PaymentIntent |
start_time | datetime UTC | Defaults to match start time |
API Endpoints
Partner Dashboard
| Method | Path | Purpose |
|---|---|---|
| PUT | /partner/restaurants/{restaurantId}/reservation | Create or update reservation config |
| GET | /partner/restaurants/{restaurantId}/{reservationId} | List Reservation details and all UserReservations for a created Reservation |
| DELETE | /partner/restaurants/{restaurantId}/reservations/{reservationId} | Cancel a reservation |
Mobile App
| Method | Path | Purpose |
|---|---|---|
| GET | /restaurants/reservations | Returns open reservation |
| POST | /restaurants/reservations/{reservationId} | Creates UserResrvation on |
| GET | /{userId}/reservations | List user’s reservations |
| DELETE | /{userId}/reservations/{reservationId} | Cancel a reservation |
Backend Notes
- Implement a
ReservationServicelayer that validates all capacity and timing rules before writing. Wrap DB writes in transactions to prevent race conditions on slot count. - Stripe integration handles PaymentIntent creation and webhook events (
payment_intent.succeeded,payment_intent.payment_failed). Only confirmed webhooks activate reservations. - Partner endpoints require partner auth with ownership of
restaurantId. Guest endpoints require user auth with ownership of the reservation. - All datetimes stored in UTC; rendered in venue locale on the frontend.
Next Steps
Action Items
- Implement
ReservationandUserReservationDB models and migrations — Backend - Create Partner Dashboard API endpoints — Backend
- Create Mobile App API endpoints — Backend
- Partner Dashboard UI — Frontend
- Mobile App UI: “Reserve a spot” — Frontend
Open Questions
- What happens if a venue decreases
slotsbelow the current active reservation count — reject the change, or apply it to new bookings only? - Should
featuredmatches receive any special ordering or UI treatment beyond a badge (e.g. pushed to top of the match list)?
Approvals
You need architectural approval from Trace Carrasco & product approval from Filip Pacyna / Troy Lenihan
- Trace Carrasco
- Filip Pacyna/Troy Lenihan