# ADR — Flux de réservation, hold de sièges et paiement

Statut : retenu pour le MVP. À revisiter en v2 (synchro temps réel avec les
guichets physiques).

## Problème

Une compagnie vend aussi des places au guichet physique, en parallèle de
l'app. Sans coordination, l'app peut vendre un siège déjà vendu en gare.

## Solution v1 : quota par départ

Chaque `Departure` a un quota fixe (`app_seats_quota`) alloué à l'app,
indépendant des ventes guichet. L'app ne peut jamais vendre plus que ce
quota, donc jamais en conflit avec le guichet.

## Cycle de vie d'un Booking

```
pending  → (paiement confirmé par webhook) → paid
pending  → (10 min écoulées sans paiement)  → expired (siège restitué)
pending  → (utilisateur annule)             → cancelled (siège restitué)
```

1. `POST /bookings` : transaction DB avec `lockForUpdate()` sur la ligne
   `Departure`, décrément de `app_seats_available`, création du `Booking`
   en `pending` avec `reserved_until = now()->addMinutes(10)`.
2. `POST /payments/initiate` : appelle l'agrégateur, crée un `Payment` en
   `initiated`. Le `Booking` reste `pending`.
3. `POST /payments/webhook` (appelé par l'agrégateur, pas par les clients) :
   - vérifie la signature HMAC,
   - upsert idempotent sur `provider_tx_id`,
   - recalcule le montant attendu côté serveur (ne fait jamais confiance au
     payload pour le montant),
   - si tout correspond → `Booking.status = paid`, génère le `Ticket`
     (QR + code SMS), dispatch le job d'envoi SMS.
4. Job planifié `ExpireStaleBookings` (cron, toutes les minutes) : repasse
   en `expired` tout `Booking` `pending` dont `reserved_until` est dépassé,
   restitue `app_seats_available`.
5. Job quotidien `ReconcilePayments` : liste les transactions côté
   agrégateur, les compare aux `Payment` en base, log tout écart.

## Ce qu'on exclut volontairement du MVP

- Synchronisation temps réel avec le système de caisse du guichet physique.
- Choix précis du siège (numéro de place) — le MVP réserve un nombre de
  sièges, pas un siège nommé.
- Remboursement automatisé.

## Pourquoi pas une réservation optimiste sans verrou ?

Le volume reste faible au lancement (quelques compagnies, quelques départs
par jour), donc le coût du verrou DB est négligeable et la garantie de ne
jamais survendre prime sur la performance à ce stade.
