API Disco (Club Cashless)¶
Il modulo Disco rappresenta un'attività di club/discoteca gestita in modo completamente senza contanti: ogni ospite riceve all'ingresso una carta (chip/RFID/NFC) che funge da conto per l'intera permanenza. Tutto — ingresso, bevande, guardaroba, ordinazioni al tavolo — viene addebitato sulla carta e regolato solo all'uscita (oppure tramite ricarica prepagata). L'API copre l'intero ciclo di vita di una carta: ingresso → addebito → ricarica → regolamento → uscita.
Tutti gli endpoint si trovano sotto il prefisso comune api/v1/disco (tre controller condividono
questo prefisso: ospiti/carte, statistiche, configurazione — più l'API hardware del tornello sotto
api/v1/disco/turnstile). Come in tutti gli ambiti: JWT-Bearer, envelope di risposta
{ "success": true, "data": { ... } }, campi in camelCase, ID opachi.
Richiede il modulo Disco
Richiede il modulo Disco; senza licenza attiva del modulo, gli endpoint restituiscono
HTTP 403 (errorCode: "MODULE_NOT_LICENSED").
ID carta vs. ID interno
Ogni record ha un id interno (chiave del database) e un guestId — è questo il numero
della carta con cui il chip è fisicamente contrassegnato. Tutte le rotte {cardId} si
aspettano il guestId, non l'id interno.
Ospiti & carte¶
DiscoController — base api/v1/disco:
| Metodo | Endpoint | Scopo |
|---|---|---|
POST |
/enter |
Far entrare l'ospite al tornello (scansione carta all'ingresso) |
POST |
/leave |
Far uscire l'ospite con pagamento (scansione all'uscita) |
POST |
/leave-combined |
Regolare e chiudere insieme più carte („coppie") su un unico scontrino |
POST |
/book |
Addebitare un articolo sulla carta (bar/banco) |
POST |
/book/batch |
Addebitare più articoli in una volta sulla carta |
POST |
/book/{guestId}/{bonIndex}/void |
Stornare una posizione addebitata |
POST |
/table/book-on-card |
Addebitare un'ordinazione al tavolo sulla carta (sostituisce il pagamento al tavolo) |
POST |
/wardrobe/return/{guestId}/{bonIndex} |
Restituzione al guardaroba (rimuove il n°, l'importo resta) |
GET |
/guest/{cardId} |
Ospite/carta incl. consumo attuale (solo carte aperte) |
GET |
/guest/{cardId}/status |
Stato della carta, anche se bloccata/stornata/regolata |
GET |
/guest/{cardId}/checkout |
Vista di regolamento: importo aperto, copertura prepagata, può uscire |
GET |
/guests |
Tutti gli ospiti attualmente presenti (con check-in) |
POST |
/guest/{cardId}/topup |
Ricaricare il credito prepagato |
POST |
/guest/{cardId}/transfer |
Trasferire carta/credito su un'altra carta (cambio carta) |
POST |
/guest/{cardId}/lock |
Bloccare la carta (non sono più possibili addebiti) |
POST |
/guest/{cardId}/unlock |
Sbloccare la carta |
POST |
/guest/{cardId}/storno |
Stornare l'intera carta e contrassegnarla come moroso |
POST |
/guest/{cardId}/collect-storno |
Incassare successivamente alla cassa una carta stornata |
POST |
/guest/{cardId}/image |
Salvare una foto della carta |
POST |
/terminal/pay |
Avviare un pagamento EC self-service al terminale informativo |
POST |
/personal/{cardId}/close |
Regolare una carta del personale (consumo proprio) |
POST |
/personal/close-all |
Regolare tutte le carte del personale aperte come consumo proprio |
POST |
/close-all |
Chiudere tutte le carte aperte (fine attività/chiusura giornaliera) |
Addebitare sulla carta¶
POST /api/v1/disco/book addebita un articolo su una carta aperta (vendita al bar/banco senza
pagamento — il pagamento avviene solo al 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"
}
Risposta (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 }
]
}
}
Stato della carta¶
GET /api/v1/disco/guest/{cardId}/status restituisce lo stato indipendentemente dal fatto che
la carta sia ancora aperta — a differenza di GET /guest/{cardId}, che trova solo le carte
aperte. Così bar/guardaroba/cassa riconoscono anche le carte bloccate o stornate.
{
"success": true,
"data": {
"found": true,
"isOpen": true,
"isLocked": false,
"stornoed": false,
"guest": { "id": "dgu_9f8e7d", "guestId": "crd_10042", "amount": 7.00, "uploadAccount": 20.00 }
}
}
Consumo attuale¶
GET /api/v1/disco/guest/{cardId} restituisce la stessa DiscoGuestResponse di cui sopra, incl.
gli addebiti aperti in corso (openBons) e il credito prepagato (uploadAccount). Risposta
404 GUEST_NOT_FOUND se la carta non è (più) aperta.
Regolamento/checkout¶
GET /api/v1/disco/guest/{cardId}/checkout non modifica nulla — è una semplice anteprima di
quanto la carta debba ancora, di quanto sia coperto dal credito prepagato e se possa uscire
automaticamente (self-checkout, cassa d'uscita mobile, tornello).
{
"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 è uno tra ok | pay | locked | closed | notfound.
Tavolo su carta¶
POST /api/v1/disco/table/book-on-card sposta le posizioni aperte di un tavolo su una carta Disco
(modalità tavolo Disco) — sostituisce il pagamento in contanti/EC al tavolo, i limiti della carta
restano validi, il tavolo si libera.
openBonIds: null trasferisce tutte le posizioni aperte; un elenco trasferisce solo un giro
„immediato".
Ricaricare¶
POST /api/v1/disco/guest/{cardId}/topup ricarica il credito prepagato della carta
(uploadAccount) — l'importo è liberamente scelto, un credito residuo viene rimborsato al
checkout.
La risposta è di nuovo la DiscoGuestResponse aggiornata (uploadAccount aumentato).
Rischio di confusione: credito della carta Disco vs. credito del conto cliente
Questo è il prepagato della carta di una singola permanenza in discoteca (decade/viene rimborsato all'uscita). Un credito permanente del conto cliente è un concetto a sé — vedi Conto cliente: credito e punti fedeltà.
Statistiche¶
DiscoStatsController — base api/v1/disco:
| Metodo | Endpoint | Scopo |
|---|---|---|
GET |
/stats |
KPI in tempo reale (fatturato, ospiti presenti, affluenza per gruppo/postazione/personale/ora) |
GET |
/search |
Cercare carte/ospiti (numero carta, nome, periodo, genere, filtro storni, paginazione) |
GET |
/personal/{ownerId}/activity |
Attività di una carta del personale (consumo, storno, rotture) |
GET |
/daylog/{date} |
Registro giornaliero per una data |
Esempio: 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 supporta tra l'altro cardId, name, dateFrom/dateTo, gender,
timeFrom/timeTo, workplaceId, enterGroupId, wardrobe, stornoed, offset/limit,
excludeImages.
Configurazione¶
DiscoConfigController — base api/v1/disco, classico CRUD per i gruppi di ingresso/prezzo
(„Entergroups" — stabiliscono ad es. il prezzo d'ingresso, il limite della carta, le bevande
omaggio, il consumo minimo):
| Metodo | Endpoint | Scopo |
|---|---|---|
GET |
/entergroups |
Tutti i gruppi di ingresso |
GET |
/entergroups/{id} |
Singolo gruppo di ingresso |
POST |
/entergroups |
Creare un gruppo di ingresso |
PUT |
/entergroups/{id} |
Aggiornare un gruppo di ingresso |
DELETE |
/entergroups/{id} |
Eliminare un gruppo di ingresso |
Hardware del tornello¶
DiscoTurnstileController — api/v1/disco/turnstile/{key}/{cardId} (GET/POST).
Nessun JWT — autenticazione tramite API key nell'URL ({key}), in modo che un semplice
controller del tornello possa richiamare la rotta senza un flusso di login. 200 = la carta può
uscire, ogni altro codice porta con sé un motivo (bloccata/aperta/già regolata/sconosciuta/key
errata).
→ Dettagli, configurazione e tabella dei codici di stato: Tornello Disco.
Flusso end-to-end¶
Un tipico percorso dell'ospite, tutti i passaggi contro 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 } ] }'
Caso critico — la carta non può pagare all'uscita (moroso):
# 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 } ] }'