Skip to main content

Reservation Billing Lifecycle (Fees, Refunds, Claims, Transactions)

This document explains when reservation fees are calculated, what gets persisted, and how refunds/claims map to transactions.

What “reservation fees” are

In this repo, “fees” are computed as line items inside the Billing Engine and then (sometimes) copied onto DB columns for convenience.

  • Billing Engine entrypoint: packages/utils/billing/BillingEngine.ts
  • Service fee math: packages/utils/billing/sections/ServiceFeeSection.ts

The canonical fee line items are:

  • guest_service_fee (added to guest total)
  • host_service_fee (deducted from host payout)

When calculations happen

1) Reservation creation (persisted snapshot)

When a reservation is created (currently on PRE_APPROVE), the backend runs BillingEngine.runBilling() and persists the computed amounts onto the Reservation row.

  • Creation + persistence: apps/backend/src/core/workflow/ReservationWorkflow.ts
  • DB fields: apps/backend/prisma/schema.prisma (model Reservation)

2) “Billing UI” reads (computed on-demand, not persisted)

For itinerary/receipt/invoice breakdowns, the backend recomputes billing on-demand and returns the render-ready sections via GraphQL.

  • Resolver + context build: apps/backend/src/data/types/ReservationType.ts
  • Background doc: apps/backend/docs/billing-ui-backend-integration.md

3) Refund math (computed on-demand; settlement persists snapshots + creates transactions)

Refund math is produced by BillingEngine.runRefund(), but the system persists outcomes only when the reservation is settled.

  • Refund pipeline: packages/utils/billing/BillingEngine.ts (runRefund)
  • Claim/cancellation settlement orchestrator: apps/backend/src/core/claims/ClaimService.ts

4) Claims settlement (creates/upserts transactions + writes claim + cancellation snapshot)

Claim settlement is executed via ClaimService.settleClaim() (used for “no claim” settlements too).

  • Settlement: apps/backend/src/core/claims/ClaimService.ts

Where values are saved

Reservation (booking snapshot)

Stored on the Reservation row:

  • Amounts: guestServiceFee, hostServiceFee, total, hostTotal
  • Fee configuration snapshot: guestServiceFeeType/value, hostServiceFeeType/value

Source of truth for schema: apps/backend/prisma/schema.prisma

CancellationDetails (settlement snapshot)

When a cancelled reservation is settled, a CancellationDetails record is created with:

  • refundToGuest, payoutToHost, total, currency
  • guestServiceFee, hostServiceFee (see note below)

Creator: apps/backend/src/core/claims/ClaimService.ts (see createCancellationDetails)

ReservationClaim (+ items/messages)

Claim state and settlement results live in:

  • ReservationClaim (requested amount, payout/refund amounts, statuses, settlement metadata)
  • ReservationClaimItems, ReservationClaimMessages

Schema: apps/backend/prisma/schema.prisma (model ReservationClaim*)

Transactions

Payment lifecycle records live in Transaction rows:

  • booking is created on successful PAY and is typically marked completed
  • guestrefund and hostpayout are created/upserted on settlement and start as pending

Key files:

  • Creation helper: apps/backend/src/core/payment/createTransaction.ts
  • Enums: apps/backend/src/core/payment/transactionEnums.ts
  • Admin execution: apps/backend/src/data/mutations/SiteAdmin/Paymob/adminExecuteReservationTransaction.ts

When values are updated

  • Reservation fee/totals are not recomputed on later workflow transitions (those transitions mainly update reservationState, paymentState, etc.).
  • Refund/payout amounts are computed at settlement time and reflected in:
    • ReservationClaim (statuses/metadata)
    • CancellationDetails (snapshot for cancellations)
    • Transaction (pending/completed, provider ids)

Refund nuance: guest_service_fee_refund line item

Guest-facing refund line items (rental_refund, delivery_refund, discount_refund) are already inclusive of the guest service fee, so the refund totals do not need a separate fee line item.

However, backend cancellation/claim settlement snapshots still need the fee component explicitly, so ServiceFeeSection.processRefund() adds a guest_service_fee_refund line item that:

  • represents the fee portion of the refunded base subtotal
  • does not affect refundState.guestTotal (to avoid double counting)

Relevant call sites:

  • Producer: packages/utils/billing/sections/ServiceFeeSection.ts
  • Consumers: apps/backend/src/data/types/ReservationType.ts, apps/backend/src/core/claims/ClaimService.ts

UI usage of reservation columns (outside Billing UI)

Even though billing line items come from billingUI, many reservation columns are still used directly in UI for non-billing elements:

  • Dates/times, listing title, confirmation code, states: guest itinerary/receipt/invoice pages under apps/guest-frontend/app/(routing)/reservations/

Some screens (notably admin) also display Reservation.total directly:

  • Example: apps/guest-frontend/src/screens/admin/AdminPaymobScreen.tsx