Cross-Context Event Contracts: Commerce (Booking Lifecycle) β
Formal event contracts for all Commerce domain events. For the full Hasura Action and Scheduled Trigger specs, see booking-lifecycle-protocol.md and cancellation-protocol.md. For the broader domain events catalog, see event-catalog.md.
Event Catalog β
| Event | Emitter | Consumer | Purpose |
|---|---|---|---|
BookingConfirmed | Commerce (Booking β DEPOSIT_PAID) | Backoffice (CostingSheet auto-lock β ADR-009), Communications (confirmation) | Signals first payment received; triggers cost lock and passenger notification |
BookingFullyPaid | Commerce (Booking β FULLY_PAID) | Communications (full payment confirmation) | Signals all payments complete |
BookingCancelled | Commerce (Booking β CANCELLED) | Communications (cancellation notification), Operations (manifest update) | Signals booking cancellation with optional refund initiation |
BookingRefunded | Commerce (Booking β REFUNDED) | Communications (refund confirmation) | Signals Mollie refund settlement; optionally triggers Invoice Storno |
BookingCompleted | Commerce (Booking β COMPLETED) | Communications (post-trip review request) | Signals successful trip completion (Scheduled Trigger: end_date + 24h) |
BookingNoShow | Commerce (Booking β NO_SHOW) | Communications (operator-specific triggers) | Signals no boarding event recorded (Scheduled Trigger: end_date + 48h) |
PaymentReceived | Commerce (Payment β COMPLETED) | Commerce (FinancialLedger revenue tracking), Backoffice | Fires on every Mollie payment capture (deposit, final payment, ancillary). Distinct from BookingConfirmed β that event signals the state transition, this one signals the financial event. |
FinancialLedgerClosed | Commerce (FinancialLedger β CLOSED) | DATEV Export Service, Communications | Signals ledger finalization; triggers TaxLedgerEntry creation and DATEV export readiness |
CheckoutAbandoned | Commerce (CheckoutSession β EXPIRED) | Communications (re-engagement: email at +1h, WhatsApp at +24h) | Signals checkout session expiry without conversion |
FinalPaymentDue | Commerce (Scheduled Trigger) | Communications (payment reminder with Mollie payment link) | Fires when departure_date - reminder_days_before_start for bookings in DEPOSIT_PAID |
FinalPaymentOverdue | Commerce (Scheduled Trigger) | Communications (urgent notification), Backoffice (dispatcher flagging) | Fires when departure_date - flag_days_before_start β€ now() for DEPOSIT_PAID bookings with no FINAL_PAYMENT |
SeatHoldExpired | Commerce (Scheduled Trigger) | Commerce (self β availability count update) | Fires when hold_expires_at < now() for HELD reservations |
PassengerCancelled | Commerce (Hasura Action) | Commerce (seat release, ticket void, refund calc), Communications (notification) | Fires when removing a single passenger from an active booking (partial cancellation) |
AncillaryCancelled | Commerce (Hasura Action) | Communications (notification) | Fires when removing an ancillary from an active booking |
PassengerAdded | Commerce (Hasura Action) | Commerce (self β supplementary payment link), Communications (notification), Operations (manifest) | Fires when adding a new passenger to an active booking |
PassengerUpdated | Commerce (Hasura Action) | Operations (manifest update) | Fires when passenger details change on an active booking |
Payload Schemas β
BookingConfirmed β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
booking_id | UUID | Confirmed booking |
tour_offering_id | UUID | Target offering |
price_matrix_id | UUID | PriceMatrix version booked against |
passenger_count | INT | Total passengers on booking |
deposit_amount | Decimal | Deposit payment amount |
reference_number | String | Human-readable booking reference |
confirmed_at | TIMESTAMPTZ | Confirmation timestamp |
Note: Also consumed by Backoffice for CostingSheet auto-lock. See event-contracts-pricing.md Β§
BookingConfirmedfor the Backoffice consumption contract.
BookingFullyPaid β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
booking_id | UUID | Fully paid booking |
total_amount | Decimal | Total amount paid |
payment_method | String | Mollie payment method used for final payment |
paid_at | TIMESTAMPTZ | Timestamp of final payment completion |
BookingCancelled β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
booking_id | UUID | Cancelled booking |
reason | String | Cancellation reason (user-provided or system-generated) |
refund_initiated | Boolean | Whether the system initiated a Mollie refund |
cancelled_by | String | Actor: DISPATCHER, PASSENGER, SYSTEM (timeout) |
cancelled_at | TIMESTAMPTZ | Cancellation timestamp |
BookingRefunded β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
booking_id | UUID | Refunded booking |
refund_amount | Decimal | Amount refunded |
refund_payment_id | UUID | FK to payments.id (the refund Payment row) |
refunded_at | TIMESTAMPTZ | Mollie refund settlement timestamp |
BookingCompleted β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
booking_id | UUID | Completed booking |
tour_offering_id | UUID | Target offering |
passenger_count | INT | Total passengers |
completed_at | TIMESTAMPTZ | Completion timestamp |
BookingNoShow β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
booking_id | UUID | No-show booking |
passenger_ids | UUID[] | All passengers who did not board |
detected_at | TIMESTAMPTZ | Detection timestamp |
NOTE
Trigger query (cross-schema): The NoShow evaluation window opens when ServiceLegCompleted fires for the final leg of a TourDeparture (payload: is_final_leg = true). The Commerce consumer queries:
SELECT p.id AS passenger_id, b.id AS booking_id
FROM commerce.bookings b
JOIN commerce.passengers p ON p.booking_id = b.id
JOIN commerce.tickets t ON t.passenger_id = p.id
WHERE b.tour_offering_id = :offering_id
AND b.status = 'CONFIRMED'
AND t.status = 'ACTIVE'
AND NOT EXISTS (
SELECT 1 FROM operations.boarding_events be
WHERE be.ticket_id = t.id
AND be.check_in_status IN ('SUCCESS', 'MANUAL_OVERRIDE')
);For each booking_id with β₯1 unboarded passenger, emit one BookingNoShow event. Per-passenger granularity (not per-booking) ensures that group bookings with partial no-shows are handled correctly β e.g., a booking of 4 where 3 boarded and 1 did not emits a BookingNoShow with passenger_ids = [the_missing_one].
PaymentReceived β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
booking_id | UUID | Associated booking |
payment_id | UUID | The payments.id row |
payment_type | String | DEPOSIT, FINAL_PAYMENT, or PARTIAL_REFUND |
amount | Decimal | Payment amount |
payment_method | String | Mollie payment method |
provider_transaction_id | String | Mollie transaction ID |
captured_at | TIMESTAMPTZ | Mollie capture timestamp |
CheckoutAbandoned β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
session_id | UUID | Expired checkout session |
tour_offering_id | UUID | Offering the session was for |
contact_email | String | Passenger email for re-engagement |
expired_at | TIMESTAMPTZ | Session expiry timestamp |
FinalPaymentDue β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
booking_id | UUID | Booking awaiting final payment |
passenger_email | String | Primary contact email |
amount_remaining | Decimal | Outstanding amount |
due_date | DATE | Derived from departure_date - operator.final_payment_config.reminder_days_before_start |
payment_link | String | Pre-generated Mollie payment URL |
severity | String | Escalation tier: REMINDER (first notice) or URGENT (second notice). See booking-lifecycle-protocol.md Β§final_payment_escalation_sweep. |
channel | String | Preferred delivery channel: EMAIL (REMINDER tier) or WHATSAPP (URGENT tier) |
FinalPaymentOverdue β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
booking_id | UUID | Overdue booking |
severity | String | Always CRITICAL |
flagged_at | TIMESTAMPTZ | Timestamp when the system flagged the booking |
tickets_voided | Boolean | Whether the system voided tickets (true if the system issued ADR-014 tickets at DEPOSIT_PAID) |
SeatHoldExpired β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
seat_reservation_id | UUID | The released reservation |
service_leg_id | UUID | Affected service leg |
seat_identifier | String | Seat position key |
expired_at | TIMESTAMPTZ | Hold expiry timestamp |
PassengerCancelled β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
booking_id | UUID | Parent booking |
passenger_id | UUID | Cancelled passenger |
refund_amount | Decimal | Amount refunded for this passenger (after cancellation fee) |
cancelled_at | TIMESTAMPTZ | Cancellation timestamp |
AncillaryCancelled β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
booking_id | UUID | Parent booking |
ancillary_id | UUID | Cancelled ancillary |
refund_amount | Decimal | Amount refunded (after cancellation fee) |
cancelled_at | TIMESTAMPTZ | Cancellation timestamp |
PassengerAdded β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
booking_id | UUID | Parent booking |
passenger_id | UUID | Newly added passenger |
additional_amount | Decimal | Price added to booking total |
supplementary_payment_required | Boolean | Whether a the system generated a new Mollie payment link |
added_at | TIMESTAMPTZ | Addition timestamp |
PassengerUpdated β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
booking_id | UUID | Parent booking |
passenger_id | UUID | Updated passenger |
changes | String[] | List of changed field names (e.g., ["first_name", "last_name"]) |
ticket_reissued | Boolean | Whether the system voided and reissued the ticket due to a name change |
updated_at | TIMESTAMPTZ | Update timestamp |
FinancialLedgerClosed β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
financial_ledger_id | UUID | Closed ledger |
tour_offering_id | UUID | Parent offering |
realized_revenue | Decimal | Final revenue at closure |
realized_expense | Decimal | Final expense at closure |
margin_delta | Decimal | Final margin deviation from plan |
tax_entry_count | INT | Number of TaxLedgerEntries created at closure |
closed_at | TIMESTAMPTZ | Closure timestamp |
InvoiceIssued β
| Field | Type | Description |
|---|---|---|
event_id | UUID | Idempotency key |
tenant_id | UUID | Owning operator |
invoice_id | UUID | The issued invoice |
booking_id | UUID | Parent booking |
invoice_number | String | Gap-free human-readable number |
total_gross | Decimal | Invoice grand total |
issued_at | TIMESTAMPTZ | Issuance timestamp |
Delivery Contracts β
| Event | Mechanism | Trigger | Idempotency |
|---|---|---|---|
BookingConfirmed | Hasura Event Trigger (async, post-commit) | commerce.bookings.status β DEPOSIT_PAID | event_id |
BookingFullyPaid | Hasura Event Trigger (async, post-commit) | commerce.bookings.status β FULLY_PAID | event_id |
BookingCancelled | NestJS @nestjs/event-emitter β Hasura Event Trigger | cancelBooking Action handler commits | event_id |
BookingRefunded | Hasura Event Trigger (async, post-commit) | commerce.bookings.status β REFUNDED | event_id |
BookingCompleted | NestJS Cron Trigger handler | booking_completion_sweep (end_date + 24h) | event_id |
BookingNoShow | NestJS Cron Trigger handler | no_show_detection_sweep (end_date + 48h) | event_id |
PaymentReceived | Hasura Event Trigger (async, post-commit) | commerce.payments.status β COMPLETED | event_id + provider_transaction_id uniqueness |
CheckoutAbandoned | NestJS Cron Trigger handler | checkout_abandoned_sweep (every 5 min) | Session status = ACTIVE guard in WHERE clause |
FinalPaymentDue | NestJS Cron Trigger handler | final_payment_escalation_sweep (daily 08:00) | NOT EXISTS check on pending/completed FINAL_PAYMENT + tier derivation from days_until_departure |
FinalPaymentOverdue | NestJS Cron Trigger handler | final_payment_escalation_sweep (daily 08:00) | Same sweep as FinalPaymentDue, emitted at CRITICAL tier (flag_days_before_start). Sets booking.flagged = true, voids tickets if applicable. |
SeatHoldExpired | NestJS Cron Trigger handler | seat_hold_cleanup (every 60s) | status = HELD guard in WHERE clause |
PassengerCancelled | NestJS @nestjs/event-emitter β Hasura Event Trigger | cancelPassenger Action handler commits | event_id |
AncillaryCancelled | NestJS @nestjs/event-emitter β Hasura Event Trigger | cancelAncillary Action handler commits | event_id |
PassengerAdded | NestJS @nestjs/event-emitter β Hasura Event Trigger | addPassengerToBooking Action handler commits | event_id |
PassengerUpdated | NestJS @nestjs/event-emitter β Hasura Event Trigger | updatePassengerDetails Action handler commits | event_id |
FinancialLedgerClosed | Hasura Event Trigger (async, post-commit) | commerce.financial_ledgers.status β CLOSED | event_id |
InvoiceIssued | NestJS @nestjs/event-emitter β Hasura Event Trigger | issueInvoice Action handler commits | event_id |
All consumers implement at-least-once processing with event_id-based deduplication. See domain-driven-design.md Β§4 for the standard delivery pattern.