Zum Inhalt
v26.3

Externe Gutscheinsysteme anbinden

Diese Anleitung zeigt Schritt für Schritt, wie eine fremde Kasse oder Warenwirtschaft die Kettengutscheine von DiKAS nutzt: Gutscheine prüfen, einlösen, erstatten, ausgeben und Änderungen abholen. Sie richtet sich an Entwickler und Dienstleister, die eine Anbindung bauen, und an Ketten-Eigentümer, die den Zugang freigeben.

Eine Nachschlagetabelle aller Aufrufe und Fehlercodes steht in Gutscheine und Fremdsysteme. Die Abläufe an der DiKAS-Kasse selbst beschreibt Filialen & Ketten.

Welche Variante passt?

Ihre Lage Weg Wo beschrieben
Eine Fremdkasse soll DiKAS-Kettengutscheine annehmen oder ausgeben Integrations-API (diese Anleitung) unten
Gutscheine aus einem Altsystem sollen künftig in DiKAS leben Einmalige Übernahme per Import Offene Gutscheine aus einem Altsystem übernehmen
Beide Kassen sollen dieselbe Karte parallel einlösen, der Saldo liegt im anderen System Nur mit einem Anbieter, der verbindlich reserviert, bestätigt und Wiederholungen erkennt Parallelbetrieb vorbereiten

Ein reiner Dateiexport (CSV) eignet sich für die Übernahme oder einen Bericht, nicht für den gleichzeitigen Zahlungsbetrieb: Zwischen „noch 50 €" lesen und „jetzt 20 €" zurückschreiben kann eine zweite Kasse dieselben 50 € einlösen.

Die Grundregeln

  1. DiKAS führt den Restwert. Die Fremdkasse merkt sich keinen Saldo, sie fragt ihn ab und bucht über die Schnittstelle. Es gibt kein Zurückschreiben eines Saldos.
  2. Einlösen ist ein Ablauf in vier Schritten, kein einzelner Aufruf: prüfen, reservieren, Beleg ankündigen, bestätigen. Das schützt vor Doppeleinlösung und vor Buchungen ohne Beleg.
  3. Jeder schreibende Aufruf ist wiederholbar. Er trägt einen Idempotency-Key und eine clientReference. Wiederholen Sie bei Unsicherheit mit demselben Key, nie mit einem neuen.
  4. Beträge sind ganze Zahlen in der kleinsten Einheit (amountMinor): 10000 bei Euro sind 100,00 €. Die Währung steht immer dabei und wird nie umgerechnet.
  5. Gutscheincodes erscheinen nie vollständig in Antworten. Prüfung, Listen und Änderungen zeigen nur die maskierte Nummer (***001X). Nur bei Ausgabe und Aktivierung kommt der volle Code einmal zurück, damit Sie ihn drucken können.

Schritt 1: Zugang einrichten (Ketten-Eigentümer)

Den Zugang legt der Ketten-Eigentümer mit seiner Portal-Anmeldung an. Er bestimmt, was der Zugang darf (Rechte) und für welche Filialen:

Recht Erlaubt
voucher:lookup Gutschein prüfen
voucher:redeem Reservieren, Beleg ankündigen, bestätigen, freigeben
voucher:refund Erstatten
voucher:issue Gutscheine ausgeben und Karten aktivieren
voucher:export Änderungen abholen

Jeder Zugang mit mindestens einem Recht darf außerdem programs und die Vorgangsabfragen (operations/…) aufrufen.

POST /api/v1/portal/chain/<kettenId>/integration-clients
Content-Type: application/json

{"name":"Fremdkasse","scopes":["voucher:lookup","voucher:redeem","voucher:refund"],"branches":["filiale_b"]}

Die Antwort enthält den Zugangsschlüssel (<clientId>.<geheimnis>) genau einmal. DiKAS speichert nur einen Hash und kann ihn nicht wiederherstellen. Legen Sie den Schlüssel sofort in einem Tresor ab, nie in ein Protokoll, eine Mail oder ein Ticket.

Vergeben Sie nur die Rechte, die die Anbindung wirklich braucht, und nur die Filialen, in denen sie arbeitet. Ein Zugang sieht ausschließlich seine freigegebenen Filialen; alles andere antwortet wie „unbekannt" (404).

Schritt 2: Verbindung testen

Alle Aufrufe gehen an /api/v1/integrations/vouchers. Der Schlüssel kommt als Authorization: Bearer <schluessel> oder als X-Api-Key:

GET /api/v1/integrations/vouchers/programs
Authorization: Bearer <schluessel>

programs nennt das Gutscheinprogramm der Kette mit Währung, Fähigkeiten sowie den Filialen und Rechten Ihres Zugangs. Steht dort paidWriteAllowed=false, kann der Anbieter hinter dem Programm Reservierung, Wiederholungsschutz oder Vorgangsabfrage nicht sicher leisten; dann enden schreibende Aufrufe vor der Ausführung mit 422 PROVIDER_UNSUPPORTED. Bei Kettengutscheinen von DiKAS ist das nicht der Fall.

Antwortet der Server mit 401, stimmt der Schlüssel nicht (oder er wurde erneuert); 403 INSUFFICIENT_SCOPE heißt, dem Zugang fehlt das Recht für den Aufruf.

Schritt 3: Einen Gutschein einlösen

3.1 Prüfen

POST /api/v1/integrations/vouchers/lookup
Authorization: Bearer <schluessel>
Content-Type: application/json

{"code":"<gescannter Code>","branchId":"filiale_b"}

Die Antwort enthält die voucherId, die maskierte Nummer, den verfügbaren Betrag, Währung und Status. Abgelaufene oder entwertete Gutscheine melden sich nicht als einlösbar. Gilt ein Einzweckgutschein an dieser Filiale nicht, kommt VOUCHER_NOT_ACCEPTED_HERE – bevor etwas gebucht wird.

3.2 Betrag reservieren

POST /api/v1/integrations/vouchers/reservations
Idempotency-Key: kasse7-bon4711-res
Content-Type: application/json

{"voucherId":"<aus lookup>","amountMinor":3000,"currency":"EUR","branchId":"filiale_b","clientReference":"kasse7-bon4711","allowPartial":false}

Die Reservierung bindet den Betrag für 15 Minuten. Mit allowPartial: false wird ganz oder gar nicht reserviert; mit true so viel, wie verfügbar ist (die Antwort nennt den reservierten Betrag, den Rest zahlt der Gast anders). Merken Sie sich die reservationId.

3.3 Beleg ankündigen – vor dem ersten Speichern

POST /api/v1/integrations/vouchers/reservations/<reservationId>/prepare
Idempotency-Key: kasse7-bon4711-prep
Content-Type: application/json

{"receiptReference":"B-4711","clientReference":"kasse7-bon4711"}

Rufen Sie prepare auf, bevor Ihre Kasse den Beleg speichert, und halten Sie die Zuordnung Reservierung ↔ Beleg lokal fest. Ab jetzt verfällt die Reservierung nicht mehr von selbst: Stürzt Ihre Kasse nach dem Speichern ab, geht der Gutschein nicht wieder frei, sondern bleibt gesperrt, bis die Buchung geklärt ist. Das ist gewollt, weil sonst derselbe Betrag doppelt eingelöst werden könnte.

3.4 Bestätigen

POST /api/v1/integrations/vouchers/reservations/<reservationId>/commit
Idempotency-Key: kasse7-bon4711-commit
Content-Type: application/json

{"receiptReference":"B-4711","clientReference":"kasse7-bon4711"}

Der Beleg muss derselbe sein wie bei prepare, sonst antwortet DiKAS 409 RECEIPT_MISMATCH. Die Antwort enthält die transactionId der Einlösung. Speichern Sie sie am Beleg – Sie brauchen sie für eine spätere Erstattung.

3.5 Verkauf abgebrochen

Entsteht der Beleg nie, geben Sie die Reservierung frei:

POST /api/v1/integrations/vouchers/reservations/<reservationId>/release
Idempotency-Key: kasse7-bon4711-rel

release funktioniert auch nach prepare. Rufen Sie es dann aber nur auf, wenn sicher kein Beleg entstanden ist. Wurde der Beleg angekündigt, klären Sie zuerst, ob er gespeichert wurde: Ja → commit; nein → release.

Schritt 4: Erstatten

Wird ein Beleg storniert oder eine Ware zurückgegeben, erstatten Sie über die transactionId der Einlösung:

POST /api/v1/integrations/vouchers/refunds
Idempotency-Key: kasse7-bon4711-refund1
Content-Type: application/json

{"originalTransactionId":"<transactionId>","amountMinor":1000,"currency":"EUR","receiptReference":"S-88","clientReference":"kasse7-storno88","reason":"Retoure"}
  • Ohne amountMinor wird der volle noch erstattbare Betrag zurückgegeben; Teilbeträge sind mehrfach möglich, bis die Einlösung ausgeschöpft ist (REFUND_LIMIT_EXCEEDED).
  • Erstattet wird nur in der Filiale, die eingelöst hat.
  • Ein inzwischen entwerteter Gutschein lässt sich nicht erstatten (VOUCHER_VOID). Ob ein abgelaufener noch erstattet wird, bestimmt die Kettenregel (VOUCHER_EXPIRED, wenn ausgeschaltet).
  • Ist die Einlösung noch nicht bestätigt, kommt REDEMPTION_PENDING – erst commit oder release klären.
  • reason (Grund) ist Pflicht.

Schritt 5: Gutscheine ausgeben und Karten aktivieren

Mit dem Recht voucher:issue (gilt auch für complete und cancel) kann eine Fremdkasse Gutscheine verkaufen. Ausgabe (POST /issues) und Aktivierung einer vorgedruckten Karte (POST /activations) sind vorläufig: Der Gutschein hat noch kein Guthaben, bis der Verkaufsbeleg gespeichert ist. Danach:

Situation Aufruf
Beleg gespeichert POST /operations/{operationId}/complete mit receiptReference
Verkauf abgebrochen POST /operations/{operationId}/cancel mit reason

Der volle Code steht nur in der ersten Antwort. Drucken Sie ihn sofort; eine spätere Abfrage zeigt nur die maskierte Nummer. Ob eine abgebrochene Aktivierung die vorgedruckte Karte wieder freigibt oder entwertet, legt die Kette in den Regeln für Gutscheine fest.

Schritt 6: Änderungen abholen

GET /api/v1/integrations/vouchers/changes?cursor=<letzter cursor>&limit=100

Der Feed liefert bestätigte Buchungen in der Reihenfolge, in der DiKAS sie bestätigt hat, mit einem nextCursor. Speichern Sie den Cursor erst, nachdem Sie die Seite verarbeitet haben. Reservierungen erscheinen nicht. Ein zu alter Cursor antwortet 410 CURSOR_EXPIRED: Lesen Sie den Bestand neu und beginnen Sie ohne Cursor. Ein Cursor mit falschem Aufbau ergibt INVALID_CURSOR. Fragen Sie in regelmäßigen Abständen ab.

Wenn eine Antwort ausbleibt

Das ist der wichtigste Fall in der Praxis. Bricht die Verbindung ab, während Sie reservieren, bestätigen oder erstatten:

  1. Nicht mit neuem Key und neuer Referenz erneut buchen.
  2. Den Aufruf mit demselben Idempotency-Key und demselben Inhalt wiederholen – DiKAS liefert das Ergebnis der ersten Ausführung und bucht nie ein zweites Mal.
  3. Antwortet DiKAS mit 202 und dem Zustand unknown, ist der Ausgang noch nicht geklärt. 202 heißt nicht bezahlt. Fragen Sie den Vorgang ab, mit operationId oder mit Ihrer Referenz:
GET /api/v1/integrations/vouchers/operations/by-reference/kasse7-bon4711
  1. Erst wenn der Vorgang klar „durchgeführt" oder „nicht durchgeführt" meldet, handeln Sie weiter.

Derselbe Key mit anderem Inhalt – auch mit anderer clientReference – wird mit 409 IDEMPOTENCY_CONFLICT abgewiesen, ohne zu buchen. Eine fehlende Pflichtangabe (zum Beispiel receiptReference) ergibt 400 VALIDATION_ERROR und ist nicht wiederholbar, bevor Sie sie ergänzt haben.

Abnahmetest vor dem Echtbetrieb

Spielen Sie diese Fälle mit einem Testgutschein durch und prüfen Sie jeweils Antwort und Restwert in DiKAS:

# Fall Erwartung
1 Prüfen, reservieren, ankündigen, bestätigen Restwert um den Betrag niedriger, eine transactionId
2 Denselben commit zehnmal wiederholen Ein Abzug, immer dieselbe Antwort
3 Gleicher Key, anderer Betrag 409 IDEMPOTENCY_CONFLICT, kein Abzug
4 Reservieren, dann release Betrag wieder verfügbar
5 commit mit anderem Beleg als bei prepare 409 RECEIPT_MISMATCH
6 Mehr einlösen als verfügbar mit allowPartial: false / true Abweisung / Teilbetrag
7 Teilerstattung, Rest, dann zu viel Betrag zurück, dann REFUND_LIMIT_EXCEEDED
8 Erstattung aus fremder Filiale Abweisung (nur die einlösende Filiale)
9 Falsche Währung CURRENCY_MISMATCH, kein Abzug
10 Einzweckgutschein an fremdem Rechtsträger VOUCHER_NOT_ACCEPTED_HERE
11 Antwort „verloren" nachstellen (Aufruf abbrechen, wiederholen, dann operations/by-reference) Genau eine Buchung
12 Cursor aus changes weiterführen Nur neue Ereignisse, keine Lücke

Betrieb

  • Schlüssel erneuern: POST …/integration-clients/<clientId>/rotate-key. Der alte Schlüssel gilt sofort nicht mehr, es gibt keine Übergangsfrist. Planen Sie das Umstellen der Kasse vorher.
  • Zugang sperren: PUT …/integration-clients/<clientId>/blocked mit {"blocked":true}. Aufrufe antworten danach mit CLIENT_BLOCKED.
  • Drosselung: 600 Aufrufe pro Minute und Zugang. Danach 429 mit Retry-After; warten Sie die angegebene Zeit ab und wiederholen Sie mit demselben Key.
  • Filiale tritt aus: wirkt bei der nächsten Anfrage; die Filiale ist für den Zugang dann „unbekannt".
  • Ein Server: Betreiben Sie die Kette mit einem Server; mehrere gleichzeitige Instanzen derselben Datenbank sind nicht unterstützt.
  • Fehler melden: Für die Klärung genügen Filiale, Belegreferenz, Betrag, Zeitpunkt und die maskierte Nummer. Schicken Sie nie Zugangsschlüssel oder vollständige Gutscheincodes in Tickets oder Mails.

Die maschinenlesbare Beschreibung (OpenAPI gutschein-gateway-v1.yaml) erhalten Sie von Ihrem DiKAS-Ansprechpartner.

Häufige Fragen

Muss ich den Restwert selbst speichern? Nein. Fragen Sie vor jeder Einlösung per lookup ab; DiKAS ist die einzige Quelle.

Darf die Fremdkasse den Gutschein „leer buchen" und später korrigieren? Nein. Reservieren Sie den Betrag und bestätigen Sie ihn mit dem Beleg; Korrekturen laufen über Erstattung.

Was passiert, wenn meine Kasse zwischen prepare und commit abstürzt? Der Gutschein bleibt gesperrt, bis die Buchung geklärt ist. Nach dem Neustart prüft Ihre Kasse, ob der Beleg gespeichert wurde, und ruft dann commit (Beleg vorhanden) oder release (kein Beleg) auf – mit den gespeicherten Keys.

Können mehrere Kassen denselben Zugang nutzen? Ja, solange jeder Aufruf eigene, eindeutige Keys und Referenzen trägt. Für getrennte Verantwortung legen Sie je Kasse einen eigenen Zugang an; dann lässt sich ein einzelner sperren.