Entwicklerdokumentation

Changelog

Was sich in der öffentlichen API, im JavaScript- und .NET-SDK und im WordPress-Plugin geändert hat, Release für Release, neueste zuerst.

Kuratiert aus den GitHub-Releases: Aufgeführt ist nur, was eine Integration bemerken kann; interne, Konsolen- und Infrastrukturänderungen bleiben weg. Versionen sind die vX.Y.Z-Tags; dieselbe Version benennt den CDN-Pfad, die NuGet-Pakete und das WordPress-Zip des Releases (Versionen und Releases).

v0.20.1 — 2026-10-31

  • Apps — Die Android-App ist bei Google Play und die Windows-App im Microsoft Store verfügbar; bookdineplay.com und app.bookdineplay.com verlinken jetzt beide. Die Versionen im App Store folgen nach Abschluss der Prüfung durch Apple.

v0.20.0 — 2026-10-29

  • E-Mails — Jede E-Mail von BookDinePlay (Buchungsbestätigungen, Event-Anmeldungen und Tickets, Gutscheine, Nachrichten der Location, Abrechnung) hat jetzt ein gestaltetes HTML-Layout neben der unveränderten Textfassung; E-Mails einer Location tragen den Hinweis „Gesendet von BookDinePlay im Auftrag von …“. Die Betreffzeilen bleiben gleich.
  • Kontakt — Die veröffentlichte Kontaktadresse ist jetzt überall hello@threebit.io, auch in den AGB und im Auftragsverarbeitungsvertrag.
  • Apps — Die BookDinePlay-Apps für iPhone, iPad, Mac, Android und Windows: Location-Verzeichnis, Buchung, QR-Bestellung am Tisch und für Location-Teams der Operator-Modus mit Tarifen in der App. Android App Links werden jetzt auch für Installationen aus Google Play bestätigt.

v0.19.0 — 2026-10-29

  • API — GET /api/venues/{venueSlug}/specials?date=&days= listet die Specials der Location für 1 bis 14 aufeinanderfolgende Tage ab date (Standard: das heutige Datum der Location). Die Route gibt es in jedem Tarif und sie braucht wie die übrigen Location-Routen einen API-Schlüssel; ein Special, das auf einen Menüeintrag verweist, liefert dessen Namen, Beschreibung, Bild und regulären Preis aufgelöst mit.
  • .NET SDK — IBookDinePlayClient hat GetSpecialsAsync(venueSlug, date, days) dazubekommen. Handgeschriebene Implementierungen müssen es ergänzen, und jeder Konsument muss gegen das neue BookDinePlay.Shared neu kompilieren (Mocks sind nicht betroffen).
  • JavaScript SDK — client.specials({ date, days }) und renderSpecialsWidget({ container, venueSlug, apiBaseUrl, publishableKey, view, locale, theme }): die Specials von heute oder dieser Woche mit einer Markierung „Jetzt“ in der Zeitzone der Location; ohne Specials wird nichts angezeigt.
  • WordPress — [bookdineplay_specials venue="…" view="week" language="de"] rendert die Specials von heute oder dieser Woche, mit denselben Attributen api, theme und key wie die anderen Shortcodes.
  • API — GET /api/venues/{venueSlug} hat showSpecialsInBookingFlow dazubekommen (Standard true): ob die Location die Specials des Tages in der Buchung zeigen möchte. Was GET /api/venues/{venueSlug}/specials antwortet, ändert sich dadurch nicht (Location-Profil).
  • JavaScript SDK — renderBookingWidget zeigt die Specials des gewählten Datums: eine Zeile „An diesem Tag“ unter den Schritten für Datum und Uhrzeit und die Liste des Tages in der Bestätigung, wenn showSpecialsInBookingFlow der Location an ist (oder fehlt). Sie wird nach der Wahl eines Datums separat geladen und hält weder die Uhrzeiten noch die Buchung auf; schlägt die Anfrage fehl, wird nichts angezeigt. Das optionale language: 'de' übersetzt sie.
  • API — GET /api/venues?query=&city=&page=&pageSize= ist das Location-Verzeichnis der BookDinePlay-App: Locations, deren Betreiber zugestimmt hat und deren Konto aktiv ist, nach Name sortiert, höchstens 50 pro Seite. Es antwortet nur BookDinePlays eigenem App- und Plattform-Schlüssel; der Schlüssel einer Location bekommt 403 platform-key-required.
  • API — Ein drittes Schlüssel-Präfix, bdp_ak_, für BookDinePlays eigene Apps aus den Stores. Betreiber können ihn nicht anlegen, mit einem Origin-Header wird er abgelehnt (403 app-key-from-browser), und er ist pro Schlüssel begrenzt (429 rate-limited mit Retry-After); für veröffentlichbare und geheime Schlüssel ändert sich nichts (Authentifizierung).
  • .NET SDK — IBookDinePlayClient hat ListDirectoryAsync(query, city, page, pageSize) dazubekommen, und BookDinePlay.Shared hat VenueDirectoryEntry, VenueDirectoryPage, AppListingDto und das Enum-Mitglied ApiKeyType.App dazubekommen. ListDirectoryAsync hat eine Standardimplementierung, die NotSupportedException wirft, sodass handgeschriebene Implementierungen weiter kompilieren; jeder Nutzer muss neu kompilieren (Mocks sind nicht betroffen).
  • Operator API — GET/PUT /api/operator/venue/app-listing liest und setzt listed, das für jede Location aus ist, bis der Betreiber zustimmt. Ein PUT ohne booleschen Wert listed wird mit 400 abgelehnt. Nicht an den Tarif gebunden. POST /api/operator/venue/api-keys lehnt den Typ App mit 400 ab.
  • Operator API — /api/operator/venue/specials: Specials einer Location auflisten, lesen, anlegen, ändern, archivieren und in der Vorschau ansehen.
  • Operator API — GET/PUT /api/operator/venue/specials/settings liest und setzt showInBookingFlow, das für jede Location an ist, bis der Betreiber es abschaltet. Ein PUT ohne booleschen Wert showInBookingFlow wird mit 400 abgelehnt. Nicht an einen Tarif gebunden.
  • Operator API — GET /api/operator/venue/specials/print?date=&days=1|7&paper=A4|A5&lang=en|de liefert die Specials des Tages oder der Woche als druckbares PDF, oder 503, wenn keine PDF-Engine verfügbar ist; GET /api/operator/venue/specials/days?date=&days=1|7 liefert die Specials-Tage, aus denen die Konsole ihre Bilder zum Teilen zeichnet. Beide sind nicht an einen Tarif gebunden und bleiben verfügbar, solange das Abonnement der Location gesperrt ist.
  • Operator-API — Tarifkäufe in den eigenen Store-Apps von BookDinePlay: GET /api/operator/billing/store (Abrechnungsquelle des Kontos, ob ein In-App-Kauf möglich ist, und das Kauf-Token), POST /api/operator/billing/app-store/transactions und POST /api/operator/billing/google-play/purchases (beim Store geprüft und dann dem Konto des Aufrufers zugeordnet). Ein Konto wird über genau eine Quelle abgerechnet; Stripe-Checkout und -Portal antworten bei einem über einen Store abgerechneten Konto mit 409 store-billing.
  • Operator-API — DELETE /api/operator/account löscht das Konto der angemeldeten Person (die Anmeldung selbst und den Operator-Zugang; die Geschäftsdaten der Location bleiben erhalten). Solange ein Store-Abo noch verlängert wird und dies der letzte Operator der Location ist, wird mit 409 abgelehnt; ist der Identitätsanbieter nicht erreichbar, antwortet die Route mit 503 account-deletion-unavailable, ohne etwas zu löschen.
  • Onboarding — POST /api/onboarding nimmt optional billing an (Web, AppStore, GooglePlay; Standard Web): eine Store-Registrierung öffnet keinen Stripe-Checkout und wird durch den In-App-Kauf abgeschlossen. GET /api/onboarding/legal-documents liefert die heute geltenden AGB und den Auftragsverarbeitungsvertrag zur Anzeige. Registrierungen sind ratenbegrenzt.
  • .NET SDK — BookDinePlay.Shared hat die Verträge für die Store-Abrechnung (AppStoreTransactionRequest, GooglePlayPurchaseRequest, den Store-Abrechnungsstatus) und OnboardingRequest.Billing dazubekommen; jeder Konsument muss neu kompilieren.

v0.18.1 — 2026-10-12

  • Intern — An der öffentlichen API, den SDKs und dem WordPress-Plugin ändert sich in diesem Release nichts. BookDinePlay.Sdk und BookDinePlay.Shared werden jetzt per NuGet Trusted Publishing statt mit einem gespeicherten API-Schlüssel auf nuget.org veröffentlicht; die Pakete selbst entstehen genau wie bisher.

v0.18.0 — 2026-10-09

  • JavaScript SDK — window.BookDinePlay.renderVoucherShop({ container, venueSlug, apiBaseUrl, publishableKey, locale, theme }) verkauft die Gutscheine der Location: Es listet die Angebote, nimmt Käufer und optional die beschenkte Person, eine Grußbotschaft und ein Versanddatum auf und schickt den Käufer zu Stripe Checkout. Eine Location, die Gutscheine nicht online verkaufen kann, zeigt statt eines Formulars einen klaren Hinweis „nicht verfügbar“; eine Guthabenprüfung für einen vorhandenen Gutschein steht in beiden Fällen darunter (Gutscheine).
  • JavaScript SDK — Neue Client-Methoden: client.voucherOffers(), client.checkoutVoucher(input, { idempotencyKey }), client.voucher(code) (ohne Schlüssel: Es wird kein Schlüssel gesendet, und eine Zeichenkette, die kein Gutscheincode ist, ergibt null ohne Anfrage) und client.payDeposit({ reservationReference, amount, currency, voucherCode }). BookDinePlay.helpers.parseVoucherCode und formatVoucherCode lesen einen Code so wie die API, Prüfzeichen eingeschlossen.
  • JavaScript SDK — Der Checkout von renderEventsWidget für eine kostenpflichtige Veranstaltung hat ein Feld „Have a voucher?“ (voucherCode in der Anfrage). Die Bestätigung von renderBookingWidget hat einen Schritt „Pay deposit“ für eine Buchung, deren Anzahlung noch offen ist – per Karte oder mit einem Gutschein; er erscheint nur, wenn die Location onlineDepositsAvailable meldet. Das optionale language: 'de' übersetzt diesen Schritt.
  • API — GET /api/venues/{venueSlug} liefert neu onlineDepositsAvailable: true nur, wenn die Location Zahlungen und Anzahlungen annimmt, ihr Tarif Online-Zahlungen und Anzahlungen enthält und ihr Auszahlungskonto verkaufen kann (Location-Profil).
  • API — Geschenkgutscheine. GET /api/venues/{venueSlug}/vouchers/offers listet, was eine Location online verkauft (in jedem Tarif; onlineSaleAvailable und unavailableReason sagen, ob ein Checkout starten kann), und POST /api/venues/{venueSlug}/vouchers/checkout kauft einen über Stripe Checkout auf dem eigenen Konto der Location (Premium, ein Auszahlungskonto, das verkaufen darf; beachtet Idempotency-Key wie der Ticket-Checkout). Der Gutscheincode entsteht erst, wenn die Zahlung eintrifft, und erreicht Käufer und Beschenkte per E-Mail (Gutscheine).
  • API — GET /api/vouchers/{code} ist eine neue Route ohne Schlüssel – die elfte: Der Code ist das Credential wie ein Ticketcode, behandeln Sie ihn so und loggen Sie ihn nie. Sie liefert Wert, Guthaben, Ablaufdatum und Status eines Gutscheins, hängt nicht am Tarif und antwortet für jeden Code, der kein Gutschein ist, mit demselben 404 voucher-not-found, damit Codes nicht durchprobiert werden können. Sie ist die erste Route der API mit Ratenbegrenzung: 30 Abfragen pro Minute und Client, danach 429 mit Retry-After (Ratenbegrenzung).
  • API — Ein Gutschein bezahlt online: POST /api/qr/{token}/payment (das jetzt einen optionalen Body nimmt), POST /api/venues/{venueSlug}/payment-intents und POST …/events/{eventSlug}/tickets/checkout nehmen ein optionales voucherCode. Erst zahlt der Gutschein, die Karte den Rest, mindestens 0,50 € – der Gutschein zahlt etwas weniger, wenn die Karte sonst unter dem Mindestbetrag von Stripe belastet würde. Ein Gutschein, der alles deckt, öffnet keine Zahlungsseite, und checkoutUrl ist dann die Erfolgsseite, sodass ein Client, der ihr folgt, weiter funktioniert. Eine Erstattung bucht den Gutscheinteil auf den Gutschein zurück; weder das Bezahlen mit einem Gutschein noch die Erstattung hängt am Tarif (Mit einem Gutschein bezahlen).
  • API — Neue nachgestellte Member: voucherAmount/paidInFull auf TabPaymentResponse, voucherAmount/cardAmount auf PaymentIntentResponse, voucherAmount/amountDue auf EventTicketCheckoutResponse, voucherCredits/amountDue auf TableSessionResponse (vom Personal am Tisch angewendete Gutscheine), depositVoucherAmount auf ReservationResponse und AdminReservationSummary sowie voucherAmount auf OperatorEventRegistrationDto. Alle sind null (bzw. false), wenn kein Gutschein beteiligt ist. Zwölf neue Problemtypen, voucher-not-found bis voucher-hold-live (Gutschein-Probleme).
  • API — POST /api/venues/{venueSlug}/payment-intents ist strenger, mit und ohne Gutschein: amount und currency müssen genau die Anzahlung sein, die die Buchung schuldet (sonst 400), und eine Buchung, die auf keine Anzahlung mehr wartet – storniert, von der Location bestätigt, vorbei oder schon bezahlt –, wird mit 409 und reason deposit-not-payable bzw. deposit-already-paid abgelehnt, statt eine zweite Zahlung zu öffnen (Payment Intents).
  • API — Neue Buchungsreferenzen haben sechs Zeichen (BDP-XXXXXX), Gruppenreferenzen ebenso, damit eine viel genutzte Plattform keine Kollision mehr riskiert; eine Anlage, die doch kollidiert, wird mit einer neuen Referenz wiederholt. Früher ausgegebene Referenzen bleiben mit vier Zeichen gültig – behandeln Sie eine Referenz als undurchsichtige Zeichenkette beliebiger dieser Längen. Referenzen von Event-Anmeldungen (BDP-E-XXXX) bleiben unverändert.
  • .NET SDK — IBookDinePlayClient hat vier Member bekommen: GetVoucherOffersAsync, StartVoucherCheckoutAsync, GetVoucherAsync (ohne Schlüssel: KeylessRouteHandler entfernt ihn; null für jede Ablehnung) und eine Überladung StartTabPaymentAsync(token, voucherCode, …). Eigene Implementierungen müssen sie ergänzen, und jeder Verbraucher muss gegen das neue BookDinePlay.Shared neu kompilieren (Mocks sind nicht betroffen). CreatePaymentIntentRequest.VoucherCode und EventTicketCheckoutRequest.VoucherCode nehmen einen Gutschein in den bestehenden Aufrufen, und AddBookDinePlayClient erzeugt jetzt auch für den Gutschein-Checkout einen Idempotency-Key (.NET SDK).
  • WordPress — [bookdineplay_vouchers venue="…" language="de"] rendert den Gutschein-Shop, mit denselben Attributen api, theme und key wie die anderen Shortcodes (WordPress-Plugin).
  • API — Menüartikel haben eine stabile id: items[].id in GET /api/venues/{venueSlug}/menus (und MenuItemDto.Id, ein nachgestelltes Member) wird vom Server vergeben und übersteht eine Umbenennung – verweisen Sie auf einen Artikel über id, nicht über name. Null nur bei einem Artikel, der gespeichert wurde, bevor es IDs gab (Speisekarten).
  • Operator API — PUT /api/operator/venue/menus trägt die Artikel-id ebenfalls. Ein Artikel ohne id behält die des gespeicherten Artikels derselben Karte mit demselben Namen, nur ein neuer Artikel bekommt eine neue; eine id, die die Location nicht hat, oder eine doppelt verwendete wird mit 400 (errors.menu) abgelehnt, und nichts wird gespeichert.
  • Operator API — Die Gutschein-Routen der Konsole unter /api/operator/venue/voucher-settings und /api/operator/venue/vouchers: Einstellungen, Ausgabe von Hand, die Liste, Entwerten, Änderung des Ablaufdatums, Abfrage und Einlösung am Tisch (der Code steht im Body, nie in der URL), Rückgängig, Erstattung eines Online-Verkaufs, erneuter Versand und Druck. Keine hängt am Tarif, außer dass der Online-Verkauf Premium braucht. DemoVenueResetResponse hat ein nachgestelltes vouchersDeleted bekommen.
  • Operator API — Version 1.1 der Nutzungsbedingungen und des Auftragsverarbeitungsvertrags deckt den Online-Verkauf von Gutscheinen ab. Sie ist in Kraft, deshalb meldet GET /api/operator/me legalAcceptanceRequired, bis der Betreiber sie in der Konsole akzeptiert.

v0.17.0 — 2026-10-06

  • Operator-API — PUT /api/operator/venue/reservations/{reference} lehnt das Bearbeiten einer Buchung im Status PaidButUnavailable jetzt mit 409 und dem Problemtyp reservation-not-editable ab. Eine solche Buchung belegt keinen Slot; wurde sie verschoben, bekam sie eine Uhrzeit, die nichts schützte, und ein anderer Gast konnte denselben Tisch buchen. Solche Buchungen werden stattdessen per Erstattung oder als „erledigt“ aufgelöst.
  • Intern — Darüber hinaus ändert sich an der öffentlichen API, den SDKs und dem WordPress-Plugin in diesem Release nichts. Es bringt eine für Tablets ausgelegte Reservierungskonsole (Zeitleiste als Standard, Tabs je Ressourcentyp, ein Reservierungsdialog, der per Tippen bearbeitet, eine Seite, die sich selbst aktualisiert), die Anzahlungen und „bezahlt, aber nicht verfügbar“ direkt auf der Zeitleiste zeigt.

v0.16.0 — 2026-09-30

  • API — POST /api/venues/{venueSlug}/events/{eventSlug}/registrations und POST /api/venues/{venueSlug}/events/{eventSlug}/tickets/checkout akzeptieren jetzt einen Idempotency-Key-Header, mit demselben Vertrag wie bereits bei Reservierungen: bis zu 200 Zeichen, begrenzt auf die Location und auf das Konto, das ihn vorlegt, 24 Stunden lang gültig. Eine Wiederholung mit demselben Key und demselben Body gibt die ursprüngliche Antwort zurück, statt eine zweite Anmeldung anzulegen. Ein Key, der für einen tatsächlich anderen Body wiederverwendet wird, wird mit idempotency-key-reused abgelehnt; ohne den Header ändert sich nichts (Events).
  • API — Beim Ticket-Checkout ist das mehr als ein Schutz vor Doppelbuchungen. Ein wiederholter Checkout legte bisher eine weitere vorläufige Anmeldung an, die 31 Minuten lang Plätze blockierte, während nur die checkoutUrl des letzten Versuchs beim Aufrufer ankam — ein vorübergehender Fehler oder ein zweiter Klick sperrte also Kontingent für einen Kauf, den niemand abgeschlossen hat, und ein fast ausverkauftes Event konnte sich als ausverkauft melden, obwohl die Plätze nicht verkauft waren. Eine Wiederholung mit demselben Key gibt jetzt dieselbe Checkout-Sitzung zurück, statt eine zweite zu eröffnen, und die Reservierung des ersten Versuchs bleibt unangetastet.
  • API — Ein neuer Problemtyp beim Ticket-Checkout: checkout-in-progress (409), für eine Wiederholung, die genau zwischen dem Blockieren der Plätze und der Rückgabe der Session-URL durch Stripe eintrifft. Kurz warten und mit demselben Key erneut versuchen — das ist eine Frage des Zeitpunkts, keine Ablehnung.
  • .NET-SDK — Ein mit AddBookDinePlayClient registrierter Client erzeugt jetzt auch für diese beiden Routen einen Idempotency-Key, wenn der Aufrufer keinen mitgibt — nicht mehr nur beim Anlegen einer Reservierung. Ein automatischer Wiederholungsversuch auf Transportebene erreicht die API damit unter einem einzigen Key. Ein selbst gesetzter Key wird nie ersetzt, und kein anderer Aufruf bekommt einen Header dazu.
  • Operator-API — Tickets innerhalb einer Anmeldung kommen jetzt in einer stabilen Reihenfolge zurück, und der CSV-Export unter GET /api/operator/venue/events/{id}/registrations.csv mit ihnen. Die Reihenfolge war bisher die, die die Datenbank zufällig lieferte — zwei Exporte unveränderter Daten konnten sich also voneinander unterscheiden.

v0.15.0 — 2026-09-30

  • Intern — An der öffentlichen API, den SDKs und dem WordPress-Plugin ändert sich in diesem Release nichts. Es ergänzt eine Route für Plattform-Administratoren, mit der ein bestehender Mandant einen eigenen Konsolen-Zugang erhält — eine von BookDinePlay eingerichtete Location muss damit nicht mehr per Identitätsübernahme durch Mitarbeitende betreut werden —, und es stellt die eigenen Persistenztests auf eine Datenbank um, die eindeutige Indizes tatsächlich erzwingt: genau die Lücke, durch die der Deployment-Fehler aus v0.13.0 unbemerkt durchging. BookDinePlay.Shared bekommt drei nur für Administratoren nutzbare Vertragstypen (CreateOperatorRequest, UpdateOperatorRequest, OperatorSummary); sie kommen rein additiv hinzu, sind nur mit Plattform-Administrator-Zugang erreichbar, und kein bestehender Typ, kein Mitglied und keine Antwortform hat sich geändert.

v0.14.0 — 2026-09-29

  • API — Ein Gast kann seine Tischrechnung per Karte bezahlen: POST /api/qr/{token}/payment friert die Rechnung 31 Minuten lang auf ihre aktuelle Summe ein und gibt eine Stripe-Checkout-Sitzung auf dem verbundenen Konto der Location zurück (TabPaymentResponse { checkoutUrl, amount, currency, expiresAt }); die Rechnung schließt sich selbst, sobald die Zahlung eingeht – POST …/close ist also nicht zusätzlich nötig. Ohne API-Schlüssel wie der Rest der QR-Gruppe – der gedruckte Token ist das Zugangsmerkmal –, setzt aber einen Tarif mit Online-Zahlungen, eingeschaltete Zahlungen und ein abgeschlossenes Stripe-Auszahlungs-Onboarding voraus. Ein erneuter Aufruf während einer laufenden Zahlung gibt dieselbe Checkout-Sitzung zurück statt eine zweite bezahlbare zu eröffnen; ein Idempotency-Key-Header ist deshalb nicht nötig (QR-Codes und Tisch-Sitzungen).
  • API — Während eine Rechnungszahlung läuft, wird POST /api/qr/{token}/orders mit 409 abgelehnt (die abzubuchende Summe steht schon fest), und GET /api/qr/{token}/session bekommt zwei neue, angehängte Felder: paymentPending (immer vorhanden, false für jede Rechnung ohne laufende Zahlung – bestehende Antworten bedeuten also unverändert dasselbe) und paymentUrl, dieselbe Checkout-URL, sodass ein zweites Gerät am Tisch die laufende Zahlung anzeigt statt eine weitere zu starten. Bricht der Gast den Checkout ab, läuft das Einfrieren von selbst aus.
  • .NET-SDK — IBookDinePlayClient hat ein neues Mitglied, StartTabPaymentAsync – handgeschriebene Implementierungen müssen es ergänzen (Mocks sind nicht betroffen), dieselbe 0.x-Praxis wie bei den zwei Mitgliedern in v0.12.0 und den fünf in v0.10.0. Es sendet wie die anderen QR-Aufrufe keinen API-Schlüssel.
  • API — Eine über Stripe gezahlte Anzahlung auf eine Reservierung kann jetzt erstattet werden, und eine Anzahlung, die für eine bereits vergebene Zeit eintrifft, geht nicht mehr verloren. Die Location entscheidet, was passiert, wenn ein Gast eine Buchung mit online gezahlter Anzahlung storniert – über das neue depositRefund bei GET/PUT /api/operator/venue/cancellation-policy (NoOnlineRefund – der Standard und das Verhalten jeder Location bis hierher –, RefundOnGuestCancel oder ForfeitOnGuestCancel). POST /api/reservations/{cancelToken}/cancel bekommt einen neuen Problemtyp: 502 reservation-refund-failed, für eine erstattende Location, deren Erstattung nicht durchging – es wird nichts storniert und ein erneuter Versuch lohnt sich, weshalb es ein 502 ist und nicht das regelbasierte 409 reservation-deposit-not-refundable (Reservierungen, Fehler).
  • API — reservation-deposit-not-refundable bedeutet jetzt die eigene Entscheidung der Location statt einer fehlenden Funktion: Eine Location, die depositRefund von NoOnlineRefund wegschaltet, erzeugt es gar nicht mehr, und eine Buchung, deren Anzahlung das Personal erlassen oder in Bar genommen hat, erzeugte es nie – dieser Fall war vorher von einer echten Online-Zahlung nicht zu unterscheiden und wurde mit ihr abgelehnt.
  • API — AdminReservationSummary hat ein neues, angehängtes depositRefundedAt (null, solange die Anzahlung nicht vollständig erstattet wurde), bei GET /api/operator/venue/reservations und GET /api/admin/reservations.
  • API — Ein Idempotency-Key bei POST /api/venues/{venueSlug}/reservations gilt jetzt zusätzlich zur Location auch nur für das Konto, das ihn gesendet hat. Jeder API-Key Ihrer Location kann einen Key wiedergeben, den ein anderer Ihrer Keys angelegt hat – eine Key-Rotation mitten im Vorgang, oder ein Anlegen über einen serverseitigen Secret-Key mit Wiederholung von anderswo, riskiert damit keine zweite Buchung mehr; und ein von Ihrem Konto verwendeter Key kann nicht mehr von den Keys eines anderen Kontos wiedergegeben oder blockiert werden, und auch nicht von den von BookDinePlay selbst gehosteten Buchungsseiten Ihrer Location, die jetzt einen eigenen Namensraum haben. Ihre eigenen Keys teilen untereinander weiterhin einen Namensraum – bewusst, denn sie gehören zu einem Konto. Für den üblichen Key als Zufalls-UUID pro Versuch ändert sich nichts, und ein vor diesem Release belegter Key funktioniert seine restlichen 24 Stunden weiter (Reservierungen).
  • Behoben — Eine Anzahlung, die direkt im Stripe-Dashboard der Location erstattet wurde, landet jetzt wieder an der Buchung. Ihr charge.refunded passte vorher auf überhaupt nichts, und der Status der Buchung änderte sich nie.

v0.13.1 — 2026-09-29

  • Fixed (intern) — An der öffentlichen API, den SDKs und dem WordPress-Plugin ändert sich in diesem Release nichts. Es behebt die Datenspeicher-Initialisierung beim Deployment, die v0.13.0 fehlerhaft hinterlassen hatte: Die Seed-Routine für unsere eigenen Demo-Locations prüfte anhand der Id, ob ein Event bereits existiert, während die Datenbank die Eindeutigkeit über Location- und Event-Slug gemeinsam erzwingt — ein unter einer anderen Id bereits vorhandener Slug wurde beim Einfügen daher abgewiesen. Keine Kunden-Location, keine Reservierung und keine Integration war betroffen: Die API beantwortete Anfragen durchgehend normal, und das Datenbankschema war bereits aktuell.

v0.13.0 — 2026-09-29

  • API — Eine buchbare Ressource kann jetzt platzweise statt exklusiv verkauft werden: seatSold unter Ressourcen (immer vorhanden, Standard false — jede bestehende Ressource verhält sich weiterhin wie bisher: eine Reservierung belegt die gesamte Ressource). Eine platzweise verkaufte Ressource lässt mehrere Reservierungen sich ein Zeitfenster teilen, bis zusammen capacity erreicht ist — eine Eventfläche mit 31 Plätzen geht bei einer Gruppe von vier Personen nicht mehr komplett vom Markt. Verfügbarkeit-Slots einer platzweise verkauften Ressource erhalten seatsRemaining („27 von 31 frei"); bei einer exklusiven Ressource fehlt es im JSON ganz, sodass bestehende Integrationen unverändert bleiben. POST/PUT /api/operator/venue/resources akzeptieren ein nullbares seatSold — wird es bei einem Update weggelassen, bleibt der aktuelle Modus der Ressource unverändert (beim Anlegen gilt weiterhin exklusiv als Standard).
  • API — POST /api/venues/{venueSlug}/reservations akzeptiert einen optionalen Idempotency-Key-Header (bis zu 200 Zeichen, gültig für die Location, 24 Stunden lang berücksichtigt): Eine Wiederholung mit demselben Key und demselben Request-Body gibt die Original-Reservierung zurück – dieselbe reference, derselbe cancelToken – statt eine zweite anzulegen, sodass ein Client, der eine verlorene Antwort nicht von einer verlorenen Anfrage unterscheiden konnte, nicht mehr raten muss. Ein Key, der für einen tatsächlich anderen Body wiederverwendet wird, wird abgelehnt mit dem neuen 409 idempotency-key-reused; wird der Header weggelassen, ändert sich nichts (Reservierungen, Fehler).
  • SDKs — Der Headless-Client sendet den neuen Header über reserve(input, { idempotencyKey }), das Buchungs-Widget erzeugt automatisch pro Versuch einen Key und verwendet ihn bei eigenen Wiederholungen nach einem fehlgeschlagenen Absenden erneut, und das .NET-SDK erhält eine zweite CreateReservationAsync-Überladung mit idempotencyKey vor dem Cancellation-Token — die bestehende dreiargumentige Signatur bleibt unverändert, sodass ein gegen ein älteres BookDinePlay.Shared kompilierter Aufrufer weiterhin bindet.
  • .NET-SDK — Ein mit AddBookDinePlayClient registrierter Client erzeugt für POST /api/venues/{venueSlug}/reservations jetzt selbst einen Idempotency-Key, wenn der Aufrufer keinen übergibt – pro Aufruf ein frischer Wert, angehängt vor jeder Wiederholung auf Transportebene, sodass eine automatische Wiederholung einer Anlage (der Resilience-Handler eines .NET-Aspire-Hosts wiederholt POST standardmäßig) die API unter einem einzigen Key erreicht statt als mehrere Buchungen. Zwei getrennte Aufrufe bekommen immer getrennte Keys, und ein selbst übergebener Key wird nie ersetzt. Kein anderer Aufruf erhält einen Header.
  • SDKs — Der Headless-Client erhält client.registration(cancelToken) für GET /api/registrations/{cancelToken} – die dritte der drei schlüssellosen Gast-Routen, die fehlte, während die anderen zwei bereits verfügbar waren, sodass eine Storno-Seite ein eigenes fetch schreiben musste, um dem Gast vor dem Button zu zeigen, was er gebucht hat. Gebaut wie client.ticket(): kein Schlüssel-Header, kein Location-Segment, dieselbe BookDinePlayError-Zuordnung. Im .NET-SDK deckte GetRegistrationAsync das schon ab (Events).
  • API — Jedes Zeichenkettenfeld eines öffentlichen Requests wird jetzt an der Schnittstelle gegen genau die Grenze geprüft, mit der auch seine Spalte konfiguriert ist: Ein zu langer Wert wird mit 400 und Nennung des Feldes abgelehnt, statt die Datenbank zu erreichen und dort als 500 zu scheitern. Am deutlichsten bei Reservierungen: email war aus dem Widget zuvor unbegrenzt und ist nun auf 256 Zeichen begrenzt — so, wie es die Event-Contracts schon hielten. Keine Grenze wurde gesenkt; geschlossen wird die Lücke, in der Schnittstelle und Spalte voneinander abwichen, weshalb auch ein generierter Ressourcen-Code nicht mehr überlaufen kann.
  • API — Eine Wiederholung mit Idempotency-Key, deren extras in anderer Reihenfolge ankommen, wird jetzt als derselbe Request erkannt. Der Fingerprint, der über „derselbe Request“ entscheidet, wird aus dem geparsten Body berechnet, hashte die Extras-Liste aber in Ankunftsreihenfolge — ein Client, der seinen Warenkorb aus einem Dictionary neu aufbaut, konnte deshalb mit 409 idempotency-key-reused für eine Buchung abgelehnt werden, die nie zustande kam. Extras werden vor dem Hashen sortiert; unterschiedliche Mengen kennzeichnen weiterhin einen tatsächlich anderen Request. Vor diesem Release geschriebene Claims werden weiterhin nach dem vorherigen Verfahren verglichen, sodass eine Wiederholung über das Upgrade hinweg in beiden Fällen unberührt bleibt.
  • Operator-API — Das Bearbeiten einer Reservierung ohne Verschieben — Personenzahl, Notizen, Telefonnummer — meldet nicht mehr 409 „dieser Zeitraum wurde inzwischen gebucht“, wenn der Schreibvorgang aus einem unabhängigen Grund fehlschlägt. Die Konfliktprüfung fragte den eigenen Slot der Buchung erneut ab und traf dabei genau die Zeile, die bearbeitet wurde; diese wird nun ausgeschlossen, sodass ein unabhängiger Fehler als er selbst erscheint und nicht als Slot-Kollision, die es nie gab.
  • Fixed — seatSold und Idempotency-Key sind beide additiv: Eine Location, die seatSold nie setzt, erhält byteweise identische availability-Antworten, und eine Reservierungsanlage ohne Idempotency-Key-Header verhält sich genau wie bisher. Die eine Verhaltensänderung, die man kennen sollte, ist die Längenprüfung oben — ein Request mit einem zu langen Wert, der zuvor die Datenbank erreichte, erhält jetzt ein 400.

v0.12.1 — 2026-09-23

  • API — Zwei neue Problemtypen auf POST /api/operator/venue/payouts/onboarding-link, die vorher ein 500 war, wenn Stripe ablehnte: 409 connect-not-enabled (die Plattform hat Connect nicht freigeschaltet — die Location kann nichts tun) und 409 connect-rejected (Stripe lehnt die Daten dieser Location ab — Land, Umsatzsteuer-ID oder die Form der Website-URL — und der Betreiber kann sie korrigieren und es erneut versuchen). Zwei Typen, weil der nächste Schritt des Betreibers unterschiedlich ist (Fehler).
  • Auszahlungen — Die Stripe-Application-Fee ist ausschliesslich die BookDinePlay-Provision. Vorher enthielt sie zusätzlich eine Schätzung der Stripe-Gebühr, die bei Direct Charges das Konto der Location ohnehin selbst zahlt — die Location wurde also doppelt belastet und der effektive Satz lag über den ausgewiesenen 5 %. Bei einem Ticket zu EUR 20,00 betraegt die Gebuehr jetzt EUR 1,00 statt EUR 1,55. Keine API-Struktur hat sich geaendert; Locations erhalten schlicht das, was die Preisseite verspricht.
  • Behoben — Ein fehlgeschlagenes Auszahlungs-Onboarding sperrt eine Location nicht mehr 24 Stunden aus (Stripe speichert eine Antwort — auch einen Fehler — zu einem Idempotency-Key), und ein abgelaufener Stripe-Onboarding-Link erzeugt jetzt einen neuen, statt den Betreiber ohne Ausweg auf die Abrechnungsseite zurueckzuschicken.

v0.12.0 — 2026-09-23

  • API – Bezahlte Events verkaufen echte Tickets: POST /api/venues/{venueSlug}/events/{eventSlug}/tickets/checkout öffnet eine Stripe-Checkout-Session auf dem eigenen verbundenen Konto der Location (EventTicketCheckoutRequest – dieselben Felder wie bei einer kostenlosen Anmeldung, plus optionale successUrl/cancelUrl; 201 EventTicketCheckoutResponse { reference, checkoutUrl, expiresAt, cancelToken }); GET /api/registrations/{cancelToken} reiht sich in die bestehenden Ausnahmen für schlüssellose Routen ein (Ticketcode und Storno-Token) – die eigene Anmelde-/Ticket- und Zahlungsstatus-Seite eines Gasts (Warum drei Routen keinen Schlüssel brauchen). Vier neue Problemtypen: 409 ticket-sales-not-enabled (die eine gastseitige Ablehnung, die „Tarif ohne Ticketverkauf“, „Auszahlungskonto nicht verbunden“ und „Event nicht Paid“ zusammenfasst), 409 payouts-not-connected (nur auf betreiberseitigen Routen), 409 refund-not-available (eine eigene Stornierung eines Gasts für eine bezahlte Anmeldung, die die Rückerstattungsrichtlinie nicht mehr erlaubt) und 403 tenant-suspended (hier dokumentiert; ausgeliefert mit der Onboarding-Änderung in v0.11.0) (Events).
  • JavaScript-SDK – client.checkoutEventTickets(eventSlug, input) startet die Checkout-Session; das Formular eines bezahlten Events im Events-Widget zeigt jetzt eine Live-Summe (Intl.NumberFormat) und einen „Bezahlen & anmelden“-Button, der zu checkoutUrl weiterleitet, statt in einer Bestätigung im Widget zu enden – der frühere statische Zustand „Tickets bald verfügbar“ ist weg, denn eine Premium-Location mit verbundenem Auszahlungskonto kann jetzt tatsächlich verkaufen. Neue Optionen successUrl/cancelUrl bei createClient/renderEventsWidget. ticket-sales-not-enabled und payouts-not-connected zeigen absichtlich denselben Satz (Events).
  • WordPress – [bookdineplay_events]/[bookdineplay_event] bekommen die Attribute success_url/cancel_url, direkt an das Widget durchgereicht (WordPress-Plugin).
  • .NET-SDK – IBookDinePlayClient hat zwei Member dazubekommen: CheckoutEventTicketsAsync und GetRegistrationAsync – von Hand implementierte Klassen müssen sie ergänzen (Mocks sind nicht betroffen), derselbe 0.x-Präzedenzfall wie bei den fünf Membern aus v0.10.0. BookDinePlayApiException hat die Konstanten TenantSuspendedType, PayoutsNotConnectedType und RefundNotAvailableType dazubekommen (.NET-SDK).

v0.11.0 — 2026-09-23

  • API – Die Selbstregistrierung (POST /api/onboarding) verlangt jetzt Firmen- und Abrechnungsdaten in derselben Anfrage: company (Firmenname, Adresse, USt-IdNr., Rechnungs-E-Mail, Ansprechpartner), ein Abrechnungsintervall interval (Monthly oder Yearly) und die rechtlichen acceptances des Mandanten; die Antwort liefert eine Stripe-Checkout-checkoutUrl, die eine 14-tägige Testphase mit vorab hinterlegter Karte startet.
  • API – Zwei neue Problemtypen: 403 tenant-suspended auf jeder /api/venues/**-Route, sobald das Abonnement einer Location ausgelaufen oder gesperrt ist oder eine Selbstregistrierung nie bezahlt wurde; und 409 billing-managed-by-stripe bei PUT /api/admin/tenants/{id}, wenn Tarif und Status des Mandanten von einem aktiven Stripe-Abonnement bestimmt werden.

v0.10.0 — 2026-09-16

  • API – Events der Location. Eine Location auf Pro oder höher veröffentlicht Events in der Konsole; die öffentliche API listet sie (GET /api/venues/{venueSlug}/events, ?upcoming=false für vergangene), liest eines mit seinem Anmeldefenster (…/events/{eventSlug} – registration.open und reason) und meldet Gäste zu kostenlosen Events an (POST …/events/{eventSlug}/registrations) – mit einem namentlichen Ticket pro Teilnehmer, einer BDP-E-XXXX-Referenz, Ticketcodes mit QR-Seiten und einem Storno-Link; der Gast bekommt dasselbe per E-Mail (Events).
  • API – Zwei schlüssellose Routen: GET /api/tickets/{code} und POST /api/event-registrations/{cancelToken}/cancel brauchen keinen API-Schlüssel und sind nicht tarifgebunden – Code und Token sind die gemailten Zugangsmerkmale des Gastes, wie das gedruckte QR-Token (Warum zwei Routen keinen Schlüssel brauchen).
  • API – Zwei neue Problemtypen bei der Anmeldung: 409 registration-closed mit reason (closed, sold-out, past, not-open) und remaining: 0 bei Ausverkauf sowie 409 ticket-sales-not-enabled für ein bezahltes Event, bis der Online-Ticketverkauf kommt. POST …/events/{eventSlug}/tickets/checkout existiert als Stub, der Letzteres antwortet; eine Anmeldung bei einer Location, deren Tarif keine Events enthält, ist 403 feature-not-in-plan. Eine verletzte Anmelderegel ist eine 400, deren errors-Wörterbuch die Meldungen unter registration trägt.
  • JavaScript-SDK – BookDinePlay.renderEventsWidget({ container, venueSlug, apiBaseUrl, publishableKey, layout, language, eventSlug, onNavigate, theme }) rendert die Events einer Location als Karten, das Detail eines Events, das Anmeldeformular und eine Bestätigung mit Ticketcodes – in derselben isolierten Wurzel und mit denselben neun --bdp-*-Properties wie das Buchungswidget; eventSlug rendert ein Event für eine eigene Seite, onNavigate(slug, summary) leitet auf die Seiten der Website. Der Headless-Client bekommt events(), event(), registerForEvent(), ticket() und cancelEventRegistration() (die letzten beiden senden keinen Schlüssel); BookDinePlayError hebt type, reason, remaining und errors vom Problem und ergänzt messages(). createClient verlangt venueSlug nicht mehr vorab – nur die an die Location gebundenen Aufrufe werfen ohne ihn, eine Ticket- oder Storno-Seite braucht also nur apiBaseUrl und publishableKey (Events).
  • WordPress – [bookdineplay_events venue="…" layout="grid|list" language="en|de"] rendert das Events-Widget und [bookdineplay_event venue="…" event="…"] ein Event direkt; beide nehmen api, theme und key wie der Buchungs-Shortcode (WordPress-Plugin).
  • .NET-SDK – IBookDinePlayClient hat fünf Member dazubekommen: GetEventsAsync, GetEventAsync, RegisterForEventAsync, GetTicketAsync und CancelEventRegistrationAsync – handgeschriebene Implementierungen müssen sie ergänzen (Mocks sind nicht betroffen). BookDinePlayApiException trägt Type, Reason, Remaining, Errors, Messages und Extensions plus die Konstanten RegistrationClosedType und TicketSalesNotEnabledType. AddBookDinePlayClient registriert einen KeylessRouteHandler, der X-BookDinePlay-Key und einen Bearer bdp_…-Authorization-Header aus den beiden schlüssellosen Routen entfernt (.NET-SDK).

v0.9.0 — 2026-09-16

  • API – Tarifstufen werden durchgesetzt. Ein Starter-Mandant kann keine Ressource vom Spieltyp (Billardtisch, Dartscheibe, Shuffleboard, Bowlingbahn, Eventfläche, Sonstiges) anlegen oder bearbeiten, den Raumplan öffnen oder Mitarbeiter verwalten – die Operator-API antwortet mit 403 feature-not-in-plan und einer requiredPlan-Extension, die den freischaltenden Tarif nennt (Fehler).
  • API – Bei einer Starter-Location lassen resources und resourceTypes deren Ressourcen vom Spieltyp vollständig weg; Gäste sehen sie nie (Venue).
  • API – Die Verfügbarkeit für einen resourceType, den der Tarif der Location nicht anbietet, liefert 200 mit leeren slots und notice: "resource-type-not-offered"; eine Reservierung für diesen Typ wird trotzdem mit demselben feature-not-in-plan-Problem wie bei der Operator-API abgelehnt.

v0.8.0 — 2026-09-16

  • JavaScript-SDK – BookDinePlay.createClient({ apiBaseUrl, publishableKey, venueSlug }) – ein Headless-Client für Seiten mit eigenem Formular: venue(), floorPlan(), availability(), reserve(), RFC-7807-Fehler als BookDinePlayError; das Widget läuft jetzt darauf, und renderBookingWidget lehnt einen Schlüssel ab, der kein veröffentlichbarer bdp_pk_…-Schlüssel ist (gibt null zurück, mit Konsolenmeldung), statt zu rendern und mit 403 zu scheitern (Eigene Formulare).
  • API – Öffnungszeiten dürfen nach Mitternacht schließen (opens 18:00, closes 02:00); Verfügbarkeit und Reservierungen folgen der Spanne in den nächsten Tag, Pausen bleiben auf denselben Tag beschränkt (Öffnungszeiten).

v0.7.0 — 2026-09-16

  • Docs – Tutorials: Buchungsablauf (was das Widget von Anfang bis Ende tut) und Azure Static Web Apps (von null zu einer Live-Seite der Location); dieses Changelog. Keine Änderung an der öffentlichen API, den SDKs oder dem WordPress-Plugin.

v0.6.0 — 2026-09-16

  • API – Jeder Aufruf von /api/venues/** verlangt einen API-Schlüssel: im Browser einen veröffentlichbaren bdp_pk_…-Schlüssel von einer erlaubten Origin, auf einem Server einen geheimen bdp_sk_…-Schlüssel. Ein fehlender, falscher oder falsch eingesetzter Schlüssel wird mit einem von sechs RFC-7807-Problemtypen abgelehnt (Authentifizierung, Fehler).
  • API – Buchungsdauern sind je Ressourcentyp konfigurierbar: Das Profil der Location listet bookingDurations, Verfügbarkeit und Reservierungen akzeptieren durationMinutes, und die Verfügbarkeit meldet die Dauer, die ihre Slots darstellen.
  • API – Verfügbarkeit und Reservierungsanlage lösen die eigene Buchungsdauer einer Ressource auf dieselbe Weise auf, sodass ein angebotener Slot nicht mehr wegen seiner Dauer abgelehnt werden kann; eine Gruppe, die mehrere Ressourcen braucht, wird von beiden Aufrufen gleich aufgeteilt.
  • API – phone beim Anlegen einer Reservierung ist optional: 3–40 Zeichen, wenn angegeben; ein leerer Wert gilt als nicht angegeben.
  • JavaScript-SDK – Auf dem CDN veröffentlicht: sdk/<version>/bookdineplay.js (unveränderlich, mit SRI-Hash in sri.txt daneben) und sdk/v0/bookdineplay.js (Fünf-Minuten-Alias). renderBookingWidget verlangt jetzt publishableKey und nimmt theme (auto, light, dark); neun --bdp-*-Custom-Properties gestalten das Widget von der Host-Seite aus; window.BookDinePlay.version nennt das geladene Release (JavaScript-SDK, Theming).
  • JavaScript-SDK – durationMinutes wird nur gesendet, wenn der Gast eine Dauer gewählt hat; sonst gilt der Standard der Location.
  • .NET-SDK – BookDinePlay.Sdk und BookDinePlay.Shared sind auf nuget.org, versioniert im Gleichschritt mit dem Release (.NET-SDK).
  • WordPress – Das Plugin erscheint als bookdineplay-<version>.zip am Release, lädt das SDK vom v0-Alias, speichert den veröffentlichbaren Schlüssel unter Einstellungen → BookDinePlay, und der Shortcode erhält ein key-Attribut (WordPress-Plugin).
  • Docs – Diese Website.

v0.5.0 — 2026-09-09

  • Keine Änderung an der öffentlichen API, den SDKs oder dem WordPress-Plugin. Nur Betreiberkonsole.

v0.4.1 — 2026-09-02

  • Keine Änderung an der öffentlichen API, den SDKs oder dem WordPress-Plugin. Nur Betreiberkonsole.

v0.4.0 — 2026-07-23

  • API – Meldungen und Problem-Antworten sind lokalisiert: Senden Sie Accept-Language: de für deutsche title und detail; Englisch ist der Standard (Fehler). Die Bestätigungs-E-Mail des Gastes verwendet die Sprache der Anfrage, die die Reservierung angelegt hat.
  • API – Buchbare Extras: Das Profil der Location listet extras; eine Reservierung akzeptiert extras: [{ "extraId": "…", "quantity": 1 }] und liefert bepreiste extras-Zeilen und extrasTotal.
  • API – Preis pro Spiel: Ein Slot kann pricingModel: "PerGame" tragen; eine Reservierung akzeptiert und liefert games (1–10).
  • API – Gruppenbuchungen: Eine Gruppe, die für eine Ressource zu groß ist, bekommt einen Gruppen-Slot (groupResourceCount, groupResourceNames); die Reservierung liefert groupReference und groupResources.
  • API – Neuer Ressourcentyp BowlingLane; Essensbestellung ist eine Einstellung je Ressource (allowsFoodOrdering auf Ressourcen).
  • JavaScript-SDK, WordPress – BowlingLane in der Liste der Ressourcentypen und in den Standard-types des Plugins; das Widget erfasst Spiele pro Person und Extras, wo die Location sie anbietet.

Releases vor v0.4.0 liegen vor den öffentlichen Integrationswegen und sind nicht aufgeführt.