Zum Inhalt
v26.3

Kundenkonto: Guthaben & Treuepunkte

Jeder Kunde (api/v1/customers) führt zwei getrennte, dauerhafte Konten:

  • Prepaid-Guthaben (accountBalance) — Echtgeld-Guthaben, das der Kunde vorab einzahlt und dann bei Käufen als Zahlmittel nutzt. Auszahlbar.
  • Treuepunkte / Bonus (loyaltyPoints / bonusBalance) — Punkte/Bonus, die bei Käufen gutgeschrieben und eingelöst werden. Nicht auszahlbar (Bonus verfällt bei einer Guthaben-Auszahlung).

Diese Seite korrigiert eine ältere Falschangabe in der REST-API-Übersicht: Es gibt kein POST /customers/{id}/credit und keine GET /customers/{id}/transactions — diese Routen existieren im Code nicht. Die echten Routen sind unten dokumentiert.

Endpoints

CustomersController — Basis api/v1/customers:

Methode Endpoint Zweck
GET /{id}/account-transactions Guthaben-/Kontobewegungen (Prepaid-Ledger)
GET /{id}/point-transactions Treuepunkte-Bewegungen
POST /{id}/payout Restguthaben auszahlen (nur accountBalance, nicht Bonus)
POST /{id}/settle Offenen Saldo (z. B. Disco-Schuldschein) durch Einzahlung ausgleichen
GET /card/{cardId} Kunde per Kundenkarte auflösen
GET /{id}/invoice-summary Rechnungs-/Abo-Übersicht des Kunden

Beide Transaktions-Endpoints akzeptieren optional dateFrom/dateTo als Query-Parameter zur Eingrenzung des Zeitraums.

Kontobewegungen (account-transactions)

GET /api/v1/customers/cus_a1b2c3/account-transactions?dateFrom=2026-07-01&dateTo=2026-07-31
Authorization: Bearer eyJ...
{
  "success": true,
  "data": [
    {
      "id": "cat_001",
      "customerId": "cus_a1b2c3",
      "customerName": "Max Mustermann",
      "receiptId": "rec_9f8e7d",
      "type": 0,
      "amount": 50.00,
      "balanceAfter": 50.00,
      "bonusBalanceAfter": 2.50,
      "description": "Guthaben-Aufladung: Guthaben 50€ x 1",
      "personalId": "usr_kasse1",
      "transactionDate": "2026-07-05T18:22:00Z"
    }
  ]
}

type ist ein int ohne eigenes öffentliches Enum-Schema; in der Praxis beobachtete Werte: 0 Aufladung/Einzahlung, 1 Verwendung (Bezahlung mit Guthaben), 2 Korrektur (manuelle Stornoberichtigung), 3 Bonusgutschrift, 4 Bonusverwendung/-verfall, 5 Auszahlung.

Punktebewegungen (point-transactions)

GET /api/v1/customers/cus_a1b2c3/point-transactions
Authorization: Bearer eyJ...
{
  "success": true,
  "data": [
    {
      "id": "cpt_001",
      "customerId": "cus_a1b2c3",
      "customerName": "Max Mustermann",
      "receiptId": "rec_9f8e7d",
      "articleId": "art_cola",
      "articleName": "Cola 0,3l",
      "type": 0,
      "points": 3.5,
      "pointsAfter": 42.5,
      "description": "Punkte für Verkauf",
      "transactionDate": "2026-07-05T18:22:00Z"
    }
  ]
}

type: 0 = gutgeschrieben (Earned), 1 = eingelöst (Redeemed), 2 = Korrektur (Correction).

Auszahlen (payout)

Zahlt Guthaben aus dem accountBalance aus (nicht aus dem Bonus — der verfällt dabei).

POST /api/v1/customers/cus_a1b2c3/payout
Authorization: Bearer eyJ...
Content-Type: application/json

{ "amount": 20.00 }
{ "success": true, "data": { "success": true, "accountBalance": 30.00, "bonusBalance": 0 } }

Fehlerfall (zu wenig Guthaben) — 400 mit errorCode: "INSUFFICIENT_BALANCE".

Saldo ausgleichen (settle)

Für einen offenen (negativen) Saldo — z. B. ein Disco-Schuldschein (nicht gedeckter Restbetrag beim Verlassen, siehe Disco-API) — zahlt der Kunde ein, der Saldo bewegt sich Richtung Null:

POST /api/v1/customers/cus_a1b2c3/settle
Authorization: Bearer eyJ...
Content-Type: application/json

{ "amount": 15.00, "method": "EC" }

method ist frei (Standard "Bar") und landet nur in der Beschreibung der Buchung — keine Verknüpfung zu einem Zahlungsterminal.

Guthaben aufladen — der echte Weg

Es gibt keinen eigenen /credit-Endpoint. Ein Prepaid-Guthaben wird stattdessen genauso aufgeladen wie jeder andere Verkauf: über die normalen Zahlungs-Endpoints (POST /api/v1/payments/direct oder POST /api/v1/payments/table), mit zwei Bedingungen:

  1. Der verkaufte Artikel hat extraOption: 1 (KundenAufladung) — ein „Aufladeartikel" (z. B. „Guthaben 50 €"), angelegt über die normale Artikel-API (POST /api/v1/articles).
  2. Die Zahlung ist mit customerId an den Kunden verknüpft.

Der Artikelpreis (× Menge) wird dann automatisch dem accountBalance des Kunden gutgeschrieben — zusätzlich zur eigentlichen Zahlungsabwicklung (der Gast bezahlt den Aufladebetrag ganz normal per payments[], z. B. bar oder Karte). Ist am Artikel ein Aufladebonus konfiguriert (cardUploadPercent/cardUploadAmount), wird automatisch zusätzlich Bonus gutgeschrieben.

POST /api/v1/payments/direct
Authorization: Bearer eyJ...
Idempotency-Key: 6f1c2a9e-3b7d-4e21-9b0a-1f2e3d4c5b6a
Content-Type: application/json

{
  "items": [ { "articleId": "art_aufladung50", "quantity": 1 } ],
  "payments": [ { "method": "Cash", "amount": 50.00 } ],
  "customerId": "cus_a1b2c3"
}

Die Aufladung erscheint danach als type: 0-Eintrag in account-transactions (siehe oben), verknüpft über receiptId mit dem entstandenen Kassenbeleg.

Flow

  1. Kunde anlegenPOST /api/v1/customers (Minimum: name/customerNumber).
  2. Guthaben aufladen — Verkauf eines KundenAufladung-Artikels mit customerId (POST /api/v1/payments/direct, s. o.).
  3. Auf Konto buchen — spätere Käufe des Kunden mit payments[].method = "CustomerAccount" ziehen vom accountBalance ab (belastet, nicht aufgeladen).
  4. KontrolleGET /{id}/account-transactions prüft den Verlauf.
  5. Punkte — werden im normalen Verkaufsprozess gutgeschrieben/eingelöst, nachlesbar über GET /{id}/point-transactions.
  6. Auszahlen/AusgleichenPOST /{id}/payout (Restguthaben zurück) oder POST /{id}/settle (offenen Saldo/Schuldschein begleichen).

Abgrenzung: Kundenkonto-Guthaben ↔ Disco-Karten-Guthaben

Zwei unterschiedliche Konzepte, die leicht verwechselt werden:

Kundenkonto-Guthaben Disco-Karten-Guthaben
Endpoint payments/* + customers/* POST /api/v1/disco/guest/{cardId}/topup
Bindung an den Kunden-Datensatz, dauerhaft an die physische Karte, für einen Aufenthalt
Auszahlung POST /{id}/payout Restguthaben beim Checkout (disco/guest/{cardId}/checkout)

→ Details zur Karten-Variante: Disco-API (Cashless-Club).