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 — slots represents the total number of guests a venue is willing to accept for a match. Each booking consumes party_size slots (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_at defaults to match.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_time unchanged, 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 ReservationService layer 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 Reservation and UserReservation DB 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 slots below the current active reservation count — reject the change, or apply it to new bookings only?
  • Should featured matches 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