Skip to main content
The bookings module (@voyant-travel/bookings) owns the durable, customer-safe operational record of a sale. When a pursuit hardens past the proposal, the result is a Booking: the travelers, the priced line items, the inventory holds, the provenance of how it was created, and the lifecycle state it sits in. Everything operational about a confirmed sale routes through here. Bookings is the third and fourth rung of the commitment chain: Quote -> accepted Quote Version -> reserve workflow -> Booking -> Fulfillment. A booking does not require a generic first-party order to exist; it is the first-party commitment, and its origin record carries the provenance of wherever it came from. Identity is referenced by snapshot (personId plus organizationId), never owned here.

Key concepts

Booking
durable commitment
The durable first-party commitment and customer-safe operational record: travelers, booking items, allocations, fulfillments, redemptions, origin and provenance, and state. Avoid “reservation” and “order”; the booking is the persistent record. See Booking.
Booking Item
line on a booking
A line item on a Booking (unit, service, extra, fee, tax, discount, accommodation, transport). Each allocation belongs to exactly one booking item, and each item produces zero or more fulfillments. See Booking Item.
Allocation
capacity hold
A capacity hold against a Slot, Pickup, or Resource, moving held to confirmed to fulfilled. An allocation is the inventory-line entity and belongs to exactly one booking item. See Allocation.
Hold
time-limited claim
The temporal status of a booking before confirmation, carrying a hold_expires_at. A hold is a temporary, time-limited claim on inventory that expires if the booking is not confirmed in time. Distinct from an Allocation, which is the inventory line itself. See Hold.
Booking Origin
provenance record
Bookings-owned provenance describing how a Booking was created. The durable booking_origins row is keyed by booking_id and records one of accepted_quote_version, catalog_price_availability, catalog_snapshot, provider_source_order, or legacy_transaction, plus the matching reference ids (quote version, trip snapshot, reservation plan, catalog response, provider order ref, or migrated transaction id). New flows write this owner path. See Booking Origin.
Fulfillment
issued deliverable
Issuance of a deliverable artifact (voucher, ticket, PDF, QR, barcode) for a booking item. A fulfillment is delivered over exactly one channel, and consumed at the point of service by a redemption. See Fulfillment.
Booking traveler
person who travels, with PII
A Traveler on the booking, carrying category and PII. Travel details (identity document, date of birth, dietary and accessibility needs) are KMS-encrypted and can be pre-filled from a Relationships Person document snapshot.

The commitment ladder

A booking is the operational endpoint of the chain. Each rung hardens what came before.
1

Quote

A tracked pursuit in the quotes module, with a Person or Organization.
2

Accepted quote version

The client accepts a sent quote version. That marks the version accepted, closes the quote won, and seeds the reserve workflow. Acceptance is a sales decision, not supplier confirmation.
3

Reserve workflow

Reserve takes the accepted version’s frozen snapshot, rechecks sellability and cost on live lines, moves manual lines into supplier confirmation, and secures the component commitments. A reservation plan captures what was reserved (and what failed) before any booking is created.
4

Booking

Once reserve secures the commitments, the Booking is created. It holds allocations against slots (or pickups, or resources) for each item, snapshots the buyer, and records its origin pointing back at the accepted quote version.
5

Fulfillment

Booking items issue fulfillments (vouchers, tickets), each delivered over one channel and redeemed at the point of service.

Status state machine

Booking status moves along an explicit transition graph; illegal transitions raise a BookingTransitionError. The transitions are:
FromAllowed next states
drafton_hold, awaiting_payment, confirmed, cancelled
on_holdawaiting_payment, confirmed, expired, cancelled
awaiting_paymentconfirmed, expired, cancelled
confirmedin_progress, cancelled
in_progresscompleted, cancelled
completed, expired, cancelledterminal
Transitions stamp the matching timestamp: confirmedAt on confirm, paidAt additionally when confirming out of awaiting_payment (so reporting can split free confirmations from paid ones), expiredAt, cancelledAt, and completedAt. Cancel is the operational reversal of a booking, distinct from voiding an invoice (a financial reversal) and from closing a quote.
An admin can override status outside the transition graph, but only as an explicit, audit-logged action with a required reason. The transition graph is the default; the override is the escape hatch.

PII handling and audit logging

Booking travelers carry sensitive personal data: passport numbers, dates of birth, dietary and accessibility needs. Bookings treats this PII as first-class.
  • Encryption at rest. Identity, dietary, and accessibility fields are encrypted through a KMS provider into JSON envelopes. The PII service (getTravelerTravelDetails, upsertTravelerTravelDetails, deleteTravelerTravelDetails) is the only path that decrypts them, and decryption is on demand per traveler.
  • Audit logging. Every encrypt, decrypt, and delete of traveler travel details emits a BookingPiiAuditEvent carrying the action, the traveler id, and the acting principal, through the service’s onAudit hook. Reads of PII are logged, not just writes.
  • Snapshot, do not re-key. A booking traveler can pre-fill its travel snapshot from a Relationships Person document. Explicit input always wins; the snapshot only fills the gaps. Provenance of the snapshot (the source person-document id) is recorded.
  • Route-boundary redaction. Callers without the PII-reveal scope receive redacted contact and traveler fields at the API boundary (email, phone, names, tax id, and address lines are masked). Internal callers such as cron jobs and workflows bypass redaction; redaction is an in-flight protection at the route layer.

Action-ledger approvals

Agent and workflow status mutations that require approval do not mutate the booking directly. They return 202 with the requested action and approval ids, and must send an Idempotency-Key. The key is fingerprinted with the command input and the approval-policy inputs, so replaying the same key with different input returns a conflict rather than a second action. After approval, the same status mutation is executed again with the approval-id header from @voyant-travel/action-ledger. The route revalidates that the approval is approved, unexpired, linked to the same requested action and current principal, and command-equivalent to the original request before it touches the booking. Approved execution ledger fields are stamped so execution entries link back to the requested action and its approval.

What it owns

  • Bookings (book) and their status state machine.
  • Booking travelers (bkps) with KMS-backed PII and snapshot pre-fill.
  • Booking origins (booking_origins): the provenance owner path.
  • Booking items, allocations, and the hold lifecycle.
  • Booking supplier statuses (bkss), the booking activity log (bkal), and booking notes (bnot).
  • Booking session states (bkst): first-class persisted wizard state for the public storefront flow, with preview and applyToSession repricing.
  • Booking requirements (./requirements) and the supplier extension (./extensions).
  • The reservation-plan and refund workflows, and the dependency-injected checkout capability.
It does not own catalog truth, inventory authoring, or money collection. Those belong to catalog, inventory, and the finance module.

Working with it

Mount the module and drive a booking through its lifecycle.
import { createApp } from "@voyant-travel/hono";
import { bookingsModule } from "@voyant-travel/bookings";

const app = createApp({
  modules: [bookingsModule],
});
Transitions are validated against the state machine before any write.
import { transitionBooking, canTransitionBooking } from "@voyant-travel/bookings";

if (canTransitionBooking(booking.status, "confirmed")) {
  const patch = transitionBooking(booking.status, "confirmed");
  // patch stamps confirmedAt, and paidAt when coming from awaiting_payment
  await bookingsService.applyStatusPatch(db, booking.id, patch);
}
Record provenance so the booking can always explain where it came from.
import { upsertBookingOrigin } from "@voyant-travel/bookings";

await upsertBookingOrigin(db, {
  bookingId: booking.id,
  originSource: "accepted_quote_version",
  quoteVersionId: "qver_01h...",
});
Read traveler PII through the PII service, which decrypts on demand and audits the access.
const details = await piiService.getTravelerTravelDetails(
  db,
  travelerId,
  actorId, // logged in the BookingPiiAuditEvent
);
Approval-required status mutations from agents or workflows must send an Idempotency-Key, and must replay through the action-ledger approval header after approval. Skipping the ledger path is rejected at the route, not silently executed.
  • quotes feeds the accepted quote version that a booking’s origin points at. The booking_crm_details extension links the booking back to its quote.
  • relationships owns the buyer snapshotted onto the booking and the person documents that seed traveler PII.
  • trips groups independent component bookings into one Trip Envelope without erasing their per-component lifecycle boundaries.
  • catalog is where the booking engine lands rows; bookings is the shared parent so an itinerary can mix owned and sourced lines.
  • The finance module attaches payment schedules and invoices to a booking; legal attaches contracts and policy acceptances.

React package

@voyant-travel/bookings-react is the client tier. Headless consumers import from the root, ./hooks, ./client, or ./query-keys with no styling peers, including public storefront helpers (usePublicBookingSession, usePublicBookingSessionState, usePublicBookingSessionFlowMutation) that target the wizard-state and repricing contract. Styled surfaces live under ./ui, ./components/*, ./journey, and ./admin, with optional heavier peers. The customer-facing booking journey under @voyant-travel/bookings-react/journey pairs with the catalog booking-engine surfaces.

Next steps

Quotes

The accepted quote version that seeds the reserve workflow.

Trips

Trip envelopes that compose multiple component bookings into one itinerary.

Catalog

The sellable plane the booking engine lands owned and sourced rows into.

Glossary

The shared vocabulary: Booking, Booking Item, Allocation, Hold, Origin, Fulfillment.