---
title: "Payment + Booking Distributed Transaction"
description: "Model payment and supplier booking as a distributed transaction using authorization, capture, compensation, UNKNOWN outcomes and reconciliation."
slug: "payment-booking-distributed-transaction"
translationKey: "architecture-payment-booking-distributed-transaction"
locale: "en"
type: "guide"
category: "architecture"
tags: ["payment","booking","distributed-transaction","saga","reconciliation","idempotency"]
publishedAt: "2026-09-27"
updatedAt: "2026-09-27"
reviewedAt: "2026-09-27"
technicalVerifiedAt: "2026-09-27"
codeExampleStatus: "illustrative"
---

A payment gateway and a supplier booking API do not participate in one ACID transaction. Travel systems therefore need to model **booking + payment as a distributed transaction**, with success, failure and unknown outcomes handled independently for every external step.

## The core problem

Two outcomes need to line up:

1. the traveler payment obligation,
2. the supplier reservation.

They happen in different systems, so intermediate divergence is normal:

- payment authorized, booking failed,
- booking confirmed, capture failed,
- payment timeout with unknown outcome,
- booking timeout with unknown outcome,
- cancellation succeeded but refund failed,
- refund succeeded while local booking state stayed stale.

## There is no single transaction

```mermaid
%% title: Payment and booking distributed transaction
%% description: Payment authorization, provider booking and capture use separate failure and compensation paths.
flowchart LR
  A[Booking Intent] --> B[Payment Authorize]
  B -->|failed| C[Stop / Payment Failed]
  B -->|authorized| D[Provider Booking Create]
  D -->|confirmed| E[Payment Capture]
  D -->|failed| F[Void Authorization]
  D -->|unknown| G[Booking Reconciliation]
  G -->|confirmed| E
  G -->|failed| F
  E -->|captured| H[Booking Complete]
  E -->|failed/unknown| I[Payment Recovery / Ops]
```

## Why authorize → book → capture?

In many models, authorizing first and capturing only after booking confirmation reduces risk:

- the traveler is not fully charged when no booking exists,
- a failed booking can be followed by an authorization void.

This is not universal. Some suppliers or commercial models require full capture, pay-at-property, or virtual-card settlement.

## Other sequencing models

### Book → pay

Create the reservation first, then collect payment.

Risk: a payment failure may require cancelling an already-confirmed booking.

### Pay/capture → book

Finalize payment before the reservation.

Risk: if booking fails, the system must compensate with a refund or void.

### Pay at property

The platform may not orchestrate the payment, but booking state and payment liability still need separate modeling.

## Canonical transaction state

Do not use one `transaction_status`. Keep separate state machines:

```text
PaymentState:
CREATED -> AUTHORIZING -> AUTHORIZED -> CAPTURING -> CAPTURED
                        -> FAILED
                        -> UNKNOWN
                        -> VOIDED
                        -> REFUNDED

BookingState:
REQUESTED -> CONFIRMED
          -> FAILED
          -> UNKNOWN
          -> CANCELLED
```

An orchestration state can sit above them:

```text
BookingTransaction:
STARTED
PAYMENT_READY
BOOKING_PENDING
BOOKING_CONFIRMED
PAYMENT_FINALIZING
COMPLETED
RECOVERY_REQUIRED
COMPENSATING
FAILED
```

## Saga pattern

Distributed work cannot be rolled back like one database transaction, so use compensation.

```mermaid
%% title: Travel booking compensation saga
%% description: Compensation steps after booking or payment failures.
flowchart TD
  A[Authorize Payment] --> B[Create Booking]
  B -->|confirmed| C[Capture Payment]
  B -->|failed| D[Void Authorization]
  C -->|capture failed| E[Retry / Alternative Capture]
  E -->|cannot recover| F[Cancel Booking]
  F --> G[Void or Refund Payment]
```

Compensation is not a true rollback. Supplier cancellation fees or payment costs may already exist.

## UNKNOWN outcomes can happen on both sides

### Booking UNKNOWN

The supplier request times out. Do not blindly repeat create; perform lookup and reconciliation.

### Payment UNKNOWN

The gateway times out. Starting a new payment transaction can double-charge the traveler. Use the provider's idempotency or status-lookup mechanism.

## Idempotency boundaries

Use a distinct key per semantic operation:

- booking create idempotency key,
- payment authorization key,
- capture key,
- cancellation key,
- refund key.

Do not reuse one key across different operations.

## Transaction log

The orchestrator should retain immutable attempt history as well as current state.

```json
{
  "bookingIntentId": "bi_123",
  "operation": "BOOK",
  "attempt": 1,
  "idempotencyKey": "book_bi_123_v1",
  "status": "UNKNOWN",
  "providerReference": null,
  "startedAt": "2026-09-27T10:00:00Z"
}
```

## Recovery policy

| Situation | First action |
|---|---|
| Payment authorization UNKNOWN | provider lookup / idempotent status query |
| Booking create UNKNOWN | booking lookup / reconciliation |
| Booking FAILED + payment AUTHORIZED | void authorization |
| Booking CONFIRMED + capture FAILED | capture recovery; cancel if policy requires |
| Cancellation CONFIRMED + refund FAILED | retry/reconcile refund |
| Refund UNKNOWN | payment lookup; no blind retry |

## Outbox for local consistency

A supplier may confirm the booking while local event publication fails. Use a transactional outbox:

```text
local transaction:
  update booking state
  insert outbox event
commit

background publisher:
  publish outbox event
  mark published
```

This does not make the external booking call ACID. It only improves consistency between local state and event publication.

## Retry policy

Retry should not happen simply because an error looks technical.

A safe retry requires:

- idempotent semantics or provider idempotency support,
- known outcome of the previous attempt,
- exponential backoff with jitter,
- bounded retry budget,
- rate-limit awareness.

Booking create without idempotency is one of the most dangerous operations to retry.

## Reconciliation worker

Continuously inspect records such as:

- long-lived UNKNOWN booking,
- AUTHORIZED payment with no terminal booking,
- CONFIRMED booking with incomplete payment,
- CANCELLED booking with incomplete refund,
- local/provider state mismatch.

Fetch authoritative provider evidence and repair canonical state.

## Manual operations queue

Some divergence needs human review. An operations queue should show:

- traveler-safe booking summary,
- provider reference,
- payment reference,
- current booking/payment states,
- attempt history,
- suggested next action,
- risk indicators such as duplicate charge, duplicate booking or cancellation fee.

## Failure modes

- booking confirmed while capture fails,
- payment captured while booking is FAILED/UNKNOWN,
- compensation itself timing out,
- duplicate capture/refund retries,
- local outbox/state diverging from provider state,
- manual operations creating a second booking or refund.

## Observability

Track:

- authorization success rate,
- booking confirmation rate,
- capture success rate,
- compensation rate,
- void/refund latency,
- booking UNKNOWN rate,
- payment UNKNOWN rate,
- booking-payment divergence count,
- reconciliation age,
- manual-intervention rate,
- duplicate-prevention hits.

## Production checklist

- separate payment and booking state machines,
- choose sequencing explicitly per provider/commercial contract,
- use idempotency keys for every write operation,
- model UNKNOWN as a first-class state,
- define compensation policy,
- provide reconciliation workers and manual operations queues,
- use a transactional outbox for local state/event consistency.

## Design principle

**Payment and Booking are separate state machines; orchestration coordinates them.**

Keeping intent, attempt, evidence and compensation explicit makes timeouts and partial failures manageable instead of hiding them inside one status field.
