Direkte Buchungsstrecke ohne Warenkorb: Daten erfassen, Gutschein prüfen, Zahlungsweise wählen, Erklärungen einholen, Platz serverseitig reservieren und über Stripe Checkout bezahlen. Keine Überbuchung, auch bei parallelen Checkouts.
Umgesetzt
Buchungsdialog lazy geladen mit Fokusführung und Escape (src/components/booking/BookingDialog.tsx), mobile Buchungsleiste
/api/booking/checkout legt Buchung, Preis-Snapshot (per DB-Trigger unveränderlich), Ratenplan und Reservierung in einer Transaktion an und startet eine Stripe-Checkout-Session (src/lib/booking-service.ts, src/lib/checkout.ts)
Kapazität: Sperre der Camp-Zeile SELECT … FOR UPDATE, dann der Gutscheinzeile; Test- und Livebuchungen getrennt gezählt; keine öffentliche Platzzahl (ADR-4)
Idempotenz bei Doppelklick, Same-Origin- und JSON-Pflicht für schreibende Endpunkte, Rate Limits
Preis erst unter der gewählten Zahlungsweise anzeigen (7a3e37f, 06.10.2026)
Erklärung „Ich bin volljährig“ und Zustimmung zum Rateneinzug mit Zeitstempel
Offene Entscheidungen
Teilnahme Minderjähriger: Die Buchung verlangt derzeit Volljährigkeit; Bestätigung oder andere Regel durch den Inhaber steht aus (gesammelt in #32).
Code: ja · Geprüft: ja (Integrationstests gegen PostgreSQL, Stripe-Attrappe) · Gemergt: ja · Produktiv: ja (im Cluster belegt ausgerollt sha-5a88f3d am 13.10.2026); Live-Verkauf gesperrt, siehe #22
Stand: 2026-10-16 (Quelle: git origin/main 9785eb2, Gitea-CI-Läufe und Registry, Memory der Sessions)
## Ziel
Direkte Buchungsstrecke ohne Warenkorb: Daten erfassen, Gutschein prüfen, Zahlungsweise wählen, Erklärungen einholen, Platz serverseitig reservieren und über Stripe Checkout bezahlen. Keine Überbuchung, auch bei parallelen Checkouts.
## Umgesetzt
- [x] Buchungsdialog lazy geladen mit Fokusführung und Escape (`src/components/booking/BookingDialog.tsx`), mobile Buchungsleiste
- [x] `/api/booking/checkout` legt Buchung, Preis-Snapshot (per DB-Trigger unveränderlich), Ratenplan und Reservierung in einer Transaktion an und startet eine Stripe-Checkout-Session (`src/lib/booking-service.ts`, `src/lib/checkout.ts`)
- [x] Preisbindung: Session läuft 30 Minuten (`expires_at` = `priceLockedUntil`), Platz bleibt 10 Minuten länger reserviert (ADR-2)
- [x] Kapazität: Sperre der Camp-Zeile `SELECT … FOR UPDATE`, dann der Gutscheinzeile; Test- und Livebuchungen getrennt gezählt; keine öffentliche Platzzahl (ADR-4)
- [x] Idempotenz bei Doppelklick, Same-Origin- und JSON-Pflicht für schreibende Endpunkte, Rate Limits
- [x] Preis erst unter der gewählten Zahlungsweise anzeigen (`7a3e37f`, 06.10.2026)
- [x] Erklärung „Ich bin volljährig“ und Zustimmung zum Rateneinzug mit Zeitstempel
## Offene Entscheidungen
Teilnahme Minderjähriger: Die Buchung verlangt derzeit Volljährigkeit; Bestätigung oder andere Regel durch den Inhaber steht aus (gesammelt in #32).
## Grundlage
`docs/ARCHITEKTUR.md` (ADR-1, ADR-2, ADR-4), `docs/TESTS.md` (8 parallele Buchungen auf 2 Plätze). Webhooks: #6, Freigabe: #10.
## Status
Code: ja · Geprüft: ja (Integrationstests gegen PostgreSQL, Stripe-Attrappe) · Gemergt: ja · Produktiv: ja (im Cluster belegt ausgerollt sha-5a88f3d am 13.10.2026); Live-Verkauf gesperrt, siehe #22
Stand: 2026-10-16 (Quelle: git origin/main 9785eb2, Gitea-CI-Läufe und Registry, Memory der Sessions)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Ziel
Direkte Buchungsstrecke ohne Warenkorb: Daten erfassen, Gutschein prüfen, Zahlungsweise wählen, Erklärungen einholen, Platz serverseitig reservieren und über Stripe Checkout bezahlen. Keine Überbuchung, auch bei parallelen Checkouts.
Umgesetzt
src/components/booking/BookingDialog.tsx), mobile Buchungsleiste/api/booking/checkoutlegt Buchung, Preis-Snapshot (per DB-Trigger unveränderlich), Ratenplan und Reservierung in einer Transaktion an und startet eine Stripe-Checkout-Session (src/lib/booking-service.ts,src/lib/checkout.ts)expires_at=priceLockedUntil), Platz bleibt 10 Minuten länger reserviert (ADR-2)SELECT … FOR UPDATE, dann der Gutscheinzeile; Test- und Livebuchungen getrennt gezählt; keine öffentliche Platzzahl (ADR-4)7a3e37f, 06.10.2026)Offene Entscheidungen
Teilnahme Minderjähriger: Die Buchung verlangt derzeit Volljährigkeit; Bestätigung oder andere Regel durch den Inhaber steht aus (gesammelt in #32).
Grundlage
docs/ARCHITEKTUR.md(ADR-1, ADR-2, ADR-4),docs/TESTS.md(8 parallele Buchungen auf 2 Plätze). Webhooks: #6, Freigabe: #10.Status
Code: ja · Geprüft: ja (Integrationstests gegen PostgreSQL, Stripe-Attrappe) · Gemergt: ja · Produktiv: ja (im Cluster belegt ausgerollt sha-5a88f3d am 13.10.2026); Live-Verkauf gesperrt, siehe #22
Stand: 2026-10-16 (Quelle: git origin/main
9785eb2, Gitea-CI-Läufe und Registry, Memory der Sessions)