Salta ai contenuti

Reservation folios and manual payments

Questi contenuti non sono ancora disponibili nella tua lingua.

Open a reservation and use Folio. Check-in posts its frozen nightly prices. Add a catalog extra, enter a quantity and service date, and register a manual payment. The check-out dialog previews the balance after releasing unserved nights (at least the first night stays charged). What the dialog asks of you depends on who is leaving:

  • Not the last room, with a folio of its own (see Per-room settlement below): the room settles that folio, with the same rules as the last room below for a zero balance, a credit and an amount owed. The rest of the reservation is untouched.
  • Not the last room, without a folio of its own: check-out is always allowed. The balance is shown for information and is settled when the last room leaves.
  • Last room, balance zero: check out.
  • Last room, credit (the guest paid more than the bill, for example a prepaid stay left early): check-out is allowed with stays.check_out. The credit stays on the account and appears under Refunds due on the Today screen (folios.credits_due), where a member with folios.refund opens the account and refunds it.
  • Last room, balance owed: settle it first. Only a member with folios.approve_open_balance (built-in: administrators) may confirm check-out with the balance open, with a reason; that approval is audited with the amount, currency, room and reason.

A preview holds no balance: check-out revalidates it inside the writer transaction.

When a room’s rate plan includes services (its package, see rates), each served night posts two kinds of charge: Accommodation, the night’s price less its package, and one line per included service (for example Breakfast × 3) with the service’s own tax category, source package and quantity. The night’s total is unchanged; the split only says what it pays for, so tax modules see each service under its category and the revenue report shows package components apart from room revenue. Package lines follow their room’s nights to the room’s own folio or to a folio routing room nights.

Each line is reconciled on its own: a change that moves value between the room and its package reverses and reposts only the lines whose value changed, and a reversed (waived) service stays waived until its value changes. Posting, as an extra, a service the stay’s package already includes is refused with folio.included_in_package (409); send beyond_package: true to charge what the package does not cover, such as a second breakfast. Other extras are charged as before.

A reservation’s account holds one or more folios, each with its own payer. The first folio always exists, belongs to the booker and closes only at the last check-out; open up to nine more with Add folio (folios.split, built-in: front desk, accounting and administrators):

  • Payer: the booker, another live guest of the workspace (merging that guest moves the payer to the surviving guest), or a company with its billing details (address, tax number: free text, printed on its statement).
  • Room (optional, multi-room reservations): the folio settles that room on its own.
  • Routes: which new postings the folio collects. Room nights (not on a room’s own folio, which already takes that room’s nights), one catalog extra, or every charge of a tax category (for example food_beverage). A route belongs to one folio of the reservation: giving it to another folio moves it there.

New postings land on a folio this way: an extra or a payment posted on a named folio stays there; otherwise a folio attached to the charge’s room takes that room’s nights and policy fees, then the folio routing room nights, the extra or its tax category, then the first folio. A payment without a folio goes to the first folio. A tax module’s additional lines follow their charge. Routes skip closed folios. Re-priced nights (a date change, a price correction, a re-rate) stay on the folio of the night they replace.

Move to folio (folios.transfer, built-in: front desk, accounting and administrators) moves selected charges and payments to another open folio, with a reason. Nothing is edited or deleted: each moved line gets a pair of transfer movements, one on the folio it leaves and one on the folio it enters, that net to zero. The reservation’s balance and its charge, payment and refund totals never change, and a transfer is never revenue. A charge moves with its additional lines; a payment moves only while it has no refunds; a reversal, a refund and a transfer do not move. A reversal or a refund is booked on the folio its movement is on now. Each folio shows its own balance, charges, payments and refunds: the balance of a folio is the sum of its rows.

Close a folio other than the first once its balance is exactly zero, or, when its company payer is linked to a counterparty, to move what it owes to that counterparty’s open item (receivables). The check-out of the reservation’s last room closes every folio still open, the first one included; undoing that check-out reopens the folios it closed. Each folio that closes records one folio.closed event, and each that reopens one folio.reopened; both are webhook and events API types (folio.closed@1, folio.reopened@1), with the folio as subject. A closed folio takes no new charge and no transfer until it is reopened (folios.split), which an extra found after departure needs; payments, refunds and reversals still settle and correct what it holds, so a credit left at check-out is returned on its folio. Room-charge reconciliation (a channel’s price amendment) also still posts on a closed folio.

In a multi-room booking, open a folio for one room (Separate folio for room …), move that room’s nights to it, and settle it when the room leaves. Its future nights and policy fees land there by themselves. Check-out of that room, when it is not the last one, looks at the balance of the folios attached to it; the last room still looks at the whole reservation. A credit left on a room’s folio appears under Refunds due as soon as the room has left.

Print a statement for one folio, with its payer and billing details, or for the whole reservation. Refunds due lists one line per folio that holds its payer’s money: a folio of a finished or cancelled reservation, or the folio of a room that has left.

Each folio action has its own permission; the built-in roles grant them as follows. Administrators and accounting manage the property extra catalog (folios.configure_extras). Front desk, accounting and administrators post extras (folios.post) and manual cash, external-terminal card, bank transfer or other payments (folios.post_payment). A payment before check-in is a deposit. Accounting and administrators refund (folios.refund) an unreversed payment, up to its remaining refundable amount, or reverse a movement (folios.correct); both corrections require a reason, and the screen offers each only when it can succeed (a payment with active refunds cannot be reversed; a fully refunded one has nothing left to refund). They also recalculate taxes (folios.rerate, below). Front desk settles an owed balance but does not refund or approve it. Viewer and revenue manager read the folio (folios.read); housekeeping cannot read folios, catalog prices or checkout balances. A custom role holds exactly the codes it grants.

The account shows the net view by default: a charge and its reversal disappear as a pair, the nights of one room collapse into a line (“Accommodation · 3 nights”, expandable), and a tax module’s additional lines sit under their charge. Show corrections switches to the full ledger, in posting order, where a reversal reads “Reversal of «Breakfast»”. The printable statement follows the same choice (Include corrections and reversals) and carries the property, the statement date, the guest, the stay, the rooms, the totals and the tax lines.

A same-day check-out still charges the first night, and a late check-in charges the missed first night. Only a member with folios.correct (built-in: administrators and accounting) removes it, by reversing the night with a reason. A configurable first-night rule remains planned.

Posting rules: a payment, refund or reversal cannot be dated after the property’s business date; an extra’s service date is within the stay and cannot be posted to a cancelled reservation (payments for a cancelled booking, such as deposits, still can). Amounts are compared as money: retrying 10.5 as 10.500 with the same key is the same operation.

Every posting is immutable, carries its actor and instant, and has a service date and a posting key. A retry of the same operation and body returns the existing account (or, to a caller without folios.read, the same acknowledgement as below) without another movement or audit. Changed intent with the same key returns folio.key_conflict. Clients retain the key after an uncertain network outcome. Opening a new form represents a new operation. Do not automatically replay a failed write with a fresh key.

Positive amounts increase debt; payments are negative. Refunds are positive; reversals negate the original amount and frozen taxes. Payment and refund totals exclude reversed movements; the balance equals the sum of all signed movements. Gross totals and balances must fit safe integer minor units. Money on the wire is always { amount: "12.50", currency: "GBP" }, including zero- and three-decimal currencies, without floating-point conversion in the domain. Folio currency is frozen from the reservation.

In-house departure and price corrections reverse changed nights and post replacements. Unchanged charges keep their original tax snapshot. A manual room-charge reversal is a waiver until that night’s base price changes. Undo check-in/out also reconciles charges; there is no deletion. OTA price amendments use the same reconciler inside intake, including worker processing; failed financial/audit writes roll back the reservation and prevent acknowledgement. Metadata-only revisions preserve charge snapshots. An OTA cancellation reverses only the obligation of a room nobody stayed in: a departed room keeps its stayed nights at their frozen prices and their charges, whatever amounts the cancellation carries, and its reservation stays open for postings (ADR 0028). Price amendments of a room that is not cancelled are reconciled as above. Cancellation posts no room penalties; a deposit needs an explicit refund. Migration 0009 imports frozen charges for already in-house rooms, with system actors, audit and events. Prior off-system payments must be entered explicitly. It does not invent historical balances for reservations that already departed.

All paths below are under /api/v1/properties/{property_id}. A session, device token or API key authenticates, and GuestCraft-Workspace-Id selects the workspace. Folio operations serialize with reservation writes; they use posting keys rather than reservation ETags. Checkout still requires the current quoted reservation version in If-Match.

Operation ID Method and path Purpose
folios.get GET /reservations/{id}/folio The reservation’s account: every folio with its totals, movements, frozen tax lines, signed balance and totals
folioExtras.list GET /extras Catalog, including archived entries
folioExtras.create POST /extras Name, exact unit price and neutral tax category
folioExtras.update PUT /extras/{extra_id} Change or archive a catalog item without changing old postings
folioTaxCategories.list GET /tax-categories Category codes the catalog accepts: core codes plus the module’s, with labels
folioCreditsDue.list GET /credits-due One line per folio that holds a credit after the stay (refunds due), with its number, name and payer
folios.postExtra POST /reservations/{id}/folio/extras posting_key, extra_id, integer quantity, ISO date, optional folio_id (else routed)
folios.pay POST /reservations/{id}/folio/payments posting_key, decimal amount, method, reference, date, optional folio_id (else the first folio)
folios.refund POST /reservations/{id}/folio/movements/{movement_id}/refunds Payment reference, amount, date, key and reason
folios.reverse POST /reservations/{id}/folio/movements/{movement_id}/reversals Original reference, date, key and reason; reverses the charge’s additional lines too
folios.rerate POST /reservations/{id}/folio/rerate folios.rerate: calculate taxes again with the installed module; posting_key, reason
folios.transfer POST /reservations/{id}/folio/transfers folios.transfer: posting_key, date, target folio_id, movement_ids (1–100), reason
reservationFolios.list GET /reservations/{id}/folios The folios, first one first: payer, room, routes, state and version, without money
reservationFolios.get GET /reservations/{id}/folios/{folio_id} One folio with its version as ETag
reservationFolios.create POST /reservations/{id}/folios folios.split: name, payer, room_id, routes; 201 with ETag and Location
reservationFolios.update PATCH /reservations/{id}/folios/{folio_id} folios.split, If-Match: the fields named change
reservationFolios.close POST /reservations/{id}/folios/{folio_id}/close folios.split, If-Match: a folio with a zero balance, never the first
reservationFolios.reopen POST /reservations/{id}/folios/{folio_id}/reopen folios.split, If-Match: a closed folio
reservationRooms.checkoutBalance GET /reservations/{id}/rooms/{room_id}/checkout_balance Read-only quote: signed amount and currency after early checkout adjustments, last_room, scope (room: the amount of the room’s own folios; reservation: the whole account), settlement_required
reservationRooms.checkOut POST /reservations/{id}/rooms/{room_id}/check_out Optional { open_balance_reason }; only used, and only with folios.approve_open_balance, when settlement_required is true; ignored otherwise

Write answers (API revision 4.0.0). folios.postExtra, folios.pay, folios.refund, folios.reverse, folios.rerate and folios.transfer answer 200 with a FolioWriteResult. A caller that also holds folios.read for the property receives the Folio. Any other caller receives a FolioAcknowledgement with only id, workspace_id, property_id and reservation_id: a write grant alone never reads the account, not even through a retry or a re-rate that changes nothing. Check for movements before reading money from the answer, or grant the integration folios.read for the property. This change is an exception to the addition-only rule of /v1, explained in ADR 0016. The web folio screen requires folios.read, so a role with only a write grant posts through the API, for example with an API key limited to folios.post for a point-of-sale integration.

The MIT SDK exposes the operation groups above and generated response validation; @guestcraft/react/query supplies query factories. Reservation list summaries and Today stays include optional balance for financial readers (negative is a credit). Each folio movement also carries reversed_by, reversible, transferable and, for payments, refundable, so a client never guesses which corrections will be accepted. folio_id is the folio a movement is booked on and located_in the folio a charge, payment or refund is on now; a transfer leg names its other folio in counterpart_folio_id. The account’s id is its first folio’s, as before; folios lists every folio with its own totals. Reservation totals and nightly prices are also optional: housekeeping projections omit all financial amounts and open-balance approval reasons. A missing amount grants no financial access and must not be interpreted as zero.

@guestcraft/core/folios exports the TaxCalculator port (see ADR 0029). The composition root can inject it through HttpRuntimeOptions.taxCalculator. That adapter also reaches HTTP intake and authenticated cron workers. A standalone worker accepts it as the third argument to createBookingWorker, and composeBookingImport accepts taxCalculator explicitly.

interface TaxCalculator {
calculate(input: TaxInput): TaxResult | Promise<TaxResult>; // awaited before any write
readonly categories?: readonly { code: string; label: string }[];
}
// TaxInput: propertyId, reservationId, date, category (a code), quantity, base (Money),
// stay: { source, roomId, roomTypeId, adults, children, nights, nightIndex }
// TaxResult: { computed, lines: TaxLine[] (included or added to the price),
// charges?: { rule, label, category, amount }[] (extra neutral charge lines) }

Call it inside the posting transaction: read your own tables if you need to, never wait on an external service. For a per-person, per-night levy, charge stay.adults * rate as an additional line on accommodation charges; stay.nightIndex and stay.nights give caps. Categories are canonical codes: the core has accommodation, food_beverage, service and other; a module adds codes with labels, which the extras catalog then offers. The core validates currencies, nonnegative safe amounts and total bounds, then freezes the result. Reversals negate that exact snapshot without calling the adapter. The default adapter computes no taxes; no country’s tax rules are implemented here.

Charges posted before a module existed keep their uncomputed snapshot until a member with folios.rerate runs Recalculate taxes on the reservation. It reverses and reposts only the charges whose result would change, on the folio each charge is on, never touches base prices or waived charges, and is audited with a reason. A bulk tax recalculation over a whole property is planned; re-pricing stays at the current rates in bulk is a different operation, see bulk re-rating.

The UI prints an HTML statement clearly marked non-fiscal. It is not an invoice or fiscal receipt. Fiscal documents, statutory taxes, company accounts receivable (open items and allocations across reservations) and OTA collections remain planned. No external payment service or customer credential is required by this workflow. Online payments through the workspace’s own Stripe account, and cash settlement with country-pack rounding, are described in online payments: their folio movements are source: provider (method card_online) and source: rounding (method cash_rounding), and change only through the provider or with their cash movement.