Sign in to see your venue's slug and publishable key in every sample.
Your account has no venue yet, so the samples keep their placeholders. Sign out
Signed in as · . Create a publishable key in the console and reload to see it here. Sign out
Signed in as · . The samples show your venue's publishable key. Sign out
Developer docs
Vouchers
Sell gift vouchers with renderVoucherShop, let guests check a balance, and take voucher codes in the booking and events widgets.
The same SDK file sells a venue's gift vouchers and takes them as payment:
renderVoucherShop— the venue's voucher offers, a form for the buyer and an optional recipient, and a redirect to Stripe Checkout, with a balance check underneath. Use it on a "Gift vouchers" page.- Voucher fields in the widgets — the booking widget's deposit step and the events widget's ticket checkout both take a voucher code.
- The headless client —
client.voucherOffers(),client.checkoutVoucher(),client.voucher()andclient.payDeposit()for pages of your own, plus helpers that check a code before it is sent.
Selling vouchers online needs the Premium plan, a connected payout account and the venue's online voucher sale switched on; checking a balance works on every plan, and a voucher adds no plan requirement to a payment — though the payments that take one have their own: a deposit needs deposits and tickets need ticket sales, both Premium (Vouchers).
Render the voucher shop
Load the SDK as for the booking widget (Install), add an element, and call renderVoucherShop:
<div id="bookdineplay-vouchers"></div>
<script src="https://cdn.bookdineplay.com/sdk/v0/bookdineplay.js"></script>
<script>
window.BookDinePlay.renderVoucherShop({
container: '#bookdineplay-vouchers',
venueSlug: 'your-venue',
apiBaseUrl: 'https://api.bookdineplay.com',
publishableKey: 'bdp_pk_your_publishable_key',
locale: 'de',
theme: 'auto'
});
</script>The call returns the shop instance, or null when it refused to render — the same refusals as the booking widget: no container, no venueSlug or apiBaseUrl, or a key that is not a bdp_pk_… publishable key (When it refuses to render). A page may hold the shop next to a booking or events widget; they share the script and the key.
Options
| Option | Required | Meaning |
|---|---|---|
container |
yes | A CSS selector or a DOM element. The shop renders inside it. |
venueSlug |
yes | Your venue's slug. |
apiBaseUrl |
yes | https://api.bookdineplay.com. Only different for a private deployment. |
publishableKey |
yes | The venue's bdp_pk_… key. Never a secret key. |
locale |
no | en (default) or de — the shop's own texts and the Accept-Language it sends, so offer titles, terms and problem messages come back in the same language. language is accepted as a synonym; any de-* tag counts as de. |
successUrl |
no | Where Stripe Checkout returns the buyer after paying. Must be https and on an origin the key allows. Omitted, the built-in thank-you page is used. |
cancelUrl |
no | …after they abandon Checkout. Same rule and default. |
theme |
no | auto (follow the visitor's system setting, default), light or dark. The shop uses the same --bdp-* properties as the other widgets (Theming). |
What the guest sees
- The offers — one card per product the venue sells online, with its title and price, or the range the buyer may choose from for a product with a custom amount. A single offer is selected already. Below them the venue's voucher terms and how long a voucher bought today is valid.
- The form — the amount for a custom-amount offer (within its minimum and maximum), the buyer's name and e-mail, and "It is a gift": the recipient's name and e-mail, a gift message (up to 500 characters, with a counter) and an optional delivery date. A delivery date needs the recipient's e-mail. An off-screen honeypot field drops bot submissions silently.
- Checkout — the shop starts a checkout and sends the buyer to Stripe. It sends one
Idempotency-Keyper set of details and reuses it when the buyer retries, so a second tap never opens a second payment. The voucher code is e-mailed once the payment arrives: to the buyer, and to the recipient — on the delivery date, when one was chosen. - Not available — when the venue does not sell vouchers online, the shop says so instead of showing offers. "This venue does not sell gift vouchers online" appears only for
not-offered; every other reason reads as the same "not right now", so a guest never learns which of the venue's settings is missing.
Underneath, in every state, "Already have a voucher? Check its balance" opens a field for a code. It shows the balance or the value, the expiry date and the status, and calls the keyless lookup without sending a key. A mistyped code is caught before any request.
The shop announces changes to screen readers (aria-live="polite") and renders in its own Shadow DOM, like the other widgets.
Voucher fields in the widgets
Ticket checkout. The events widget's form for a Paid event shows "Have a voucher?" under the total. The guest types or pastes a code; the voucher pays what it covers and the card pays the rest (Paid events).
Deposit step. When a booking comes back Pending with a deposit to pay and the venue profile reports onlineDepositsAvailable, the booking widget's confirmation shows "Pay deposit" and "Have a voucher?". The card part opens Stripe's hosted page; a voucher that covers the whole deposit confirms the booking in place, without a redirect. The booking widget takes a language option (en or de) for this step (Booking flow).
In both places the code is checked locally first, so a typo costs no request, and sent in canonical form in the request body. A refused voucher shows a sentence for its problem: not found, used up or expired, not usable for this payment (percentage and item vouchers are for the bill at the venue), in use right now, or a card remainder below €0.50.
The headless client
BookDinePlay.createClient (Custom forms) has four voucher calls. Each returns a promise of the parsed JSON body, takes { signal } in its last argument, and rejects with a BookDinePlayError (Errors):
| Method | Endpoint | Returns |
|---|---|---|
client.voucherOffers() |
GET /api/venues/{venueSlug}/vouchers/offers |
onlineSaleAvailable, unavailableReason, offers[], terms, validityDescription |
client.checkoutVoucher(input, { idempotencyKey }) |
POST /api/venues/{venueSlug}/vouchers/checkout |
reference, checkoutUrl, expiresAt — send the buyer to checkoutUrl |
client.voucher(code) |
GET /api/vouchers/{code} |
The voucher's value, balance, expiry and status; null when the code is unknown, and null without a request when it is mistyped. No key is sent |
client.payDeposit(input) |
POST /api/venues/{venueSlug}/payment-intents |
The payment intent — checkoutUrl, voucherAmount, cardAmount |
checkoutVoucher(input) takes productId, amount, buyerName, buyerEmail, recipientName, recipientEmail, giftMessage, deliverOn, language, successUrl and cancelUrl; the return URLs fall back to the client's own from createClient({ successUrl, cancelUrl }). payDeposit(input) takes reservationReference, amount, currency and an optional voucherCode. checkoutEventTickets(eventSlug, input) takes an optional voucherCode too (Events).
client.voucher() needs no venueSlug, so a balance page may create its client with apiBaseUrl and publishableKey alone. The lookup is limited to 30 a minute per visitor, which is another reason to check the code locally first.
const client = window.BookDinePlay.createClient({
apiBaseUrl: 'https://api.bookdineplay.com',
publishableKey: 'bdp_pk_your_publishable_key',
venueSlug: 'your-venue',
language: 'en'
});
const voucher = await client.voucher(input.value); // null: unknown or mistyped
if (voucher) {
showBalance(voucher.balance, voucher.currency, voucher.expiresOn, voucher.status);
}
const intent = await client.payDeposit({
reservationReference: reservation.reference,
amount: reservation.depositAmount,
currency: reservation.currency,
voucherCode: input.value // optional
});
if (intent.cardAmount === 0) {
showConfirmed(); // the voucher paid everything
} else {
window.location.assign(intent.checkoutUrl);
}Voucher code helpers
BookDinePlay.helpers has the code rules of the API, check symbol included, so a page can catch a typo before it sends anything:
parseVoucherCode(input)— a typed, pasted or scanned code, or a full guest-page URL, to its canonical form (20 characters, no dashes), ornull. It drops dashes and spaces, ignores case, and reads O as 0 and I or L as 1.formatVoucherCode(code)— the display form, five groups of four (K7QM-2XRP-9DTA-HV3N-8W4F), or''for anything that is not a code.isValidVoucherCanonical(canonical)— whether 20 characters are a valid code.
const canonical = window.BookDinePlay.helpers.parseVoucherCode(field.value);
if (!canonical) {
field.setCustomValidity('That does not look like a voucher code.');
} else {
field.value = window.BookDinePlay.helpers.formatVoucherCode(canonical);
}A code is a credential: whoever has it can spend the voucher. Do not log it, put it in a URL or send it to analytics (The code is a credential).
Next steps
- Vouchers API — every field, and the rules every voucher payment shares.
- WordPress plugin — the shop as a shortcode.
- Errors — the voucher problems behind each sentence.