← Files WixARCHIVED FILE

skills/wix-headless-templates/events/rest/registration.ts

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

↓ Download file

See the change to this file →

// Ticket tiers, the reserve → hosted-checkout redirect, and RSVP over REST — the twin of
// app/wix/events/registration.ts. Same exports, same DTOs; rules and mappers from
// registration-core (the SAME file the SDK transport uses, deployed flat next to this one). The
// request shapes are exact and rewriting them is how registrations break. Failures are loud.
// All calls run with the visitor token — the reservation and the RSVP are the token's.
// Query Available Ticket Definitions: wix.events.ticketdef.v3.TicketDefinitionManagement.QueryAvailableTicketDefinitions
//   (POST /events/v3/ticket-definitions/available/query, as the @wix/events SDK issues it; the V3 family's reference:)
// docs: https://dev.wix.com/docs/api-reference/business-solutions/events/event-management/ticket-definitions-v3/create-ticket-definition.md
// docs: https://dev.wix.com/docs/api-reference/business-solutions/events/registration/ticketing/ticket-reservations/create-ticket-reservation.md
// docs: https://dev.wix.com/docs/api-reference/business-solutions/events/registration/rsvp-v2/create-rsvp.md
// docs: https://dev.wix.com/docs/api-reference/business-management/headless/redirects/create-redirect-session.md
import { wixRequest } from "./client.js";
import {
  availableTiersQuery,
  checkoutCallbacks,
  redirectResult,
  reservationError,
  reservationId,
  reservationTickets,
  rsvpBody,
  rsvpFormOf,
  rsvpResult,
  toTiers,
  type CheckoutEvent,
  type CheckoutPaths,
  type RsvpValues,
  type TierEvent,
} from "./registration-core.js";
import type { Raw } from "./events-core.js";
import type { EventDetail, RegistrationResult, TicketSelection, TicketTier } from "./types.js";

export { type CheckoutPaths, type RsvpValues, type TierEvent };

/**
 * Ticket tiers for a TICKETING event, in the organizer's order — the VISITOR-public V3 read (hidden
 * tiers excluded server-side), never the ticket-definitions management query (403 for a visitor).
 * Pass the EventDetail: sale dates format in its zone, its tax settings price the notes.
 * POST /events/v3/ticket-definitions/available/query  { query: { filter: { eventId }, sort: [{ fieldName: "sortIndex", order: "ASC" }] } }  → { ticketDefinitions }
 */
export async function fetchTicketTiers(event: TierEvent): Promise<TicketTier[]> {
  const res = await wixRequest<Raw>("/events/v3/ticket-definitions/available/query", { body: { query: availableTiersQuery(event.id) } });
  return toTiers(res?.ticketDefinitions, event);
}

/**
 * RSVP to a free (RSVP-type) event — completes fully client-side: no reservation, no redirect, no
 * payment. `values` answer the ORGANIZER'S form (event.rsvpForm), keyed by input name; the body
 * carries the built-in identity at the top level, the answers as `form.inputValues`, and
 * `additionalGuestDetails` when the form has a guest control. "NO" only when the event's
 * rsvpResponseType is "YES_AND_NO". Throws on closed registration, a guest limit, a missing
 * mandatory answer (INVALID_FORM_RESPONSE), or an invalid email — surface the message. A full
 * event with a waitlist answers "WAITLIST".
 * POST /events/v2/rsvps  { rsvp: { eventId, status, firstName, lastName, email, form: { inputValues: [{ inputName, value | values }] }, additionalGuestDetails?: { guestCount, guestNames } } }  → { rsvp: { id, status } }
 */
export async function submitRsvp(
  event: Pick<EventDetail, "id" | "rsvpForm">,
  values: RsvpValues,
  status: "YES" | "NO" = "YES",
  guests: { guestCount: number; guestNames: string[] } = { guestCount: 0, guestNames: [] },
): Promise<RegistrationResult> {
  const res = await wixRequest<Raw>("/events/v2/rsvps", { body: { rsvp: rsvpBody(event.id, rsvpFormOf(event), values, status, guests) } });
  return rsvpResult(res?.rsvp, status);
}

/**
 * Ticketed checkout, the exact sequence: reserve the selected tiers (a PENDING hold that
 * auto-expires), then mint the Wix-hosted checkout redirect and return its URL — the caller
 * navigates the FULL document to it; Wix collects guest details + payment and emails the tickets.
 * Selections carry the pricing option or the guest's price where the tier needs one (`ticketInfo`);
 * pass `tiers` so a missing guest price is refused before the request. `origin` must be the site's
 * real https origin as registered on the OAuth app's allowed domains (browser:
 * window.location.origin). Never hand-build the checkout URL. `paths` overrides the return routes —
 * a static site passes `{ confirmation: "/event-confirmation.html", event: "/event.html?slug=…" }`.
 * POST /events/v1/ticket-reservations  { ticketReservation: { tickets: [{ eventId, ticketDefinitionId, quantity, ticketInfo?: { pricingOptionId?, guestPrice? } }] } }  → { ticketReservation: { id, status: "PENDING", expirationDate } }
 * POST /headless/v1/redirect-session   { eventsCheckout: { reservationId, eventSlug }, callbacks: { thankYouPageUrl, postFlowUrl } }
 */
export async function startTicketCheckout(
  event: CheckoutEvent,
  selections: TicketSelection[],
  paths: CheckoutPaths = {},
  tiers: TicketTier[] = [],
  origin: string = typeof window !== "undefined" ? window.location.origin : "",
): Promise<RegistrationResult> {
  const tickets = reservationTickets(event.id, selections, tiers);
  let reservation: Raw | undefined;
  try {
    // Until the site has a premium plan AND a payment method this is a 403 NO_PAYMENT_METHOD_CONFIGURED — a business gate, not a bug.
    reservation = (await wixRequest<Raw>("/events/v1/ticket-reservations", { body: { ticketReservation: { tickets } } }))?.ticketReservation;
  } catch (e) {
    throw reservationError(e);
  }
  const session = await wixRequest<Raw>("/headless/v1/redirect-session", {
    body: { eventsCheckout: { reservationId: reservationId(reservation), eventSlug: event.slug }, callbacks: checkoutCallbacks(origin, event.slug, paths) },
  });
  return redirectResult(session);
}

SHA-256: 8f65017fa8962a6facc735984adaac0d38f17c7744c41307c15a247e94dccd7f