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
Eigene Formulare
Eigenes Reservierungsformular behalten, Headless-Client nutzen: Location, Verfügbarkeit und Reservierung mit veröffentlichbarem Schlüssel, ohne Widget-UI.
Viele Websites von Locations haben bereits ein Reservierungsformular, das zu ihrem Design passt – eines für die Billardtische, eines für die Dartscheiben, eines für den Eventbereich. BookDinePlay.createClient stellt BookDinePlay hinter ein solches Formular, ohne etwas zu rendern: Es ist die HTTP-Schicht, auf der das Widget selbst läuft, offengelegt als vier Aufrufe, die Promises zurückgeben. Sie behalten Markup, Validierung und Bestätigungsseite; der Client übernimmt die Anfragen, den Schlüssel-Header, die Fehlerabbildung und das Abbrechen.
Installation
Laden Sie dieselbe SDK-Datei, die auch das Widget verwendet. Legen Sie eine exakte Version mit ihrem Integritäts-Hash fest, damit sich die Seite nie unter Ihnen ändert (Eine exakte Version festlegen):
<script
src="https://cdn.bookdineplay.com/sdk/0.8.0/bookdineplay.js"
integrity="sha384-jWkVHY/3ODRIOP4AZxnLryBhQa58r73TbB8ZBp+1YGqTk++mutYPqmFp53yUIt9p"
crossorigin="anonymous"></script>Das Script hängt window.BookDinePlay an, mit createClient neben renderBookingWidget. Nichts rendert, bevor Sie eines von beiden aufrufen – die Datei kann also auf jeder Seite eingebunden sein.
Client erstellen
Ein Client je Location. Er braucht die Basis-URL der API, den veröffentlichbaren Schlüssel der Location und den Slug der Location; ein optionales language wird als Accept-Language gesendet, damit Problemtexte in dieser Sprache zurückkommen:
const client = window.BookDinePlay.createClient({
apiBaseUrl: 'https://api.bookdineplay.com',
publishableKey: 'bdp_pk_your_publishable_key',
venueSlug: 'your-venue',
language: 'de'
});createClient wirft synchron einen TypeError, wenn apiBaseUrl oder venueSlug fehlt oder wenn publishableKey fehlt oder kein bdp_pk_…-Schlüssel ist – ein geheimer Schlüssel gehört nie in eine Seite, und der Client startet lieber gar nicht, als ihn zu senden.
Die vier Aufrufe
Jeder Aufruf gibt ein Promise auf den geparsten JSON-Body zurück und nimmt als optionales letztes Argument { signal } – ein AbortSignal –, damit Sie eine Anfrage genauso ablösen können wie das Widget. Die Antworten sind genau die der API; die verlinkten Referenzseiten beschreiben jedes Feld.
| Methode | Endpunkt | Ergebnis |
|---|---|---|
client.venue() |
GET /api/venues/{venueSlug} |
Das Profil der Location – name, timezone, resourceTypes, bookingDurations, extras |
client.floorPlan() |
GET /api/venues/{venueSlug}/floor-plan |
Der veröffentlichte Tischplan, oder null, wenn die Location keinen hat (404) |
client.availability({ date, partySize, resourceType, durationMinutes }) |
GET /api/venues/{venueSlug}/availability |
Die slots des Tages; durationMinutes fehlt in der Query, wenn es null ist |
client.reserve(input) |
POST /api/venues/{venueSlug}/reservations |
Die Reservierung – reference, status, Anzahlungsfelder |
reserve(input) nimmt dasselbe einfache Objekt, das auch das Widget baut: resourceType, resourceId, date, start, end, partySize, games, durationMinutes, extras, customerName, email, phone, notes, depositOptIn. Jedes Feld ist im Body immer vorhanden, in der Form, in der das Widget es sendet: partySize, games und durationMinutes werden Zahlen oder null; extras ist die Liste aus { extraId, quantity } oder null, wenn leer; customerName, email, phone und notes sind getrimmte Zeichenketten – "", wenn weggelassen; depositOptIn ist true oder false; alles andere, was Sie weglassen, ist null (Was das Widget sendet).
Ein minimaler Ablauf
Ein Billardformular mit Datum, Spielerzahl und Kontaktfeldern. Das Absenden fragt die Slots des Tages ab, jeder freie Slot wird ein Button, und die Wahl eines Slots bucht ihn – dieselbe Abfolge, die das Widget durchläuft, in Ihrem Markup. Das Formular wird bei der Slot-Wahl erneut gelesen, damit ein Gast, der nach der Suche seine E-Mail korrigiert, den korrigierten Wert sendet:
const form = document.querySelector('#billiard-form');
const slotList = document.querySelector('#slots');
let pending = null;
form.addEventListener('submit', async (event) => {
event.preventDefault();
const data = new FormData(form);
if (pending) pending.abort(); // ein zweiter Klick löst die erste Abfrage ab
pending = new AbortController();
try {
const availability = await client.availability({
date: data.get('date'),
partySize: data.get('players'),
resourceType: 'BilliardTable'
}, { signal: pending.signal });
slotList.replaceChildren();
for (const slot of availability.slots.filter((s) => s.available)) {
const button = document.createElement('button');
button.type = 'button';
button.textContent = `${slot.start}–${slot.end} · ${slot.resourceName}`;
button.addEventListener('click', () => reserve(slot));
slotList.append(button);
}
if (!slotList.childElementCount) slotList.textContent = 'An diesem Tag ist kein Tisch frei.';
} catch (err) {
if (err.name !== 'AbortError') showError(err.message);
}
});
async function reserve(slot) {
const data = new FormData(form); // jetzt lesen – der Gast kann seit der Suche Felder geändert haben
try {
const reservation = await client.reserve({
resourceType: 'BilliardTable',
resourceId: slot.resourceId,
date: data.get('date'),
start: slot.start,
end: slot.end,
partySize: data.get('players'),
customerName: data.get('name'),
email: data.get('email'),
phone: data.get('phone')
});
showConfirmation(reservation.reference, reservation.status);
} catch (err) {
showError(err.message);
}
}showError und showConfirmation gehören Ihnen – genau darum geht es. Die Reservierung kommt als Confirmed zurück, oder als Pending mit depositRequired: true, wenn die Location eine Anzahlung verlangt (Anzahlungen); die API hat dem Gast bis dahin eine E-Mail geschickt.
Fehler
Ein Aufruf wird mit einem BookDinePlayError abgewiesen, wann immer die Anfrage nicht erfolgreich war. Ein von Ihnen angeforderter Abbruch ist die Ausnahme: Der AbortError wird unverändert durchgereicht, sodass err.name === 'AbortError' weiterhin „ignorieren“ bedeutet.
| Feld | Wert |
|---|---|
name |
'BookDinePlayError' |
status |
Der HTTP-Status der Ablehnung – auch ein 401/403 aus der Schlüsselprüfung, die API macht diese für Ihre Origin lesbar; 0 nur, wenn gar keine Antwort ankam: offline, DNS, ein blockiertes Netz |
problem |
Der RFC-7807-Body, wenn die Antwort JSON war – type, title, detail und bei einem 400 errors –, sonst null |
message |
problem.detail, sonst problem.title, sonst Request failed (<status>) |
Die Fälle, die eine eigene Behandlung lohnen:
async function reserveOrExplain(input) {
try {
return await client.reserve(input);
} catch (err) {
if (err.name === 'AbortError') return; // von Ihnen abgelöst – nichts anzuzeigen
if (err.status === 400 && err.problem?.errors) { // Validierung: Feldname → Meldungen
for (const [field, messages] of Object.entries(err.problem.errors)) markInvalid(field, messages[0]);
return;
}
if (err.status === 409) { await reloadSlots(); return; } // der Slot wurde inzwischen vergeben
showError(err.message);
}
}Ein 401 oder 403 betrifft den Schlüssel oder die Origin, nicht die Eingabe des Gastes: problem.type endet auf einen der sechs Gründe unter Fehler. Eine abgelehnte Origin kommt als lesbarer 403 an, dessen problem.type auf origin-not-allowed endet – die API spiegelt Ihre Origin bei jeder Ablehnung in Access-Control-Allow-Origin, genau damit Ihr Script den Grund lesen kann (Origins). Ein status von 0 bedeutet also nie einen abgelehnten Schlüssel oder eine abgelehnte Origin, sondern dass keine Antwort kam: offline, DNS, ein blockiertes Netz.
Schlüssel und CORS
Der Client sendet den veröffentlichbaren Schlüssel bei jedem Aufruf als X-BookDinePlay-Key, und die API antwortet nur, wenn die Origin der Seite auf der Liste dieses Schlüssels steht – Schema, Host und Port, genau so, wie der Browser sie in Origin sendet (Origins). Tragen Sie Ihre Live-Domain ein, Ihre www-Variante, falls Sie eine ausliefern, und http://localhost:* für die Entwicklung.
Legen Sie nie einen geheimen Schlüssel in eine Seite. createClient lehnt einen bdp_sk_…-Schlüssel mit einem TypeError ab, und die API lehnt einen ab, der mit Origin-Header ankommt, ohnehin – ein geheimer Schlüssel, den ein Browser gesehen hat, ist ein veröffentlichtes Geheimnis; widerrufen Sie ihn. Sendet Ihre Website eine Content Security Policy, erlauben Sie connect-src https://api.bookdineplay.com (Content Security Policy).
Nächste Schritte
- Buchungsablauf – die vier Aufrufe in der Reihenfolge, in der das Widget sie macht, und jedes Feld, das es sendet.
- Theming – falls Sie doch das Widget einbetten: an Ihre Marke anpassen.
- CDN – exakte Versionen festlegen, SRI, Cache-Lebensdauern.