Packages (hotel + train)

Eurostar sells packages (a hotel stay bundled with a return train) through the same staging /gateway site-api as ordinary train bookings. The bot books them through a dedicated pipeline that mirrors the train search, then funnels into the shared cart/checkout steps.

--package <destination> switches the run into packages mode. See cli.md for the full flag list.

The flow

src/core/book.ts dispatches on --package: with it set, src/core/book/package.ts (bookPackage) runs instead of src/core/book/train.ts (bookTrain). The package pre-flight lives in src/core/web/packages/:

  1. PackagesSearch — lists hotels for an origin/destination region, arrival date, nights and room occupancy. The bot picks the hotel from --hotel (name substring), else a known staging test hotel for the region, else the cheapest result (the search is sorted price-ascending).
  2. PackageHotelRooms — lists bookable rooms for that hotel. --cancellation FULL|PARTIAL narrows to rooms offering that refund type (each room carries a cancellations[] policy list); --room narrows by name/type substring.
  3. PackageTrains — lists outbound/inbound trains and the gateway's pre-selected service per bound (same journey/fare shape as journeySearch). By default the bot books the pre-selected trains; any --out/--rtn train flag re-runs the standard journey selector over the package journeys.
  4. createCart (package variant)channel: "PKG", the package total in package: { totalPrice }, and the hotel/room detail in hotels: [CartItemHotelInput]. Trains are ordinary TrainInput, identical to the train path. Then holdCartProducts and checkout run unchanged.

The search calls send the x-channel: pack HTTP header; createCart does not (its GraphQL channel: "PKG" selects the packages flow). __typename is stripped from response-derived errata/cancellations before they go back into the createCart input types (which reject it).

Destination dataset

Packages address cities by region id, not station UIC (london = 279, antwerp = 2385, paris = 390). The table in src/core/lib/booking/packages/regions.ts is mirrored from the hotel-inventory service (src/graphql/resolvers/regions/data/regions.ts and regionConnections.ts) and drives --package/--from slug resolution and route validation (assertPackageRoute). Not every origin reaches every destination; an invalid pair errors with the list of valid destinations.

Test hotels

variables/packageTestHotels.csv (human reference) and variables/packageTestHotels.json (consumed by the selector) list the known staging test hotels per city. PRID is not reliably the live staging hotel id

Example

bun book --package antwerp --from london -d 2026-07-10 --nights 2 -p 2a \
  --hotel hampton --room triple --cancellation FULL -e stg -v

Everything is staging-only with a test card - no real bookings.