← Files WixARCHIVED FILE

skills/wix-headless-templates/restaurants/rest/reservations.ts

5.5 KB · Oct 8, 2026 · 12:02 UTC

↓ Download file

See the change to this file →

// Table reservations over REST — the twin of app/wix/restaurants/reservations.ts. Same exports,
// same DTOs; rules and mappers from reservations-core (the SAME file the SDK transport uses,
// deployed flat next to this one). The flow is location → AVAILABLE slots → (AUTOMATIC approval)
// hold → reserve, or (MANUAL approval) one create with details + reservee and no hold; a
// reservation is a hold, not a purchase — no cart, no checkout. Every call here is a visitor's own
// action with the visitor token. The hold, reserve, and create calls are premium-gated on the site:
// a non-premium site answers 428 "site must be premium" — surface it, don't retry.
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/time-slots/get-time-slots.md
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/create-held-reservation.md
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/reserve-reservation.md
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/create-reservation.md
import { wixRequest } from "./client.js";
import { availableSlots, reservationBody, reserveeBody, toConfirmation, toHold, toLocations } from "./reservations-core.js";
import type { Raw } from "./menu-core.js";
import type {
  ReservationConfirmation,
  ReservationFormConfig,
  ReservationHold,
  ReservationLocationInfo,
  ReservationReservee,
  ReservationSlot,
} from "./types.js";

const RESERVATIONS = "/table-reservations/reservations/v1";

/**
 * The locations to offer: those with online reservations ENABLED (default first), or only the
 * default one when none is enabled (then `onlineReservationsEnabled: false` → render an honest
 * notice); [] when Table Reservations isn't set up. Each carries name, address, timezone, approval
 * rule, and the owner's form configuration.
 * GET /table-reservations/reservation-locations/v1/reservation-locations
 */
export async function fetchReservationLocations(): Promise<ReservationLocationInfo[]> {
  const res = await wixRequest<Raw>("/table-reservations/reservation-locations/v1/reservation-locations", { method: "GET" });
  return toLocations(res?.reservationLocations ?? []);
}

/**
 * AVAILABLE slots around a moment for a party size (UNAVAILABLE / NON_WORKING_HOURS dropped);
 * `aroundIso` is built in the location's zone (`zonedIso`), labels and day keys use `timeZone`.
 * POST /table-reservations/reservations/v1/time-slots  { reservationLocationId, date, partySize, slotsBefore, slotsAfter }
 */
export async function fetchReservationSlots(
  locationId: string,
  aroundIso: string,
  partySize: number,
  { timeZone = "", slotsBefore = 6, slotsAfter = 6 }: { timeZone?: string; slotsBefore?: number; slotsAfter?: number } = {},
): Promise<ReservationSlot[]> {
  const res = await wixRequest<Raw>(`${RESERVATIONS}/time-slots`, {
    body: { reservationLocationId: locationId, date: new Date(aroundIso).toISOString(), partySize, slotsBefore, slotsAfter },
  });
  return availableSlots(res?.timeSlots ?? [], timeZone);
}

/**
 * Hold a slot for 10 minutes while the visitor enters their details (AUTOMATIC approval); the hold's
 * { reservationId, revision } feed completeReservation, `expiresAtIso` a countdown.
 * POST /table-reservations/reservations/v1/reservations/hold  { reservationDetails: { reservationLocationId, startDate, partySize } }
 */
export async function holdReservation(locationId: string, startIso: string, partySize: number): Promise<ReservationHold> {
  const res = await wixRequest<Raw>(`${RESERVATIONS}/reservations/hold`, {
    body: { reservationDetails: { reservationLocationId: locationId, startDate: new Date(startIso).toISOString(), partySize } },
  });
  return toHold(res, startIso, partySize);
}

/**
 * Complete a held reservation with the visitor's details, validated against the location's `form`
 * (firstName + phone always; lastName/email/custom fields as configured); an expired hold can't be
 * reserved — start a fresh hold. The outcome carries the API's real status.
 * POST /table-reservations/reservations/v1/reservations/{reservationId}/reserve
 *   { revision, reservee: { firstName, phone, lastName?, email?, marketingConsent?, customFields? } }
 */
export async function completeReservation(
  hold: Pick<ReservationHold, "reservationId" | "revision">,
  reservee: ReservationReservee,
  form: ReservationFormConfig,
): Promise<ReservationConfirmation> {
  const body = reserveeBody(reservee, form);
  const res = await wixRequest<Raw>(`${RESERVATIONS}/reservations/${encodeURIComponent(hold.reservationId)}/reserve`, {
    body: { revision: hold.revision, reservee: body },
  });
  return toConfirmation(res);
}

/**
 * Request a reservation that needs the restaurant's approval (MANUAL, or a large party under
 * MANUAL_FOR_LARGE_PARTIES): ONE create with details + reservee, no hold. Normally ends REQUESTED (pending).
 * POST /table-reservations/reservations/v1/reservations
 *   { reservation: { details: { reservationLocationId, startDate, partySize }, reservee: { … } } }
 */
export async function requestReservation(
  locationId: string,
  startIso: string,
  partySize: number,
  reservee: ReservationReservee,
  form: ReservationFormConfig,
): Promise<ReservationConfirmation> {
  const reservation = reservationBody(locationId, new Date(startIso).toISOString(), partySize, reservee, form);
  const res = await wixRequest<Raw>(`${RESERVATIONS}/reservations`, { body: { reservation } });
  return toConfirmation(res);
}

SHA-256: c0378592249cfb7b6dab5dd2074621e9a3f05ebca1a9e5aaa3a84f651c122315