Anmelden um den Slug und den veröffentlichbaren Schlüssel Ihrer Location in jedem Beispiel zu sehen.
Ihr Konto hat noch keine Location, die Beispiele behalten daher ihre Platzhalter. Abmelden
Angemeldet als · . Legen Sie in der Konsole einen veröffentlichbaren Schlüssel an und laden Sie die Seite neu, um ihn hier zu sehen. Abmelden
Angemeldet als · . Die Beispiele zeigen den veröffentlichbaren Schlüssel Ihrer Location. Abmelden
Entwicklerdokumentation
Events
Die Events einer Location mit renderEventsWidget rendern – Karten, Detail, Anmeldung, Tickets – oder eigene Eventseiten auf den Event-Aufrufen des Clients.
Dieselbe SDK-Datei, die das Buchungswidget rendert, rendert auch die Events einer Location: veröffentlichte Events als Karten, das Detail eines Events, ein Anmeldeformular – kostenlose Anmeldung bei einem kostenlosen Event, Stripe Checkout bei einem bezahlten – und eine Bestätigung mit den Ticketcodes. Zwei Wege hinein, beide auf derselben Events-API:
renderEventsWidget– ein Aufruf, ein vollständiger Ablauf in einem Element Ihrer Wahl, stilisoliert wie das Buchungswidget. Für eine Location-Website ohne eigene Eventseiten.- Der Headless-Client –
client.events(),client.event(),client.registerForEvent(),client.checkoutEventTickets(),client.ticket(),client.registration()undclient.cancelEventRegistration()neben den Buchungsaufrufen aus Eigene Formulare. Für eine Website, die schon einen Eventbereich hat und nur die Daten und den Anmelde-/Checkout-Aufruf braucht.
Beides lässt sich kombinieren: Eine Website kann die Liste mit eigenen Karten rendern und ein gewähltes Event dem Widget übergeben – oder umgekehrt.
Events sind eine Pro-Funktion, der Online-Ticketverkauf für kostenpflichtige Events eine Premium-Funktion. Eine Location, deren Tarif keine Events enthält, antwortet mit einer leeren Liste – das Widget zeigt dann seinen Leerzustand statt eines Fehlers, mit dem ein Gast nichts anfangen kann. Tarife und Sichtbarkeit nennt die genaue Antwort pro Route, die Preisseite die Kosten je Tarif.
Installation
Laden Sie das SDK wie für das Buchungswidget – über den Alias der Hauptversionslinie oder eine exakte Version mit Integritäts-Hash (CDN) – und fügen Sie ein Element für die Events ein:
<div id="bookdineplay-events"></div>
<script src="https://cdn.bookdineplay.com/sdk/v0/bookdineplay.js"></script>Events-Widget rendern
<script>
window.BookDinePlay.renderEventsWidget({
container: '#bookdineplay-events',
venueSlug: 'your-venue',
apiBaseUrl: 'https://api.bookdineplay.com',
publishableKey: 'bdp_pk_your_publishable_key',
layout: 'grid',
language: 'de',
theme: 'auto'
});
</script>Der Aufruf gibt die Widget-Instanz zurück oder null, wenn er das Rendern verweigert hat (Wenn es nicht rendert). Eine Seite kann ein Events-Widget und ein Buchungswidget nebeneinander tragen; sie teilen sich Script und Schlüssel.
Optionen
| Option | Pflicht | Bedeutung |
|---|---|---|
container |
ja | Ein CSS-Selektor oder ein DOM-Element. Das Widget rendert darin. |
venueSlug |
ja | Der Slug Ihrer Location. |
apiBaseUrl |
ja | https://api.bookdineplay.com. Nur bei einer privaten Installation anders. |
publishableKey |
ja | Der bdp_pk_…-Schlüssel der Location. Nie ein geheimer Schlüssel. |
layout |
nein | grid (Standard) – Karten in Spalten – oder list, eine Karte pro Zeile. |
eventSlug |
nein | Rendert direkt das Detail dieses einen Events, ohne Liste und ohne Zurück-Button – für eine Seite, die die Seite des Events ist (Ein Event pro Seite). |
language |
nein | en (Standard) oder de – die eigenen Texte des Widgets (Buttons, Zustände, Datumsangaben) und das Accept-Language, das es sendet, damit Problemmeldungen in derselben Sprache zurückkommen. Jedes de-*-Tag zählt als de. Eventtitel und -beschreibungen kommen immer in der Sprache, in der die Location sie geschrieben hat. |
onNavigate |
nein | function (slug, summary). Wenn gesetzt, ruft die Wahl einer Karte diese Funktion auf, statt das Detail im Widget zu öffnen – die Website leitet auf ihre eigene Eventseite (Ein Event pro Seite). |
successUrl |
nein | Wohin das Stripe Checkout eines bezahlten Events den Gast nach der Zahlung zurückschickt (Bezahlte Events). Ohne Angabe wird die eingebaute Anmeldeseite genutzt. |
cancelUrl |
nein | … wenn er das Checkout abbricht. Gleicher Standard. |
theme |
nein | auto (folgt der Systemeinstellung des Besuchers, Standard), light oder dark. |
Was der Gast sieht
- Die Liste – jedes anstehende veröffentlichte Event als Karte: Bild (mit Alt-Text), ein Datums-Badge (oder „Laufend“ für ein Event ohne Datum), ein Chip – Kostenlos oder Tickets –, Titel, Teaser und eine Zeile mit Wochentag, Datum, Uhrzeiten und dem Zeitlabel der Location. „Ausverkauft“ erscheint erst im Detail, sobald das Anmeldefenster bekannt ist. Eine Location ohne anstehende Events zeigt einen leeren Zustand; eine Location, deren Tarif keine Events enthält, sieht genauso aus, weil die API eine leere Liste liefert.
- Das Detail – Bild, Titel, Teaser, die Beschreibung, wie die Location sie geschrieben hat, und die Fakten: wann, wo, Preis, verbleibende Tickets. Darunter entweder ein Anmelden-Button oder der Grund, warum es keinen gibt (Tabelle der Zustände unten). Ein bezahltes Event mit offenem Fenster bekommt denselben Button wie ein kostenloses – nur das Formular dahinter unterscheidet sich.
- Das Formular – Name und E-Mail (Pflicht), Telefon, die Ticketanzahl (1 bis zum kleineren Wert von
maxPerRegistrationundremaining), ein Teilnehmername pro Ticket (der erste spiegelt den Käufer, bis er bearbeitet wird), Anmerkungen. Absenden bleibt deaktiviert, bis das Formular gültig ist; ein unsichtbares Honeypot-Feld verwirft Bot-Eingaben stillschweigend. Bei einem kostenlosen Event sendet es eine Anmeldung ab und endet in der Bestätigung im Widget unten; bei einem bezahlten Event (siehe Bezahlte Events) zeigt es eine Live-Summe, sendet stattdessen ein Stripe-Checkout ab und schickt den Gast zu Stripe, statt eine Bestätigung zu zeigen. Ein abgelehnter Versuch zeigt den Grund der API über dem Formular: den Satz des Fensters beiregistration-closed, einen allgemeinen „gerade nicht verfügbar“-Satz beiticket-sales-not-enabled, die Validierungsmeldungen bei einer400. - Die Bestätigung – nur bei kostenlosen Events: die Referenz, jedes Ticket mit Code, Teilnehmer und einem Link Ticket anzeigen zur Ticketseite sowie ein Link Anmeldung stornieren. Dasselbe kommt per E-Mail, sobald die Tickets ausgestellt sind (Was der Gast erhält).
Die Widget-Wurzel meldet Ansichtswechsel an Screenreader (aria-live="polite"); Ladefehler sind role="alert" mit Wiederholen, Ladezustände role="status".
Der Zustand des Details ergibt sich aus status und registration des Events – BookDinePlay.helpers.registrationState(detail) liefert denselben Wert für eine Seite mit eigenem Markup:
| Zustand | Wann | Text (Deutsch) |
|---|---|---|
open |
Ein kostenloses Event mit offenem Fenster | Der Anmelden-Button |
paid |
Ein bezahltes Event mit offenem Fenster | Der Anmelden-Button – wie open; das Formular ist stattdessen ein Checkout |
none |
Anmeldemodus None |
„Keine Anmeldung nötig – kommen Sie einfach vorbei.“ |
closed |
registration.reason ist closed |
„Die Anmeldung für diese Veranstaltung ist geschlossen.“ |
sold-out |
registration.reason ist sold-out |
„Diese Veranstaltung ist ausverkauft.“ |
past |
registration.reason ist past |
„Diese Veranstaltung hat bereits stattgefunden.“ |
not-open |
registration.reason ist not-open |
„Die Anmeldung für diese Veranstaltung ist nicht geöffnet.“ |
cancelled |
status ist Cancelled |
„Diese Veranstaltung wurde abgesagt.“ |
Bezahlte Events
Eine Premium-Location mit verbundenem Stripe-Auszahlungskonto verkauft Tickets für ihre Paid-Events direkt über das Widget. Das Detail und die Zustandstabelle oben behandeln ein bezahltes Event genau wie ein kostenloses – derselbe Anmelden-Button, nur durch das Anmeldefenster begrenzt (open/closed/sold-out/past) –, denn ob die Location gerade tatsächlich verkaufen kann, ist eine eigene, engere Frage, die der Checkout-Aufruf beantwortet, nicht der Detail-Read.
Das Formular eines bezahlten Events zeigt zusätzlich eine Live-Summe (Preis × Anzahl, formatiert für die language des Widgets mit Intl.NumberFormat) über dem Absenden-Button, und der Button selbst heißt „Bezahlen & anmelden“ statt „Anmeldung bestätigen“. Das Absenden ruft client.checkoutEventTickets(eventSlug, input) statt registerForEvent auf und leitet bei Erfolg mit window.location.assign(response.checkoutUrl) zu Stripe Checkout weiter – es gibt keine Bestätigung im Widget zu zeigen, weil Tickets erst ausgestellt werden, sobald Stripe die Zahlung bestätigt, was Sekunden nach dem Verlassen dieser Seite sein kann.
Unter der Summe öffnet „Gutschein einlösen?“ ein Feld für einen Geschenkgutscheincode, mit „Gutschein entfernen“, um ihn zurückzunehmen. Der Code wird zuerst im Formular geprüft, sodass ein Tippfehler ohne Anfrage auffällt; dann wird er als voucherCode gesendet, und der Gutschein bezahlt, was er deckt, die Karte den Rest. Ein Gutschein, der alles deckt, braucht keine Zahlungsseite: checkoutUrl ist dann die Erfolgsseite, und das Widget folgt ihr genauso. Ein abgelehnter Gutschein zeigt über dem Formular seinen eigenen Satz (Gutscheine).
Zwei Widget-/Client-Optionen holen den Gast danach zurück:
<script>
window.BookDinePlay.renderEventsWidget({
container: '#bookdineplay-events',
venueSlug: 'your-venue',
apiBaseUrl: 'https://api.bookdineplay.com',
publishableKey: 'bdp_pk_your_publishable_key',
successUrl: 'https://ihre-website.example/danke',
cancelUrl: 'https://ihre-website.example/tickets'
});
</script>Beide sind optional – ohne Angabe landet der Gast auf der eingebauten Anmeldeseite von BookDinePlay (https://app.bookdineplay.com/r/{cancelToken}), die den Zahlungsstatus abfragt und die Tickets zeigt, sobald sie ausgestellt sind. Wenn gesetzt, muss jede eine https-URL auf einer Origin sein, die der aufrufende publishableKey nutzen darf (siehe Authentifizierung); eine URL, die diese Prüfung nicht besteht, antwortet mit 400 (errors.successUrl/errors.cancelUrl), und das Widget zeigt eine allgemeine Fehlermeldung – die Origin vor der Veröffentlichung prüfen.
Enthält der Tarif der Location keinen Ticketverkauf, oder hat sie noch kein Auszahlungskonto verbunden, wird checkoutEventTickets mit 409 ticket-sales-not-enabled oder 409 payouts-not-connected abgelehnt – das Widget zeigt für beide denselben Satz (Fehler), denn ein Gast hat nichts davon, zu wissen, welcher der beiden es war.
Ein Event pro Seite
Eine Website, die jedem Event eine eigene URL gibt – /events/darts-league-night –, nutzt die beiden Optionen zusammen. Auf der Eventübersicht wird geroutet, statt das Detail im Widget zu öffnen:
<script>
window.BookDinePlay.renderEventsWidget({
container: '#bookdineplay-events',
venueSlug: 'your-venue',
apiBaseUrl: 'https://api.bookdineplay.com',
publishableKey: 'bdp_pk_your_publishable_key',
onNavigate: function (slug) { window.location.href = '/events/' + slug + '/'; }
});
</script>Auf der Eventseite wird dieses Event direkt gerendert. Das Widget zeigt Detail, Formular und Bestätigung, aber keine Liste und keinen Zurück-Button – das übernimmt die Navigation der Seite:
<script>
window.BookDinePlay.renderEventsWidget({
container: '#bookdineplay-event',
venueSlug: 'your-venue',
apiBaseUrl: 'https://api.bookdineplay.com',
publishableKey: 'bdp_pk_your_publishable_key',
eventSlug: 'darts-league-night'
});
</script>onNavigate erhält als zweites Argument die summary der Karte – den Listeneintrag aus GET /api/venues/{venueSlug}/events – für einen Router, der mehr als den Slug will. Ein eventSlug, den die API nicht kennt, rendert „Diese Veranstaltung ist nicht mehr verfügbar“ mit Wiederholen.
Theming
Das Events-Widget rendert in derselben isolierten Wurzel wie das Buchungswidget und liest dieselben neun --bdp-*-Custom-Properties von der Seite – --bdp-accent für Chips, Badges und den Anmelden-Button, --bdp-surface für die Karten, --bdp-radius für deren Ecken. Einmal gesetzt, folgen beide Widgets; Theming listet jede Property mit ihrem Standard. Eine Events-spezifische Property gibt es nicht.
Wenn es nicht rendert
renderEventsWidget gibt null zurück und schreibt eine Zeile in die Browserkonsole, statt eine Ausnahme in Ihre Seite zu werfen – dieselben drei Ablehnungen wie renderBookingWidget (Wenn es nicht rendert): Der Container wurde nicht gefunden, venueSlug oder apiBaseUrl fehlt, oder publishableKey fehlt bzw. ist kein bdp_pk_…-Schlüssel.
Der Headless-Client
BookDinePlay.createClient (Eigene Formulare) hat sieben Event-Aufrufe neben den vier Buchungsaufrufen. Jeder gibt ein Promise auf den geparsten JSON-Body zurück, nimmt { signal } als optionales letztes Argument und lehnt mit einem BookDinePlayError ab (Fehler):
| Methode | Endpunkt | Liefert |
|---|---|---|
client.events({ upcoming }) |
GET /api/venues/{venueSlug}/events |
Das events-Array der Zusammenfassungen (nicht die Hülle); upcoming ist standardmäßig true und wird nur als ?upcoming=false gesendet |
client.event(eventSlug) |
GET /api/venues/{venueSlug}/events/{eventSlug} |
Das Detail mit seinem registration-Fenster; lehnt bei unbekanntem oder unveröffentlichtem Event mit status: 404 ab |
client.registerForEvent(eventSlug, input) |
POST …/events/{eventSlug}/registrations |
Die Anmeldung – reference, tickets[], cancelUrl |
client.checkoutEventTickets(eventSlug, input) |
POST …/events/{eventSlug}/tickets/checkout |
Die Checkout-Session – reference, checkoutUrl, expiresAt, cancelToken (Bezahlte Events) |
client.ticket(code) |
GET /api/tickets/{code} |
Das Ticket mit seinem qrSvg; es wird kein Schlüssel gesendet |
client.registration(cancelToken) |
GET /api/registrations/{cancelToken} |
Die eigene Anmeldung des Gastes – status, payment, tickets[], refundPolicy, canCancel, cancelNote; es wird kein Schlüssel gesendet |
client.cancelEventRegistration(cancelToken) |
POST /api/event-registrations/{cancelToken}/cancel |
Die stornierte Anmeldung; es wird kein Schlüssel gesendet |
registerForEvent(eventSlug, input) und checkoutEventTickets(eventSlug, input) nehmen dieselben Felder – buyerName, email, phone, notes, quantity und attendees – so geformt wie das Widget: Zeichenketten getrimmt, phone und notes null, wenn leer, quantity als Zahl, attendees ein getrimmter Name pro Eintrag. checkoutEventTickets nimmt außerdem einen optionalen voucherCode, der nur gesendet wird, wenn er gesetzt ist, und sendet zusätzlich successUrl/cancelUrl – entweder pro Aufruf als input.successUrl/input.cancelUrl, oder die eigenen Standards des Clients aus createClient({ successUrl, cancelUrl }) unten (ein Wert pro Aufruf gewinnt); wird keiner gesetzt, wird auch keiner gesendet, und die API greift auf ihre eingebaute Anmeldeseite zurück. Ein fehlender eventSlug, code oder cancelToken wirft synchron einen TypeError.
Die drei schlüssellosen Aufrufe brauchen keinen venueSlug: Eine Seite, die nur ein Ticket, eine Anmeldung oder einen Storno-Link zeigt, kann ihren Client allein mit apiBaseUrl und publishableKey erstellen; nur die an die Location gebundenen Aufrufe werfen ohne Slug.
const client = window.BookDinePlay.createClient({
apiBaseUrl: 'https://api.bookdineplay.com',
publishableKey: 'bdp_pk_your_publishable_key',
venueSlug: 'your-venue',
language: 'de',
successUrl: 'https://ihre-website.example/danke', // nur bei bezahlten Events – optional
cancelUrl: 'https://ihre-website.example/tickets' // optional
});Eigenes Markup bauen
Der ganze Ablauf im eigenen HTML der Seite – die Reihenfolge, die das Widget durchläuft, mit Ihren Vorlagen. Zuerst die Liste: eine Karte pro Event, verlinkt auf die Seite des Events. BookDinePlay.helpers enthält die reinen Formatierungsfunktionen des Widgets (formatEventDate, dateBadge, eventChip, registrationState, maxQuantity), damit Datumsangaben und Zustände genauso lesen wie im Widget:
const helpers = window.BookDinePlay.helpers;
const list = document.querySelector('#events');
async function renderList() {
const events = await client.events();
if (!events.length) {
list.textContent = 'Keine anstehenden Veranstaltungen.';
return;
}
for (const event of events) {
const card = document.createElement('a');
card.href = '/events/' + event.slug + '/';
const title = document.createElement('strong');
title.textContent = event.title;
const when = document.createElement('span');
when.textContent = helpers.formatEventDate('de', event.date, event.start, event.end, event.timeLabel);
const chip = document.createElement('span');
chip.textContent = event.registrationMode === 'Free' ? 'Kostenlos' : event.registrationMode === 'Paid' ? 'Tickets' : '';
const excerpt = document.createElement('p');
excerpt.textContent = event.excerpt || '';
card.append(title, when, chip, excerpt);
list.append(card);
}
}
renderList();Dann die Eventseite: das Detail lesen, die Beschreibung zeigen (descriptionHtml ist serverseitig bereinigt) und aus registrationState ableiten, ob das Formular erscheint:
const slug = location.pathname.split('/').filter(Boolean).pop();
const page = document.querySelector('#event');
const form = document.querySelector('#register');
async function renderEvent() {
let detail;
try {
detail = await client.event(slug);
} catch (err) {
page.textContent = err.status === 404 ? 'Diese Veranstaltung ist nicht mehr verfügbar.' : err.message;
throw err;
}
page.querySelector('h1').textContent = detail.title;
page.querySelector('.description').innerHTML = detail.descriptionHtml;
page.querySelector('.when').textContent =
helpers.formatEventDate('de', detail.date, detail.start, detail.end, detail.timeLabel);
page.querySelector('.where').textContent = detail.locationLabel;
const state = helpers.registrationState(detail); // 'open' | 'paid' | 'none' | 'closed' | 'sold-out' | 'past' | 'not-open' | 'cancelled'
if (state === 'open' || state === 'paid') { // 'paid' ist auch anmeldbar – siehe Bezahlte Events
form.hidden = false;
form.elements.quantity.max = helpers.maxQuantity(detail.registration);
} else {
page.querySelector('.state').textContent = stateText(state); // Ihre eigene Formulierung je Zustand
}
}
renderEvent();Und die Anmeldung: ein Teilnehmerfeld pro Ticket, das erste fällt auf den Käufer zurück, dann der Aufruf – checkoutEventTickets bei einem bezahlten Event, sonst registerForEvent. Ein kostenloses Event endet in der Bestätigung im Widget; ein bezahltes leitet zu Stripe weiter und hat keine zu zeigen:
form.addEventListener('submit', async (event) => {
event.preventDefault();
const data = new FormData(form);
const quantity = Number(data.get('quantity'));
const attendees = [];
for (let i = 0; i < quantity; i++) attendees.push(data.get('attendee' + i) || '');
const input = {
buyerName: data.get('name'), email: data.get('email'), phone: data.get('phone'),
notes: data.get('notes'), quantity, attendees
};
try {
if (helpers.registrationState(detail) === 'paid') {
const checkout = await client.checkoutEventTickets(slug, input);
window.location.assign(checkout.checkoutUrl); // Gast verlässt die Seite zu Stripe — nichts weiter zu rendern
return;
}
const registration = await client.registerForEvent(slug, input);
showConfirmation(registration.reference, registration.tickets, registration.cancelUrl);
} catch (err) {
if (err.name === 'AbortError') return;
if (err.reason) { // 409 registration-closed: 'closed' | 'sold-out' | 'past' | 'not-open'
showError(stateText(err.reason));
if (err.reason === 'sold-out') form.hidden = true;
return;
}
if (err.status === 400) { // Validierung – Feldname (oder 'registration') → Meldungen
showError(err.messages().join(' '));
return;
}
// ticket-sales-not-enabled, payouts-not-connected, 403, Netzwerk – der Satz der API (beide
// Ticketverkauf-Probleme lesen sich gleich, siehe Fehler unten)
showError(err.message);
}
});showConfirmation, showError und stateText sind Ihre. Jedes Ticket hat eine ticketUrl zum Verlinken und einen code zum Anzeigen; cancelUrl ist die Storno-Seite des Gastes. Der Gast bekommt dasselbe per E-Mail, sobald die Tickets ausgestellt sind, eine Website kann also eine kurze Bestätigung zeigen und den Rest dem Posteingang überlassen.
Ticket-, Anmelde- und Storno-Seiten auf Ihrer Website
Alle drei Seiten gibt es auf app.bookdineplay.com, und die E-Mails verlinken dorthin (Was der Gast erhält). Eine Website, die sie unter eigener Domain haben will, rendert sie aus den drei schlüssellosen Aufrufen – ein Client ohne venueSlug reicht hier:
const client = window.BookDinePlay.createClient({
apiBaseUrl: 'https://api.bookdineplay.com',
publishableKey: 'bdp_pk_your_publishable_key'
});
const code = location.pathname.split('/').pop();
const ticket = await client.ticket(code); // sendet keinen Schlüssel; lehnt bei unbekanntem Code mit 404 ab
document.querySelector('#qr').innerHTML = ticket.qrSvg;
document.querySelector('#attendee').textContent = ticket.attendeeName;
document.querySelector('#status').textContent = ticket.status; // 'Valid' | 'CheckedIn' | 'Cancelled'Die Storno-Seite hat dieselbe Form und zeigt dem Gast zuerst, was er gebucht hat: client.registration(cancelToken) liefert die gesamte Zusammenfassung, canCancel sagt, ob der Button überhaupt auf die Seite gehört, und cancelNote ist der fertige Satz für das Warum-nicht. Blind zu stornieren, ohne Bestätigungsseite, ist nie nötig:
const token = new URLSearchParams(location.search).get('token');
const registration = await client.registration(token); // sendet keinen Schlüssel; lehnt bei unbekanntem Token mit 404 ab
document.querySelector('#event').textContent = registration.eventTitle;
document.querySelector('#status').textContent = registration.status; // 'Confirmed' | 'PendingPayment' | 'Cancelled'
document.querySelector('#tickets').textContent = registration.quantity;
const button = document.querySelector('#cancel');
button.hidden = !registration.canCancel;
document.querySelector('#why-not').textContent = registration.cancelNote || '';
button.addEventListener('click', async () => {
const result = await client.cancelEventRegistration(token); // sendet keinen Schlüssel; 409, sobald ein Ticket eingecheckt wurde
document.querySelector('#status').textContent = result.status; // 'Cancelled'
});Bei einem bezahlten Event wartet der Gast auf genau dieser Seite, während die Zahlung abgeschlossen wird – woher die Seite das Token bekommt, hängt aber von der gewählten Rücksprung-URL ab (Bezahlte Events):
successUrlweglassen – der Gast landet auf der eingebauten Seite/r/{cancelToken}von BookDinePlay, die das Token schon in ihrem Pfad trägt und nichts von Ihnen braucht.- Eigene
successUrlangeben – die Plattform gibt sie unverändert weiter und hängt kein Token an. Sichern SiecancelTokenaus der Antwort voncheckoutEventTicketsselbst, bevor Sie weiterleiten:sessionStorage, Ihr eigenes Backend oder ein Parameter, den Sie selbst an IhresuccessUrlhängen. Einesession_idist kein Ersatz – keine öffentliche Route löst sie zu einer Anmeldung auf.
In beiden Fällen fragen Sie client.registration(cancelToken) alle paar Sekunden ab, bis payment den Wert 'Pending' verlässt. Behandeln Sie dieses Token als die Berechtigung, die es ist: Damit lässt sich die Buchung lesen und stornieren. Wenn Sie es in einer URL mitführen, landet es im Browserverlauf, in Referer-Headern und in jedem Zugriffsprotokoll auf dem Weg – und in Ihre eigenen Logs gehört es nicht.
ticketUrl und cancelUrl in der Anmeldeantwort zeigen weiterhin auf die BookDinePlay-Seiten; um Gäste auf Ihre zu schicken, bauen Sie die Links aus ticket.code und dem letzten Segment von cancelUrl.
Fehler
Jeder Client-Aufruf lehnt mit einem BookDinePlayError ab, wenn die Anfrage nicht erfolgreich war; ein von Ihnen ausgelöster AbortError geht unverändert durch. Zusätzlich zu den Feldern aus Eigene Formulare – name, status, problem, message – werden die Event-Probleme auf den Fehler gehoben, damit Sie die RFC-7807-Form nie kennen müssen:
| Feld | Wert |
|---|---|
type |
Die type-URL des Problems – endet auf registration-closed, ticket-sales-not-enabled, payouts-not-connected, refund-not-available, feature-not-in-plan oder einen Schlüsselgrund; die allgemeine RFC-9110-URL bei einer 400/404/einfachen 409; null, wenn die Antwort keinen Problem-Body hatte |
reason |
Bei registration-closed: 'closed', 'sold-out', 'past' oder 'not-open'; sonst null |
remaining |
0 bei sold-out; sonst null |
errors |
Bei einer 400: das Validierungs-Wörterbuch, Feldname (oder registration bei einer verletzten Regel) → Array von Meldungen; sonst null |
messages() |
Jede Meldung aus errors als ein flaches Array – [], wenn es keine gibt |
try {
await client.checkoutEventTickets(slug, input); // oder registerForEvent bei einem kostenlosen Event
} catch (err) {
if (err.type && (err.type.endsWith('/ticket-sales-not-enabled') || err.type.endsWith('/payouts-not-connected'))) {
showError('Der Online-Ticketverkauf für diese Veranstaltung ist derzeit nicht verfügbar.');
} else if (err.reason === 'sold-out') showError('Ausverkauft');
else if (err.reason) showError('Die Anmeldung ist nicht geöffnet');
else if (err.status === 400) showError(err.messages().join(' '));
else showError(err.message);
}ticket-sales-not-enabled und payouts-not-connected zeigen absichtlich denselben Satz – ein Gast kann auf keinen von beiden reagieren, und sie zu unterscheiden würde den Tarif- oder Abrechnungsstatus der Location an jeden verraten, der die API erreicht (Fehler). Der eigene registrationFailureText()-Helfer des Events-Widgets macht genau diese Zuordnung.
client.cancelEventRegistration(cancelToken) hat einen eigenen Problemtyp: Eine bezahlte Anmeldung, deren Event-Rückerstattungsrichtlinie keine Online-Rückerstattung mehr erlaubt, antwortet mit 409 refund-not-available, dessen message bereits der richtige, lokalisierte Satz „bitte die Location kontaktieren“ ist – nichts zuzuordnen, einfach anzeigen:
try {
await client.cancelEventRegistration(token);
} catch (err) {
if (err.type && err.type.endsWith('/refund-not-available')) showError(err.message);
else showError(err.message);
}Die Meldungen in errors kommen in der language des Clients. Eine 403 bei der Anmeldung oder dem Checkout ist feature-not-in-plan – der Tarif der Location enthält keine Events – oder ein Schlüsselproblem; ein status von 0 bedeutet, dass gar keine Antwort ankam.
Nächste Schritte
- Events-API – jedes Feld von Liste, Detail, Anmeldung, Checkout und Ticket.
- Fehler –
registration-closed,ticket-sales-not-enabled,payouts-not-connectedundrefund-not-availableim Detail. - Eigene Formulare – die Buchungsaufrufe desselben Clients.
- WordPress-Plugin – dasselbe Widget als
[bookdineplay_events]und[bookdineplay_event].