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,currencyguestServiceFee,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:
bookingis created on successful PAY and is typically markedcompletedguestrefundandhostpayoutare created/upserted on settlement and start aspending
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