Disco-API (Cashless-Club)¶
Das Disco-Modul bildet einen komplett unbar geführten Club-/Diskothekenbetrieb ab: Jeder Gast bekommt beim Einlass eine Karte (Chip/RFID/NFC), die während des ganzen Aufenthalts als Konto dient. Alles — Eintritt, Getränke, Garderobe, Tischbestellungen — wird auf die Karte gebucht und erst beim Verlassen (oder per Prepaid-Aufladung) abgerechnet. Die API deckt den kompletten Lebenszyklus einer Karte ab: einlassen → buchen → aufladen → abrechnen → verlassen.
Alle Endpoints liegen unter dem gemeinsamen Präfix api/v1/disco (drei Controller teilen sich
diesen Prefix: Gäste/Karten, Statistiken, Konfiguration — plus die Drehkreuz-Hardware-API unter
api/v1/disco/turnstile). Wie bei allen Bereichen: JWT-Bearer, Response-Envelope
{ "success": true, "data": { ... } }, camelCase-Felder, opake IDs.
Erfordert das Modul Disco
Erfordert das Modul Disco; ohne aktive Modul-Lizenz liefern die Endpoints HTTP 403
(errorCode: "MODULE_NOT_LICENSED").
Karten-ID vs. interne ID
Jeder Datensatz hat eine interne id (Datenbank-Schlüssel) und eine guestId — das ist die
Kartennummer, mit der der Chip physisch beschriftet ist. Alle {cardId}-Routen erwarten die
guestId, nicht die interne id.
Gäste & Karten¶
DiscoController — Basis api/v1/disco:
| Methode | Endpoint | Zweck |
|---|---|---|
POST |
/enter |
Gast am Drehkreuz einlassen (Karten-Scan Eingang) |
POST |
/leave |
Gast auschecken mit Zahlung (Scan Ausgang) |
POST |
/leave-combined |
Mehrere Karten („Pärchen") gemeinsam auf einem Beleg abrechnen & schließen |
POST |
/book |
Artikel auf Karte buchen (Bar/Theke) |
POST |
/book/batch |
Mehrere Artikel auf einmal auf Karte buchen |
POST |
/book/{guestId}/{bonIndex}/void |
Gebuchte Position stornieren |
POST |
/table/book-on-card |
Tischbestellung auf Karte buchen (löst Tisch-Zahlung ab) |
POST |
/wardrobe/return/{guestId}/{bonIndex} |
Garderoben-Rückgabe (entfernt #Nr., Betrag bleibt) |
GET |
/guest/{cardId} |
Gast/Karte inkl. aktuellem Umsatz (nur offene Karten) |
GET |
/guest/{cardId}/status |
Kartenstatus, auch gesperrt/storniert/abgerechnet |
GET |
/guest/{cardId}/checkout |
Abrechnungsansicht: offener Betrag, Prepaid-Deckung, darf-gehen |
GET |
/guests |
Alle aktuell anwesenden (eingecheckten) Gäste |
POST |
/guest/{cardId}/topup |
Prepaid-Guthaben aufladen |
POST |
/guest/{cardId}/transfer |
Karte/Guthaben auf andere Karte übertragen (Kartentausch) |
POST |
/guest/{cardId}/lock |
Karte sperren (keine Buchungen mehr möglich) |
POST |
/guest/{cardId}/unlock |
Karte entsperren |
POST |
/guest/{cardId}/storno |
Ganze Karte stornieren + als Nichtzahler markieren |
POST |
/guest/{cardId}/collect-storno |
Storno-Karte an der Kasse nachkassieren |
POST |
/guest/{cardId}/image |
Karten-Foto hinterlegen |
POST |
/terminal/pay |
Selbstbedienungs-EC-Zahlung am Infoterminal auslösen |
POST |
/personal/{cardId}/close |
Eine Personalkarte abrechnen (Eigenverbrauch) |
POST |
/personal/close-all |
Alle offenen Personalkarten als Eigenverbrauch abrechnen |
POST |
/close-all |
Alle offenen Karten schließen (Betriebsende/Tagesabschluss) |
Auf Karte buchen¶
POST /api/v1/disco/book bucht einen Artikel auf eine offene Karte (Bar/Theke-Verkauf ohne
Zahlung — die Zahlung erfolgt erst bei leave/Checkout).
POST /api/v1/disco/book
Authorization: Bearer eyJ...
Content-Type: application/json
{
"guestId": "crd_10042",
"articleId": "art_cola",
"articleName": "Cola 0,3l",
"count": 2,
"price": 3.50,
"workplaceId": "wp_bar1",
"workplaceName": "Bar 1"
}
Antwort (200 OK, DiscoGuestResponse):
{
"success": true,
"data": {
"id": "dgu_9f8e7d",
"guestId": "crd_10042",
"gender": 1,
"enterDate": "2026-07-12T22:05:00Z",
"amount": 7.00,
"payedAmount": 0,
"uploadAccount": 20.00,
"iou": 0,
"isOpen": true,
"isLocked": false,
"openBons": [
{ "articleId": "art_cola", "articleName": "Cola 0,3l", "price": 3.50, "count": 2, "total": 7.00,
"workplaceId": "wp_bar1", "workplaceName": "Bar 1", "bookDate": "2026-07-12T23:10:00Z", "isVoided": false }
]
}
}
Kartenstatus¶
GET /api/v1/disco/guest/{cardId}/status liefert den Status unabhängig davon, ob die Karte
noch offen ist — anders als GET /guest/{cardId}, das nur offene Karten findet. Damit erkennt Bar/
Garderobe/Kasse auch gesperrte oder stornierte Karten.
{
"success": true,
"data": {
"found": true,
"isOpen": true,
"isLocked": false,
"stornoed": false,
"guest": { "id": "dgu_9f8e7d", "guestId": "crd_10042", "amount": 7.00, "uploadAccount": 20.00 }
}
}
Aktueller Umsatz¶
GET /api/v1/disco/guest/{cardId} liefert dieselbe DiscoGuestResponse wie oben, inkl. der
laufenden offenen Buchungen (openBons) und des Prepaid-Guthabens (uploadAccount). Antwort
404 GUEST_NOT_FOUND, wenn die Karte nicht (mehr) offen ist.
Abrechnung/Checkout¶
GET /api/v1/disco/guest/{cardId}/checkout verändert nichts — reine Vorschau, was die Karte noch
schuldet, was das Prepaid-Guthaben deckt und ob sie automatisch raus darf (Selbst-Checkout, mobile
Ausgangskasse, Drehkreuz).
{
"success": true,
"data": {
"cardId": "crd_10042",
"found": true,
"isOpen": true,
"isLocked": false,
"consumption": 7.00,
"netOwed": 7.00,
"prepaid": 20.00,
"amountToPay": 0,
"refund": 13.00,
"canAutoSettle": true,
"status": "ok",
"message": "Karte kann raus — Restguthaben 13.00 €"
}
}
status ist eine von ok | pay | locked | closed | notfound.
Tisch auf Karte¶
POST /api/v1/disco/table/book-on-card verlagert die offenen Positionen eines Tisches auf eine
Disco-Karte (Disco-Tischmodus) — ersetzt die Bar-/EC-Zahlung am Tisch, die Karten-Limits gelten
weiter, der Tisch wird frei.
openBonIds: null überträgt alle offenen Positionen; eine Liste überträgt nur eine „Sofort"-Runde.
Aufladen¶
POST /api/v1/disco/guest/{cardId}/topup lädt das Karten-Prepaid-Guthaben (uploadAccount) auf —
der Betrag ist frei wählbar, ein Restguthaben wird beim Checkout zurückerstattet.
Antwort ist wieder die aktualisierte DiscoGuestResponse (uploadAccount erhöht).
Verwechslungsgefahr: Disco-Karten-Guthaben vs. Kundenkonto-Guthaben
Das hier ist das Karten-Prepaid eines einzelnen Disco-Aufenthalts (verfällt/erstattet sich beim Verlassen). Ein dauerhaftes Kundenkonto-Guthaben ist ein eigenes Konzept — siehe Kundenkonto: Guthaben & Punkte.
Statistiken¶
DiscoStatsController — Basis api/v1/disco:
| Methode | Endpoint | Zweck |
|---|---|---|
GET |
/stats |
Live-Kennzahlen (Umsatz, anwesende Gäste, Auslastung nach Gruppe/Arbeitsplatz/Personal/Stunde) |
GET |
/search |
Karten/Gäste suchen (Kartennummer, Name, Zeitraum, Geschlecht, Storno-Filter, Paging) |
GET |
/personal/{ownerId}/activity |
Aktivität einer Personalkarte (Verzehr, Storno, Bruch) |
GET |
/daylog/{date} |
Tagesprotokoll für ein Datum |
Beispiel: GET /api/v1/disco/stats
{
"success": true,
"data": {
"totalGuests": 214,
"currentGuests": 87,
"totalMale": 96,
"totalFemale": 118,
"totalRevenue": 4211.50,
"averageSpend": 19.68,
"byGroup": [ { "groupId": "eg_std", "groupName": "Standard", "count": 180, "revenue": 3500.00 } ],
"byWorkplace": [ { "workplaceId": "wp_bar1", "workplaceName": "Bar 1", "count": 400, "revenue": 2200.00 } ],
"byStaff": [ { "personalId": "per_1", "personalName": "Mo", "count": 120, "revenue": 900.00 } ],
"byHour": [ { "hour": 23, "count": 60, "revenue": 800.00 } ]
}
}
GET /search unterstützt u. a. cardId, name, dateFrom/dateTo, gender, timeFrom/timeTo,
workplaceId, enterGroupId, wardrobe, stornoed, offset/limit, excludeImages.
Konfiguration¶
DiscoConfigController — Basis api/v1/disco, klassisches CRUD für Eintritts-/Preisgruppen
(„Entergroups" — legen z. B. Eintrittspreis, Kartenlimit, Freigetränke, Mindestverzehr fest):
| Methode | Endpoint | Zweck |
|---|---|---|
GET |
/entergroups |
Alle Eintrittsgruppen |
GET |
/entergroups/{id} |
Einzelne Eintrittsgruppe |
POST |
/entergroups |
Eintrittsgruppe anlegen |
PUT |
/entergroups/{id} |
Eintrittsgruppe aktualisieren |
DELETE |
/entergroups/{id} |
Eintrittsgruppe löschen |
Drehkreuz-Hardware¶
DiscoTurnstileController — api/v1/disco/turnstile/{key}/{cardId} (GET/POST).
Kein JWT — Authentifizierung per API-Key in der URL ({key}), damit ein einfacher
Drehkreuz-Controller die Route ohne Login-Flow ansprechen kann. 200 = Karte darf raus, jeder andere
Code trägt einen Grund (gesperrt/offen/bereits abgerechnet/unbekannt/falscher Key).
→ Details, Einrichtung und Statuscode-Tabelle: Disco-Drehkreuz.
Ende-zu-Ende-Flow¶
Ein typischer Gastdurchlauf, alle Schritte gegen crd_10042:
TOKEN="eyJ..."
API="https://ihre-instanz.dikas.de/api/v1"
# 1. Einlass am Drehkreuz (Kartenscan Eingang)
curl -X POST "$API/disco/enter" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "guestId": "crd_10042", "gender": 1, "enterGroupId": "eg_std" }'
# 2. Status prüfen (z. B. Wiedereinlass nach kurzem Verlassen)
curl "$API/disco/guest/crd_10042/status" -H "Authorization: Bearer $TOKEN"
# 3. Prepaid aufladen
curl -X POST "$API/disco/guest/crd_10042/topup" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "amount": 20.00, "paymentMethod": "Bar" }'
# 4. An der Bar buchen (einzeln oder als Batch)
curl -X POST "$API/disco/book" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "guestId": "crd_10042", "articleId": "art_cola", "articleName": "Cola 0,3l", "count": 2, "price": 3.50 }'
# 5. Laufenden Umsatz abfragen
curl "$API/disco/guest/crd_10042" -H "Authorization: Bearer $TOKEN"
# 6. Tischbestellung auf die Karte buchen
curl -X POST "$API/disco/table/book-on-card" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "tableId": "tbl_12", "cardId": "crd_10042" }'
# 7. Vor dem Ausgang: Checkout-Vorschau
curl "$API/disco/guest/crd_10042/checkout" -H "Authorization: Bearer $TOKEN"
# 8. Verlassen mit Zahlung (falls noch etwas offen ist)
curl -X POST "$API/disco/leave" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "cardId": "crd_10042", "payments": [ { "method": "Cash", "amount": 0 } ] }'
Störfall — Karte kann am Ausgang nicht bezahlen (Nichtzahler):
# Karte stornieren: als Nichtzahler markieren, Verzehr als Schwund verbuchen
curl -X POST "$API/disco/guest/crd_10042/storno" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "reason": "Karte verloren" }'
# Später an der Kasse nachkassiert
curl -X POST "$API/disco/guest/crd_10042/collect-storno" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "payments": [ { "method": "Cash", "amount": 7.00 } ] }'