Skip to article

One booking, many money movements

Travel payments are not a checkout problem. They are a lifecycle of collection, supplier settlement, partial changes, refunds and reconciliation that must share one source of truth.

A traveller sees one total and presses one button. Behind that moment, the booking may contain a flight, room and activity supplied by different companies, priced in different currencies and payable on different schedules.

Then the flight changes. The activity moves to another day. The room is partially refundable.

This is why travel payments cannot be designed as a thin checkout layer. Checkout is the front door to a money lifecycle. The architecture has to connect what the customer paid, what each supplier is owed, what the business earns and what changes later. That lineage should attach to the same item hierarchy described in the travel order lifecycle, rather than inventing a parallel finance-only booking model.

Begin with four distinct questions

Teams get into trouble when “payment status” tries to answer everything. Separate the questions:

  1. Collection: did we successfully collect or authorise customer funds?
  2. Order: which travel components are confirmed, pending or failed?
  3. Settlement: which supplier obligation is due, through which rail and currency?
  4. Accounting: how do all movements map to revenue, cost, liability and refund positions?

These states influence each other, but they are not interchangeable. A successful card charge does not prove a supplier confirmed. A confirmed booking does not prove the supplier has been paid. A supplier-approved refund does not mean the customer sees the money yet.

Give every movement a shared lineage

The most important design choice is identifier lineage. Every financial event should trace back through a consistent hierarchy:

trip → order → component → supplier obligation → payment movement

Customer collections, virtual-card authorisations, bank transfers, fees, FX adjustments, refunds and chargebacks should all carry enough reference data to rejoin that chain.

Do not rely on free-text descriptors or a supplier’s reference as the only key. Supplier references can arrive late, change format or represent several components. Your own immutable identifiers should anchor the system, while external references remain indexed attributes.

OnArrival’s Payments product is organised around a travel ledger that follows money from offer through booking, segment and ancillary. Whether you use that layer or build your own, the principle is the same: money needs to inherit the structure of the trip.

A practical state model

Use explicit state machines instead of a universal paid: true field.

ObjectExample statesCritical question
Customer paymentcreated, authorized, captured, failed, reversed, refundedWhat did the customer’s payment method do?
Booking componentpending, confirmed, changed, cancelled, failedWhat travel service currently exists?
Supplier payablenot due, scheduled, issued, accepted, failed, nettedWhat do we owe the supplier?
Refundrequested, supplier-approved, funded, submitted, completed, failedWhere is the return of funds now?
Ledger entrypending, posted, reversedHas finance recorded the economic event?

Transitions should be event-driven, timestamped and idempotent. Store why a transition occurred and which command or external message caused it.

One charge can create a settlement tree

Consider a bundle with a flight, a hotel and an activity. The customer sees one charge, while the supplier side requires an airline settlement path, a hotel payment on a different rail and a third schedule for the activity operator. Margin may belong partly to each component. A cancellation can affect only one branch.

The correct collection shape on the customer side is one authorisation for the total, then a capture per component as each supplier confirms. That is the single-auth, staged-capture pattern the travel cart essay derives from the commit saga. It matters here because each capture is born knowing which component it pays for. Captures are the joint where customer money meets supplier money, and the joint only holds if it exists at component grain.

COLLECTION COMPONENT CAPTURES SUPPLIER SETTLEMENT captured per confirmation, never before CUSTOMER one auth · INR 52,300 C1 · FLIGHT capture INR 31,400 C2 · HOTEL capture INR 17,600 C3 · ACTIVITY capture INR 3,300 AIRLINE · EUR 316.00 virtual card BEDBANK · USD 168.75 bank transfer · due T+30 OPERATOR · THB 1,190 rail per supplier hotel cancels later REFUND WALKS THE SAME EDGES BACK supplier recovery · reverses capture C2 only · INR 17,600 to the original method
One authorisation fans out into component captures, each capture funds one supplier obligation, and a refund walks the same edges backwards.

The settlement plan should therefore be generated from confirmed order components, not directly from the cart total. Each payable needs:

  • Supplier and booking-component identity
  • Amount and settlement currency
  • Due date or release condition
  • Selected rail
  • FX rate and source where relevant
  • Current position, retries and external reference
  • Links to adjustments or refund netting

This model supports virtual cards, industry settlement systems and bank rails without pretending they behave identically. The OnArrival product surface describes several supplier payout methods under one reconciled booking model; the unifying layer is the ledger, not the rail.

Treat FX as order data

When display, collection and supplier settlement currencies differ, the exchange rate affects both customer experience and margin. Record the rate used at quote, any lock or expiry, the rate applied at capture or settlement, and resulting variance.

Do not recompute historical amounts using today’s rate. The order needs an immutable monetary snapshot: currency, precision, price components, rate context and timestamps.

If a booking changes, create adjustment entries rather than rewriting the past. Finance should be able to explain the difference between the original promise and the final outcome.

Refunds reverse economics, not history

A refund should reference the original payment and the specific cancelled or adjusted component. It should not delete the original sale or mutate a captured amount into a smaller fictional transaction.

A robust refund flow answers:

  • What is the customer entitled to under the accepted rules?
  • Has the supplier approved or returned the corresponding value?
  • Is the business advancing funds before supplier recovery?
  • Which fees or penalties remain?
  • Which payment method receives the refund?
  • What ledger entries reverse revenue, cost or liability?
  • Can part of the supplier position be netted against a future payable?

In data terms, a refund is a first-class object that points backwards. For the cancelled hotel in the diagram above:

{
  "refund_id": "ref_204",
  "order_id": "ord_7ac1",
  "component_id": "cmp_hotel_2",
  "original_capture": "cap_c2",
  "customer_amount": { "currency": "INR", "value": 17600 },
  "supplier_recovery": {
    "payable_id": "spl_02",
    "expected": { "currency": "USD", "value": 168.75 },
    "state": "supplier_approved",
    "settlement": "net_against_next_payable"
  },
  "fx_context": "rate snapshot from cap_c2, never today's rate",
  "advanced_before_recovery": true,
  "ledger_entries": ["reverse_revenue", "reverse_supplier_cost", "recognize_recovery_receivable"]
}

advanced_before_recovery is the field finance argues about. If the business refunds the customer before the bedbank returns the USD, that float is a real position, and it should be visible as a receivable rather than hidden inside a net number.

Those decisions vary by product and commercial model. The architecture should represent them without losing the link to the booking.

Reconciliation should be continuous

Month-end should confirm the system, not discover it.

Reconcile at several levels:

  1. Gateway or bank movement to payment object
  2. Payment object to customer order
  3. Supplier payout to payable and booking component
  4. Refund movement to refund object and original collection
  5. Ledger balances to external statements and safeguarded or operating accounts as applicable

Build exception queues around typed causes: missing reference, amount mismatch, currency mismatch, unexpected duplicate, overdue supplier acceptance. A single “unmatched” bucket hides the product defect.

Concretely: the bundle from the settlement tree above crosses at least four external statements over its life, and each line has exactly one internal record it must match.

External statement lineAmountShould matchOutcome
Gateway payout, batch PG-8841+INR 52,300Payment pay_9f2: captures C1, C2, C3Matched at capture level
Virtual card clearing, airline-EUR 316.00Payable spl_01 for component C1Matched; INR 12.40 funding-rate variance posted as an adjustment
Bank debit, bedbank-USD 171.00Payable spl_02, scheduled at USD 168.75Exception: amount mismatch, typed supplier-overcharge, owner assigned
Card refund, ref RF-204-INR 17,600Refund ref_204 against capture C2Matched to the original collection; USD 168.75 recovery receivable stays open

Rows one, two and four confirm the system. Row three is the process working as designed: the mismatch is caught the day the statement arrives, typed, owned and sized at USD 2.25, instead of surfacing months later as an unexplained write-off.

Product, support and finance need one vocabulary

The itinerary might say “cancelled” while finance says “refund submitted” and the bank says “processing.” All three can be true. They should be connected and translated into plain language.

Support needs read access to the relevant financial timeline without access to raw payment credentials. Finance needs component and supplier context without opening support tickets. Customers need the part that affects them: amount, destination, current stage and next expected event. The post-booking trust guide shows how those operational states become customer language.

This shared model also improves incident response. If a supplier payout rail fails, operators can identify affected obligations without treating every customer booking as failed. If capture succeeds but confirmation does not, the system can route recovery or reversal deliberately.

Architecture review checklist

Before launch, ask:

  • Can every cent be traced to an order and component?
  • Are collection, booking, settlement and refund states separate?
  • Are commands idempotent across retries and webhooks?
  • Are price and FX snapshots immutable?
  • Can partial changes create adjustments without rewriting history?
  • Does supplier settlement start from confirmed obligations?
  • Can finance explain a customer refund from a single timeline?
  • Are reconciliation exceptions typed and owned?
  • Can support see financial status without seeing sensitive credentials?
  • Do customer-facing labels reflect what the system actually knows?

Travel money becomes manageable when it follows the order instead of living beside it. One customer action may create many movements, but they should tell one consistent story.