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() and client.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

  1. 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.
  2. 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.
  3. Checkout — the shop starts a checkout and sends the buyer to Stripe. It sends one Idempotency-Key per 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.
  4. 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), or null. 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.