Booking engine integration
One script tag, four data attributes, one callback. The widget handles the quote, the hold, and the UI chip. Your server confirms (or releases) when the booking succeeds (or fails).
</body> on the booking page.<!-- 1. Drop this on the booking engine page (just before </body>) -->
<script
src="https://www.guestmaker.ai/loyalty-widget/credit-widget.v1.js"
data-tenant="${YOUR_GUESTMAKER_API_KEY}"
data-email="${memberEmail}"
data-amount="${grandTotal}"
data-currency="EUR"
data-channel="direct"
data-on-apply="onLoyaltyCreditApply"
async
></script>
<div id="gm-loyalty-credit"></div>
<!-- 2. Listen for the apply callback -->
<script>
window.onLoyaltyCreditApply = function (payload) {
// payload = {
// hold_id: string,
// points: number,
// amount: number, // in your booking currency
// currency: 'EUR',
// expires_at: string, // ISO 8601
// external_reference_id: string,
// }
applyDiscountToCart(payload.amount);
pendingHoldId = payload.hold_id;
};
</script>data-tenantYour GuestMaker API key with the `guests:write` scope. Create one at /developers and paste the full `gm_*` key here.
data-emailVerified member email address from the booking session. The widget calls /credit/quote with this to look up the spendable balance.
data-amountGrand total of the booking in major units (e.g. 320 for €320). Used by the widget to compute the maximum applicable cash credit.
data-currencyISO 4217 currency code (default `EUR`).
data-channelBooking channel: `direct` / `website` / `phone` / `walk_in`. Must be in the tenant's allowed_channels (guardrail #632 blocks OTA/TTOO regardless).
data-on-applyName of the global window function that will receive the apply payload (default `onLoyaltyCreditApply`).
window.onLoyaltyCreditApply before the script loads.interface LoyaltyCreditApplyPayload {
hold_id: string;
points: number;
amount_value: number; // major units, matches data-currency
currency: string; // ISO 4217
expires_at: string; // ISO 8601 timestamp
external_reference_id: string;
}
declare global {
interface Window {
onLoyaltyCreditApply?: (payload: LoyaltyCreditApplyPayload) => void;
}
}// Server-side: when the booking engine commits the booking,
// confirm the held points so the discount is locked in.
const res = await fetch('https://guestmaker.ai/api/v1/loyalty/credit/confirm', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.GUESTMAKER_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
external_reference_id: cartId, // same value used in the widget
reservation_id: createdReservationId,
}),
});
if (!res.ok) {
// Release the hold so spendable returns to the member.
await fetch('https://guestmaker.ai/api/v1/loyalty/credit/release', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.GUESTMAKER_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ external_reference_id: cartId }),
});
throw new Error('Cash credit confirm failed — booking rolled back');
}Pass the same external_reference_id on confirm as you did on hold. Idempotency is anchored on that value (guardrail #630), so a retry of either call is safe.