@v-office/website-headless 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,912 @@
1
+ import { BookingPaymentOption, BookingPaymentScheduleKind, GuestQuote, GuestQuoteResult, SubmittableBookingPaymentOption, createWebsiteSDK } from "@v-office/website-sdk";
2
+ //#region src/store/index.d.ts
3
+ /**
4
+ * The smallest external store that every UI framework can subscribe to:
5
+ * `getState` + `subscribe` is what React's useSyncExternalStore, Vue's
6
+ * watchers, Svelte's store contract and a plain `<script>` all understand.
7
+ * Flows in this package (checkout today, more later) build on it so the
8
+ * package never depends on a framework; the adapters live in ./react and
9
+ * friends.
10
+ */
11
+ type Listener<S> = (state: S) => void;
12
+ type Unsubscribe = () => void;
13
+ interface Store<S> {
14
+ getState(): S;
15
+ subscribe(listener: Listener<S>): Unsubscribe;
16
+ }
17
+ interface WritableStore<S> extends Store<S> {
18
+ /** Replaces the state and notifies every listener. Same reference = no-op. */
19
+ setState(next: S): void;
20
+ }
21
+ declare function createStore<S>(initial: S): WritableStore<S>;
22
+ //#endregion
23
+ //#region src/sdk.d.ts
24
+ type WebsiteSdk = ReturnType<typeof createWebsiteSDK>;
25
+ /**
26
+ * Render `error` and its whole cause chain as indented JSON.
27
+ *
28
+ * Walks the chain because a cause nests (a `CoreSDKError` wrapping a thrown
29
+ * transport object wrapping a response body). Skips `stack` — a backend
30
+ * refusal is not diagnosed from the frames that asked, and the raw error is
31
+ * one `console.error` away for the cases that are. Reads `name`/`message` by
32
+ * hand because `Error` declares both non-enumerable, so `Object.entries`
33
+ * alone would return an Error as `{}`.
34
+ *
35
+ * **`cause` is read by hand for exactly the same reason**, and it was missed
36
+ * the first time — so this file shipped without the one property it exists to
37
+ * print. Verified against the real class: `Object.keys(new CoreSDKError({…}))`
38
+ * is `["source", "operation", "_tag"]`, and `cause`'s own descriptor carries
39
+ * `enumerable: false`. `Object.entries` therefore never saw it, and every
40
+ * `CoreSDKError` logged as the five bland fields above with the sentence that
41
+ * names the fault dropped.
42
+ */
43
+ declare function describeSdkError(error: unknown): string;
44
+ /**
45
+ * Log a failed SDK call with the backend's reason, tagged by `scope`.
46
+ *
47
+ * `scope` names the SDK operation, not the component — `"quote.selectInsurance"`
48
+ * rather than `"the extras panel"`. What the console needs to answer is which
49
+ * call the backend refused; which button was on screen at the time is the part
50
+ * the reporter already knows.
51
+ *
52
+ * Logs the description ALONE. Callers that also have the raw error in hand may
53
+ * log it separately for a clickable stack; doubling it here would print every
54
+ * failure twice on the surfaces that already do.
55
+ */
56
+ declare function logSdkFailure(scope: string, error: unknown): void;
57
+ //#endregion
58
+ //#region src/quote.d.ts
59
+ /** One row of the price breakdown. `label`/`amount` are the SDK's own
60
+ * `position`/`appliedCharge`, verbatim; `nested` marks a sub-line so the UI
61
+ * can indent it under the line above.
62
+ *
63
+ * `discount` marks a row synthesised from an SDK price MODIFIER rather than
64
+ * from a charge line — see `adaptGuestQuote` for why those rows exist. Its
65
+ * `amount` is the modifier's own signed string ("-140,00 €"), so the row is a
66
+ * deduction from the running total, not another charge. */
67
+ interface QuoteLine {
68
+ label: string;
69
+ amount: string;
70
+ nested: boolean;
71
+ discount: boolean;
72
+ }
73
+ interface PriceQuote {
74
+ /** Section/line breakdown flattened in SDK order (sub-items follow their
75
+ * parent line, flagged `nested`). */
76
+ lines: QuoteLine[];
77
+ /** SDK `bookingCardInformation.total`, verbatim (already locale-formatted). */
78
+ total: string;
79
+ }
80
+ /** Bookable add-on from `quote.additionalServices`, verbatim fields. */
81
+ interface QuoteAddonService {
82
+ id: string;
83
+ label: string;
84
+ /** SDK charge string as-is (e.g. "25,00 €" — the PMS's own formatting). */
85
+ charge: string;
86
+ description?: string | undefined;
87
+ /** Max selectable per booking; absent = the SDK states no cap. */
88
+ maxPerBooking?: number | undefined;
89
+ /** Quantity the PMS PROPOSES for this service (`0` included). A proposal,
90
+ * NOT a selection — the distinction is the whole reason this field is
91
+ * named the way it is.
92
+ *
93
+ * THE SDK IS EXPLICIT (`instructions/quote.md`): "When the backend provides
94
+ * a line amount for an optional service, it is exposed as `proposedQuantity`
95
+ * (including `0`). By default this is informational only and is not added to
96
+ * the quote." A fresh quote carries these as selections ONLY on a project
97
+ * that sets `shouldAddProposedAdditionalServiceAmount: true`, which the SDK
98
+ * defaults to `false`.
99
+ *
100
+ * SO THIS IS NEVER A BASELINE. What a quote actually has selected is not
101
+ * readable from the public SDK surface at all — `GuestQuote` keeps its
102
+ * `selections` behind an unexported symbol, and there is no quote-side
103
+ * counterpart to `getBookingModificationSelectedQuantity`. Code that needs a
104
+ * known starting point therefore MAKES one with `clearQuoteServices` and
105
+ * steps up from zero. It does not read this number and hope.
106
+ *
107
+ * RENAMED FROM `preSelectedQuantity` IN booking-kit 1.21.0, because the old
108
+ * name stated the one thing the value does not mean, and `checkout-kit`
109
+ * believed it — see that kit's 1.21.0 entry. */
110
+ proposedQuantity: number;
111
+ /** False only when the PMS explicitly marks this service unbookable for the
112
+ * current multi-rate combination (SDK `available: false`). Absent → true.
113
+ * The UI disables adding it rather than letting the click silently no-op. */
114
+ available: boolean;
115
+ /** PMS's own reason the service is unavailable, verbatim; self-hides when
116
+ * the feed omits it. */
117
+ unavailableReason?: string | undefined;
118
+ }
119
+ interface QuotePolicyRule {
120
+ daysBeforeArrival: number;
121
+ /** SDK's formatted percentage string, verbatim. */
122
+ refundPercentage: string;
123
+ }
124
+ /** Selectable cancellation policy from `quote.cancellationPolicies`. */
125
+ interface QuoteCancellationPolicy {
126
+ id: string;
127
+ label: string;
128
+ selected: boolean;
129
+ description?: string | undefined;
130
+ markUpPercentage?: string | undefined;
131
+ processingFee?: string | undefined;
132
+ rules: QuotePolicyRule[];
133
+ /** False only when the PMS explicitly marks this policy unselectable for the
134
+ * current multi-rate combination (SDK `available: false`). Absent → true. */
135
+ available: boolean;
136
+ /** PMS's own reason the policy is unavailable, verbatim; self-hides when
137
+ * the feed omits it. */
138
+ unavailableReason?: string | undefined;
139
+ }
140
+ /** Voucher status echoed back on a re-priced quote (`quote.voucher`). On v10
141
+ * the SDK authoritatively reports whether the code applied a modifier; v9
142
+ * reports `"unknown"`. `message` is the PMS's own note, verbatim. */
143
+ interface QuoteVoucher {
144
+ code: string;
145
+ status: "applied" | "not_applied" | "unknown";
146
+ message?: string | undefined;
147
+ }
148
+ /** One selectable travel-insurance product from `quote.insurance.options`
149
+ * (kind "insurance"), verbatim fields. The backend loads these live from
150
+ * whichever insurer the hub setup configures, and only when that setup enables
151
+ * insurance at all — otherwise the SDK returns the "none" option alone and the
152
+ * adapter yields no `QuoteInsurance`. */
153
+ interface QuoteInsuranceOption {
154
+ id: string;
155
+ label: string;
156
+ /** SDK's own locale-formatted premium (e.g. "14,00 €"). There is no numeric
157
+ * amount on the public shape — the SDK keeps that internally — so a premium
158
+ * can be DISPLAYED but never added to another price. */
159
+ formattedPrice: string;
160
+ description?: string | undefined;
161
+ /** Insurer logo, when the feed provides one. The kit's insurance card does
162
+ * not render it; a site that wants the insurer's mark has it here. */
163
+ logoUrl?: string | undefined;
164
+ /** Plan feature names, verbatim (e.g. "Reiserücktritt"). */
165
+ features?: string[] | undefined;
166
+ /** Product documents (IPID, Bedingungen, …) — label + URL, verbatim. */
167
+ resources?: {
168
+ label: string;
169
+ url: string;
170
+ }[] | undefined;
171
+ /** True when the product needs per-traveler data before selection (the ERGO
172
+ * path: traveler forms, pre-contract, SEPA mandate, a post-booking
173
+ * `bookInsurance` call). False = the id alone completes the selection and
174
+ * the premium books WITH the rental, because `booking.book` injects
175
+ * `travelInsurancePlan`/`travelInsuranceAmount` from the quote for exactly
176
+ * these products. The checkout only offers the ones it can complete. */
177
+ requiresTravelers: boolean;
178
+ }
179
+ /** Travel-insurance choice on a live quote (`quote.insurance`), flattened for
180
+ * the checkout's radio section. Absent from the view when the feed offers no
181
+ * real product (options empty or "none"-only) — the section self-hides.
182
+ *
183
+ * The premium is NOT part of `card.total`: the SDK prices the stay and the
184
+ * policy separately, so a UI that shows both must show them as two figures.
185
+ * Adding them would mean parsing the PMS's formatted strings, which this file
186
+ * does not do for any other charge either. */
187
+ interface QuoteInsurance {
188
+ /** The SDK's own "no insurance" label, verbatim. */
189
+ noneLabel: string;
190
+ options: QuoteInsuranceOption[];
191
+ /** Selected product id, or null = no insurance (the default). */
192
+ selectedId: string | null;
193
+ }
194
+ /** The renderable view of a live GuestQuote — card + selectable options. */
195
+ interface QuoteView {
196
+ card: PriceQuote;
197
+ services: QuoteAddonService[];
198
+ policies: QuoteCancellationPolicy[];
199
+ /** Present once a code has been submitted and the quote re-priced; absent on
200
+ * a fresh quote. */
201
+ voucher?: QuoteVoucher | undefined;
202
+ /** Selectable travel insurance — absent when the feed offers no product. */
203
+ insurance?: QuoteInsurance | undefined;
204
+ }
205
+ type QuoteResult = {
206
+ status: "available";
207
+ /** Flattened price card (back-compat: the widget renders this). */
208
+ quote: PriceQuote;
209
+ /** The live SDK quote instance — required by booking.book. In-memory
210
+ * only; not serializable across navigations. */
211
+ live: GuestQuote;
212
+ /** Add-ons + policies for the checkout's optional sections. */
213
+ view: QuoteView;
214
+ } | {
215
+ status: "unavailable";
216
+ reason: string;
217
+ code: QuoteUnavailableCode;
218
+ };
219
+ /** Why the SDK refused to price the stay — machine-readable since website-sdk
220
+ * 2.16.0, and the ONLY thing callers may branch on. `reason` is localized and
221
+ * carries the SAME text for different codes, so comparing it is how a UI ends
222
+ * up telling a guest a free week is booked out.
223
+ *
224
+ * DERIVED from `GuestQuoteResult`, not re-listed: the SDK owns this set and a
225
+ * copy here would rot silently the day it grows a fourth member.
226
+ * `@v-office/sdk-core` exports the union as `GuestQuoteUnavailableCode`, but
227
+ * `@v-office/website-sdk` does not re-export it and sdk-core is a transitive
228
+ * dependency an adopting project does not declare — so `Extract` off the
229
+ * result type is the reachable spelling. */
230
+ type QuoteUnavailableCode = Extract<GuestQuoteResult, {
231
+ status: "unavailable";
232
+ }>["code"];
233
+ /** The two codes that mean "the stay WAS priced, but the rental's rate setup
234
+ * cannot be resolved to the single multi-rate a booking must be written
235
+ * against" — a v10 backend-configuration fault the guest has no part in.
236
+ *
237
+ * `resolveV10MultiRate` matches the priced quote's service lines against the
238
+ * rental's multi-rate rows for the policy, then tie-breaks on `baseRentRate`,
239
+ * which only fires when EXACTLY ONE tied row carries it. A rental whose PMS
240
+ * holds several equivalent base-rent rows per cancellation policy leaves the
241
+ * tie unbroken, and the SDK reports the quote unavailable rather than guess a
242
+ * rate — the price alone does not identify which row the booking belongs to.
243
+ *
244
+ * There is nothing an adopting site can do about the stay itself: it stays
245
+ * unbookable until the surplus rows are removed in the PMS. What the site owes
246
+ * the guest is honest copy — never the generic "not available", which asserts
247
+ * a booking conflict that does not exist. */
248
+ declare const MULTI_RATE_CODES: readonly ["AmbiguousMultiRate", "UnresolvableMultiRate"];
249
+ /** True when an unavailable quote is a rate-configuration fault rather than a
250
+ * genuinely unbookable stay. Callers branch the MESSAGE on this and must keep
251
+ * booking disabled either way (SDK 2.16.0 MIGRATION, "Required Consumer
252
+ * Work"). */
253
+ declare function isRateConfigurationFault(code: QuoteUnavailableCode | undefined): boolean;
254
+ interface QuoteOccupancy {
255
+ adults: number;
256
+ children: number;
257
+ childrenAges?: number[] | undefined;
258
+ babies: number;
259
+ pets: number;
260
+ }
261
+ /** Flatten a GuestQuote into the renderable view. Values verbatim.
262
+ *
263
+ * DISCOUNTS: the SDK does not hand us a discount LINE. When the PMS applies a
264
+ * price modifier (Frühbucher/Last-Minute, a voucher, a policy markup) it hangs
265
+ * it off the charge line as `modifiers[]` — label plus signed amount — and puts
266
+ * the pre-modifier figure in `totalBeforeModifiers`, leaving `appliedCharge`
267
+ * already net. Render `appliedCharge` alone (what every adoption did until now)
268
+ * and the saving is invisible: the guest sees only the reduced number and is
269
+ * never told a discount applied.
270
+ *
271
+ * So each modifier becomes its own row, and the line above it switches to
272
+ * `totalBeforeModifiers`. That pairing is not cosmetic — it is what makes the
273
+ * card add up. A deduction row under an `appliedCharge` that has ALREADY been
274
+ * reduced double-counts on sight: the column no longer sums to the total, and a
275
+ * guest checking the arithmetic finds it wrong. Gross line, then the deduction,
276
+ * then the remaining charges: 1.694,00 − 140,00 + 42,70 = 1.596,70.
277
+ *
278
+ * `bookingCardInformation.totalBeforeModifiers` is deliberately NOT rendered.
279
+ * The deduction rows already carry the saving, and a struck-through "was" price
280
+ * would state it a second time — as the one figure on the card that cannot be
281
+ * checked by adding, and as a price claim the customer would have to stand
282
+ * behind. The total stays the single net number.
283
+ */
284
+ declare function adaptGuestQuote(quote: GuestQuote): QuoteView;
285
+ /** Price a stay for `rentalId`. The caller owns the SDK instance and its
286
+ * lifecycle — pass a shared one to reuse its queue + cache across the caller's
287
+ * lifetime. `voucher` re-prices with a code (the PMS decides what it's worth). */
288
+ declare function fetchQuote(rentalId: string, period: {
289
+ start: string;
290
+ end: string;
291
+ }, occupancy: QuoteOccupancy, sdk: WebsiteSdk, voucher?: string): Promise<QuoteResult>;
292
+ type QuoteSelectionResult = {
293
+ ok: true;
294
+ quote: GuestQuote;
295
+ } | {
296
+ ok: false;
297
+ reason?: string | undefined;
298
+ };
299
+ declare function addQuoteService(quote: GuestQuote, id: string, sdk: WebsiteSdk): Promise<QuoteSelectionResult>;
300
+ declare function removeQuoteService(quote: GuestQuote, id: string, sdk: WebsiteSdk): Promise<QuoteSelectionResult>;
301
+ /** Drop every additional-service selection from `quote`.
302
+ *
303
+ * A fresh quote's selection state is not knowable from the service list. It is
304
+ * empty by default and carries the PMS's proposed quantities only when the
305
+ * project sets `shouldAddProposedAdditionalServiceAmount: true` — an SDK
306
+ * option a component cannot see — and the quote will not say which happened
307
+ * (`GuestQuote.selections` is behind an unexported symbol). Rather than branch
308
+ * on something invisible, a caller that needs a known starting point clears
309
+ * first and steps up from zero with `addQuoteService`. */
310
+ declare function clearQuoteServices(quote: GuestQuote, sdk: WebsiteSdk): Promise<QuoteSelectionResult>;
311
+ declare function selectQuotePolicy(quote: GuestQuote, id: string, sdk: WebsiteSdk): Promise<QuoteSelectionResult>;
312
+ /** Select a travel-insurance product (`id`) or none (`null`). Unlike the
313
+ * service/policy calls the SDK returns the new GuestQuote directly and throws
314
+ * on failure (unknown id) — normalized here to the same QuoteSelectionResult
315
+ * the other selection helpers use, so a stale id is a silent no-op the caller
316
+ * handles by keeping the previous quote.
317
+ *
318
+ * For a product that needs no traveler data this is the COMPLETE selection:
319
+ * the premium books with the rental via `booking.book`, which reads the plan
320
+ * off the quote. A product with `requiresTravelers` needs the pre-contract /
321
+ * payment / `bookInsurance` steps on top, which the checkout does not
322
+ * implement — it offers only the products it can complete. */
323
+ declare function selectQuoteInsurance(quote: GuestQuote, id: string | null, sdk: WebsiteSdk): Promise<QuoteSelectionResult>;
324
+ /**
325
+ * The concrete "free cancellation until" date for ONE policy — DERIVED, not
326
+ * fabricated: the PMS states its rules relatively (`daysBeforeArrival` +
327
+ * `refundPercentage`), so with the chosen arrival in hand the deadline is
328
+ * plain arithmetic (arrival − daysBeforeArrival), the same category of
329
+ * derivation as computing nights from a range.
330
+ *
331
+ * Uses the policy's full-refund (100%) rule. Returns null when the policy has
332
+ * no full-refund rule OR the derived date is already in the past (booking
333
+ * inside the window — advertising a bygone deadline would be wrong).
334
+ */
335
+ /** The policy's full-refund rule, if it has one. The refund figure is the SDK's
336
+ * own formatted string ("100%"), so this reads it rather than comparing a
337
+ * number — the one place that pattern is written, because both the derived
338
+ * deadline and the "is this a refundable rate at all" question depend on it. */
339
+ declare function fullRefundRule(policy: QuoteCancellationPolicy | undefined): QuotePolicyRule | undefined;
340
+ declare function policyFreeCancellationUntil(policy: QuoteCancellationPolicy | undefined, arrival: Date): Date | null;
341
+ /** The deadline for the stay as it stands — i.e. of the SELECTED policy.
342
+ * Kept as its own name because that is what a summary line means: not "some
343
+ * policy allows this" but "what you have chosen allows this". */
344
+ declare function freeCancellationUntil(policies: QuoteCancellationPolicy[], arrival: Date): Date | null;
345
+ /** The same stay priced under ONE cancellation policy. `quote` is a real
346
+ * GuestQuote branched off the base, so the UI can show its card, and hand the
347
+ * branch on to a later step, without re-quoting the stay. */
348
+ interface PolicyBranch {
349
+ id: string;
350
+ /** The branch quote — the base itself for the already-selected policy. */
351
+ quote: GuestQuote;
352
+ /** That quote flattened, so `view.card.total` is THIS policy's real price. */
353
+ view: QuoteView;
354
+ }
355
+ /**
356
+ * Price every selectable cancellation policy off ONE base quote, so a guest can
357
+ * compare the policies by what each actually costs rather than by a percentage.
358
+ *
359
+ * WHY REAL BRANCHES AND NOT ARITHMETIC. `markUpPercentage` looks like enough to
360
+ * compute the alternative price, and it is not: the modifier applies to the
361
+ * RENT, while taxes and fees ride along untouched, so the change it makes to
362
+ * the total is always smaller than the percentage says — and a guest who checks
363
+ * the number against the total would find the site's own figure wrong. The
364
+ * arithmetic is unavailable in any case: every amount the SDK returns is a
365
+ * locale-formatted string, so computing on one means parsing it back, which
366
+ * this file does for no other charge either. What the PMS prices is what the UI
367
+ * shows.
368
+ *
369
+ * `selectCancellationPolicy` does NOT mutate its input — it returns a new quote
370
+ * — so each policy can be branched off the same base and the branches held side
371
+ * by side. Costs one SDK call per policy MINUS one: the already-selected policy
372
+ * is the base quote itself. Sequential, because the API allows 1 req/s.
373
+ *
374
+ * A policy the PMS marks `available: false` is skipped rather than attempted:
375
+ * selecting it would fail by definition, and a burnt request inside a 1 req/s
376
+ * budget delays the ones that can succeed. It simply gets no branch, and the UI
377
+ * renders it as the unselectable row it already is.
378
+ */
379
+ declare function pricePolicyBranches(base: GuestQuote, sdk: WebsiteSdk): Promise<PolicyBranch[]>;
380
+ //#endregion
381
+ //#region src/booking.d.ts
382
+ /** Where the payment provider sends the guest back after an online payment
383
+ * (relative — the SDK/PSP resolves it against the site origin). */
384
+ declare const CHECKOUT_RETURN_PATH = "/buchen/bestaetigung";
385
+ /** The checkout form's flat contact fields (the UI shape). `toBookingContact`
386
+ * folds them into the SDK's nested BookingContact. */
387
+ interface BookingContactForm {
388
+ forename: string;
389
+ surname: string;
390
+ /** Anrede ("Frau" / "Herr"); omitted when the guest picks none. */
391
+ title?: string | undefined;
392
+ street: string;
393
+ housenumber?: string | undefined;
394
+ postalcode: string;
395
+ city: string;
396
+ /** ISO 3166-1 alpha-2, e.g. "DE". */
397
+ countryCode: string;
398
+ email: string;
399
+ phone?: string | undefined;
400
+ note?: string | undefined;
401
+ }
402
+ interface PaymentLine {
403
+ label: string;
404
+ amount: string;
405
+ }
406
+ /** One payment entry, verbatim — a contractual step (Anzahlung / Restzahlung /
407
+ * Rate / Kaution) or a way to settle the whole outstanding balance. */
408
+ interface PaymentScheduleView {
409
+ /** SDK's stable kind, for programmatic behaviour ONLY. Everything the guest
410
+ * reads comes from `label`: the SDK translates it, and a v10 installment is
411
+ * not automatically a v9 prepayment. */
412
+ kind: BookingPaymentScheduleKind | "full_amount";
413
+ label: string;
414
+ /** Complete translated phrase, e.g. "Zahlbar bis zum 6. Februar 2027".
415
+ * Render as-is — never parse it, never prefix another due-date label. It
416
+ * stopped being a bare date in website-sdk 2.13.0, and it was never reliably
417
+ * ISO before that either. */
418
+ dueOn?: string | undefined;
419
+ /** Where the payment happens, translated (v10 supplies it when the backend
420
+ * states a payment location). Same rule: render, don't reformat. */
421
+ paymentAt?: string | undefined;
422
+ lines: PaymentLine[];
423
+ amount: string;
424
+ /** The PMS's own payment options — the SDK union (bank_transfer / redirect /
425
+ * form_post / stripe_checkout), untouched. **EMPTY means informational:**
426
+ * the SDK cannot perform this payment right now (a future step, on-site, or
427
+ * external), so the card must not offer a button for it. */
428
+ options: BookingPaymentOption[];
429
+ }
430
+ /** A travel-insurance premium still to be settled WITH THE INSURER, from
431
+ * `book().insurancePaymentOptions` — label and URL verbatim. Deliberately not
432
+ * a `PaymentScheduleView`: those are the PMS's own steps, owed to the site's
433
+ * operator and payable through the SDK. This one is a different contract with
434
+ * a different recipient, and it leaves the site entirely. */
435
+ interface InsurancePaymentLink {
436
+ label: string;
437
+ url: string;
438
+ description?: string | undefined;
439
+ }
440
+ interface BookingConfirmation {
441
+ bookingNumber: string;
442
+ /** The contractual steps, in backend order — what the guest owes and when. */
443
+ schedules: PaymentScheduleView[];
444
+ /** Ways to settle the COMPLETE outstanding balance at once (kind
445
+ * `full_amount`), an ALTERNATIVE to the schedule above rather than an
446
+ * addition to it. Kept apart because summing the two would double-count, and
447
+ * because a synthesized alternative carries no deadline of its own — the
448
+ * schedule's steps are where the dates live. */
449
+ alternatives: PaymentScheduleView[];
450
+ /** Empty unless insurance was actually booked AND the insurer returned a
451
+ * payment page for it. Kept out of `schedules` and `alternatives` on
452
+ * purpose: the premium is in neither, so a card that listed it among them
453
+ * would misstate what is owed to whom (CONTRACT.md, "Travel insurance"). */
454
+ insurancePayments: InsurancePaymentLink[];
455
+ }
456
+ /**
457
+ * Create the booking. `quote` MUST be the live GuestQuote instance (with any
458
+ * service/policy selections applied). USER-initiated only — this reserves for
459
+ * real. Throws on transport/backend errors (the caller renders an honest
460
+ * retry message; nothing was necessarily booked when it throws).
461
+ */
462
+ declare function submitBooking(quote: GuestQuote, contact: BookingContactForm, sdk: WebsiteSdk): Promise<BookingConfirmation>;
463
+ /** True for the option kinds `submitPaymentOption` can perform in-browser
464
+ * (redirect / form_post / stripe_checkout). Bank transfer is display-only. */
465
+ declare function isSubmittableOption(option: BookingPaymentOption): option is SubmittableBookingPaymentOption;
466
+ /** Perform an online payment option (redirects the browser to the PSP).
467
+ * USER-initiated only.
468
+ *
469
+ * Logged on failure, and this is the worst place in the flow to lose a reason:
470
+ * by the time a payment option is on screen THE RESERVATION ALREADY EXISTS
471
+ * (the SDK's `booking.md` says as much), so a failure here is an unpaid
472
+ * booking, not a failed one. The checkout's catch discards the error entirely
473
+ * — `.catch(() => setPayError(true))` — so without this a guest who cannot
474
+ * pay leaves no trace at all, on a booking someone now has to chase. */
475
+ declare function payWithOption(option: SubmittableBookingPaymentOption, sdk: WebsiteSdk): Promise<void>;
476
+ //#endregion
477
+ //#region src/booking-snapshot.d.ts
478
+ /** Rental identity, parked by the checkout page (which has it from SSR) and
479
+ * folded into the snapshot at book time — the SDK seam that writes the
480
+ * snapshot never sees a rental name or a link. */
481
+ declare const CHECKOUT_CONTEXT_KEY = "voffice:checkout-context";
482
+ /** The finished record `/buchen/bestaetigung` reads. */
483
+ declare const BOOKING_SNAPSHOT_KEY = "voffice:booking-return";
484
+ /** One gallery image, in the SDK's own shape (`RentalGalleryImage` minus the
485
+ * fields this page does not render). Stored so the confirmation can show the
486
+ * unit rather than describe it — the guest booked a place, not a table. */
487
+ interface SnapshotImage {
488
+ src: string;
489
+ srcset?: string | undefined;
490
+ alt: string;
491
+ }
492
+ interface CheckoutContext {
493
+ rentalName: string;
494
+ location?: string | undefined;
495
+ href?: string | undefined;
496
+ /** Capped at the same five the checkout's own summary strip shows. */
497
+ images?: SnapshotImage[] | undefined;
498
+ }
499
+ interface BookingSnapshotGuests {
500
+ adults: number;
501
+ children: number;
502
+ babies: number;
503
+ pets: number;
504
+ }
505
+ /** Every field but `bookingNumber` self-hides when absent — the return page
506
+ * renders a row only for what it actually has, never an empty shell
507
+ * (`DATA_SCHEMA.md`: "Optional field gates optional UI"). */
508
+ interface BookingSnapshot {
509
+ bookingNumber: string;
510
+ rentalName?: string | undefined;
511
+ location?: string | undefined;
512
+ href?: string | undefined;
513
+ images?: SnapshotImage[] | undefined;
514
+ /** `YYYY-MM-DD` — the vocabulary the whole funnel already carries. */
515
+ start?: string | undefined;
516
+ end?: string | undefined;
517
+ guests?: BookingSnapshotGuests | undefined;
518
+ /** The SDK's own formatted total, verbatim. Never re-derived, never summed. */
519
+ total?: string | undefined;
520
+ }
521
+ /** Park the rental's identity for the return page. Called by the checkout page
522
+ * on load — it is the last place in the flow that knows the rental by name. */
523
+ declare function saveCheckoutContext(context: CheckoutContext): void;
524
+ /**
525
+ * Write the return page's record. Called by `submitBooking` the moment the
526
+ * reservation exists — EARLIER than the SDK's "before submitting a payment
527
+ * option", because that is when the booking number is born, and a guest who
528
+ * settles by bank transfer never leaves the checkout at all.
529
+ *
530
+ * Never throws. The snapshot is a nicety; the booking is not.
531
+ */
532
+ declare function saveBookingSnapshot(bookingNumber: string, quote: GuestQuote): void;
533
+ //#endregion
534
+ //#region src/checkout/insurance.d.ts
535
+ /** The plan's documents as the checkout treats them: what it lists, what the
536
+ * guest must open, and what makes a plan sellable at all read the SAME list,
537
+ * so a hidden document can never end up required-but-unlisted (which would be
538
+ * a gate the guest cannot close). */
539
+ declare function shownInsuranceResources(option: QuoteInsuranceOption): {
540
+ label: string;
541
+ url: string;
542
+ }[];
543
+ /** The insurance products a checkout can actually complete. Two gates, and
544
+ * this filter is where the layer's capability is declared — a site that adds a
545
+ * missing flow adds it HERE, upstream, never in its own copy.
546
+ *
547
+ * 1. **Technical.** Products that select with the id alone and book WITH the
548
+ * rental booking. One that demands per-traveler data first (traveler forms,
549
+ * pre-contract, SEPA mandate, a post-booking insurance call) needs a flow
550
+ * this layer does not implement — offering it would dead-end the guest.
551
+ *
552
+ * 2. **Legal.** The product must carry its `resources`, the insurer's
553
+ * pre-contractual documents (see "Mandatory pre-contractual documents"
554
+ * below for which duties they discharge). A product the feed sends WITHOUT
555
+ * them cannot be concluded lawfully here, so it self-hides rather than
556
+ * being sold undocumented — the same rule as (1), applied to a legal
557
+ * precondition instead of a technical one. */
558
+ declare function selectableInsuranceOptions(insurance: QuoteInsurance): QuoteInsuranceOption[];
559
+ /** The selected plan, but only when it is one the guest could actually pick
560
+ * here — so no price row can name a product the section above never showed. */
561
+ declare function selectedInsuranceOption(insurance: QuoteInsurance | undefined): QuoteInsuranceOption | undefined;
562
+ /** The identity of an insurance PRODUCT, as opposed to the id of one priced
563
+ * variant of it.
564
+ *
565
+ * An insurer may band its tariffs by trip value, and then a plan id is not the
566
+ * product — it is the product AT one price. The same tariff comes back under a
567
+ * new id and a new premium the moment the total crosses a band. URV is the
568
+ * observed case: "RRV ohne SB Deutschland" is `RROR0625` at 56,00 € up to
569
+ * roughly 1.250 €, `RROR0725` at 65,00 € above that, `RROR0825` at 74,00 €
570
+ * above that again. Every extra, every policy markup and every stay change can
571
+ * cross a band, and each of them re-prices the quote — which is what makes the
572
+ * SDK re-fetch the insurer's plan list against the new total.
573
+ *
574
+ * The label is what survives that: the same string in every band, and the name
575
+ * the guest actually chose, since it is what the radio shows. */
576
+ declare function insuranceProductKey(option: QuoteInsuranceOption): string;
577
+ /** The plan id to re-apply onto a re-priced quote, or null when there is
578
+ * nothing to do — no choice, already applied, or a product this feed no longer
579
+ * offers as a selectable one. The last case is why the feed is checked rather
580
+ * than the call simply attempted: a doomed selection would spend one of the
581
+ * rate-limited requests to be refused, and the guest ends up on "keine
582
+ * Versicherung" either way.
583
+ *
584
+ * Matched by id first, then by product (see insuranceProductKey). That
585
+ * fallback is what keeps a banded re-price from silently un-selecting
586
+ * insurance the feed still sells: the SDK carries a selection across its
587
+ * refresh only on an exact id match, so a renamed-but-identical tariff reads
588
+ * to it as a plan that vanished, and a guest who only added a towel package
589
+ * lands back on "keine Versicherung". Re-applying picks the SAME product at
590
+ * its new premium — the one they would in fact be buying — and that premium is
591
+ * on screen in the radio, in the summary rail and above the submit button, so
592
+ * nothing about the change is hidden.
593
+ *
594
+ * Checked against the SELECTABLE options, not the raw list: a plan the fresh
595
+ * feed carries without its documents (or newly demanding traveler data) is one
596
+ * the section no longer shows, so re-applying it would leave the quote
597
+ * carrying an insurance the page neither displays nor can take a document
598
+ * confirmation for. */
599
+ declare function insuranceIdToReapply(fresh: QuoteView, wanted: QuoteInsuranceOption | undefined): string | null;
600
+ /** Progress through the document step. Held by the orchestration hook because
601
+ * the submit button, a card away, is gated on it. */
602
+ interface InsuranceDocsState {
603
+ /** URLs opened so far — keyed by URL, not by plan. Plans routinely share
604
+ * every file but the IPID, and having received an identical document does
605
+ * not un-happen when the guest switches tariff. Keying by URL also survives
606
+ * a re-quote, which returns fresh option objects for the same product. */
607
+ opened: ReadonlySet<string>;
608
+ /** Product whose documents the guest confirmed receiving (keyed by
609
+ * insuranceProductKey, not by plan id); null = none yet. Product-bound on
610
+ * purpose, so a tariff switch demands a fresh acknowledgement while the
611
+ * opened files above carry over. By PRODUCT rather than by plan for the same
612
+ * reason the selection is carried by product: a banded re-price hands back
613
+ * the identical documents under a new plan id, and making the guest re-tick
614
+ * a box for files they already have — because a towel package moved the
615
+ * total — would be friction with nothing behind it. */
616
+ confirmedProduct: string | null;
617
+ }
618
+ declare const NO_INSURANCE_DOCS: InsuranceDocsState;
619
+ /** True while a chosen plan's document step is unfinished. No plan chosen (or
620
+ * "keine Versicherung") is never blocking — the duty attaches to the insurance
621
+ * contract, not to the stay. */
622
+ declare function insuranceDocsPending(option: QuoteInsuranceOption | undefined, docs: InsuranceDocsState): boolean;
623
+ //#endregion
624
+ //#region src/checkout/stay.d.ts
625
+ interface Stay {
626
+ start: Date;
627
+ end: Date;
628
+ adults: number;
629
+ children: number;
630
+ childrenAges: number[];
631
+ babies: number;
632
+ pets: number;
633
+ }
634
+ /** Date → `YYYY-MM-DD` in LOCAL time — the vocabulary the URL handover and the
635
+ * SDK both use. Local on purpose (matches date-fns `format(date,
636
+ * "yyyy-MM-dd")`, which the sites' own lib/dates.ts uses): a calendar day
637
+ * picked in the browser must not shift across midnight UTC. */
638
+ declare function isoDate(date: Date): string;
639
+ declare function occupancyOf(stay: Stay): QuoteOccupancy;
640
+ /** The identity of a stay for "is the shown price still the shown stay"
641
+ * comparisons — every field that changes the quote, in one string. */
642
+ declare function stayKeyOf(stay: Stay): string;
643
+ /** The stay as URL search params (`start`/`end`/`adults`/… — the same
644
+ * vocabulary the rental page reads + writes). */
645
+ declare function stayParams(stay: Stay): string;
646
+ //#endregion
647
+ //#region src/checkout/createCheckoutFlow.d.ts
648
+ /**
649
+ * The checkout's orchestration — everything between "a complete stay is in
650
+ * hand" and "the guest is booked and paying", with no markup and no framework.
651
+ *
652
+ * The flow owns the quote lifecycle (initial load, re-quote on stay change,
653
+ * supersession), the selection mutations (extras / insurance / voucher /
654
+ * policy re-apply), the § 7 VVG document gate, booking submit and payment —
655
+ * the parts that must be correct on every site and update mechanically. The
656
+ * UI owns stay EDITING (calendar, guest panel, URL read/write), copy, and
657
+ * every pixel. A UI subscribes to `subscribe`, reads `getState`, calls the
658
+ * actions, and feeds stay/policy changes through `update` (React: one
659
+ * useSyncExternalStore — see the README).
660
+ *
661
+ * ONE flow instance owns the whole thing because the live `GuestQuote` that
662
+ * `booking.book` requires is not serializable across a navigation: the
663
+ * consumer re-quotes from the URL handover on mount and the flow keeps the
664
+ * instance in memory through extras / policy / voucher selections until
665
+ * book().
666
+ *
667
+ * ⚠ `book()` creates a REAL booking; `pay()` starts a REAL payment. Both are
668
+ * strictly user-initiated — never automate them.
669
+ */
670
+ type CheckoutPhase = {
671
+ kind: "loading";
672
+ } | {
673
+ kind: "ready";
674
+ } | {
675
+ kind: "unavailable";
676
+ reason: string;
677
+ } | {
678
+ kind: "error";
679
+ };
680
+ /** The user-facing strings the flow itself needs — the reasons it reports when
681
+ * the backend gives none, or gives one that would be a false statement. Copy,
682
+ * so it stays per-site. */
683
+ interface CheckoutFlowMessages {
684
+ /** Shown INSTEAD of the SDK's localized unavailability reason when the quote
685
+ * failed on a rate-configuration fault (AmbiguousMultiRate /
686
+ * UnresolvableMultiRate) — the stay is free; only the price cannot be
687
+ * resolved, so the availability wording would be a false statement. */
688
+ rateFault: string;
689
+ /** Fallback when an unavailable result carries no reason of its own. */
690
+ unavailable: string;
691
+ /** Inline issue line when a RE-quote (not the initial load) failed on transport. */
692
+ quoteRefreshFailed: string;
693
+ /** Voucher could not be applied at all (transport / stay unavailable). */
694
+ voucherFailed: string;
695
+ /** Voucher was processed and rejected / not recognized. */
696
+ voucherRejected: string;
697
+ }
698
+ interface CheckoutFlowOptions {
699
+ rentalId: string;
700
+ /** The site's SDK accessor (one instance for the flow's lifetime — quote +
701
+ * selections + book share its queue and 1 req/s rate limit). The flow never
702
+ * disposes it; the owner does. */
703
+ getSdk: () => WebsiteSdk;
704
+ /** The current COMPLETE stay, or null while none is in hand (mid-edit, URL
705
+ * handover missing). The flow re-quotes whenever it changes. */
706
+ stay: Stay | null;
707
+ /** The cancellation rate chosen a step earlier (`?policy=` handover). A SEED
708
+ * for the first quote, not the source of truth: once a quote exists, the
709
+ * live quote's own `selected` flag is. Null means "whatever the PMS
710
+ * pre-selects", which is also what an unknown id degrades to. */
711
+ chosenPolicy: string | null;
712
+ /**
713
+ * Which of a fresh quote's policies may be treated as OFFERED for the
714
+ * `chosenPolicy` re-apply — REQUIRED, because this is the enforcement point
715
+ * for the whole offer-withdrawal rule: a rate stops being an offer when the
716
+ * stay moves inside the standard rate's cancellation window, at which point
717
+ * the cheaper non-refundable one is the identical booking for less. Every
718
+ * route onto the checkout passes through this check (a stale link, a
719
+ * hand-built `?policy=`, an in-page date edit), so all of them lapse to the
720
+ * PMS pre-selection together.
721
+ */
722
+ filterOfferedPolicies: (policies: QuoteCancellationPolicy[], arrival: Date) => QuoteCancellationPolicy[];
723
+ messages: CheckoutFlowMessages;
724
+ /** Called when a quote returns `AmbiguousMultiRate` / `UnresolvableMultiRate`
725
+ * — a PMS rate-configuration fault, not a booked-out stay. Route these to
726
+ * monitoring or site operations. */
727
+ onRateConfigurationFault?: ((info: {
728
+ code: QuoteUnavailableCode;
729
+ rentalId: string;
730
+ }) => void) | undefined;
731
+ }
732
+ interface CheckoutIssue {
733
+ /** The stayKey the issue belongs to — a different stay re-arms the loop. */
734
+ key: string;
735
+ reason: string;
736
+ /** Transport errors offer a manual retry; unavailability does not. */
737
+ retryable: boolean;
738
+ }
739
+ /** Everything a UI renders. A new object per change; unchanged otherwise, so
740
+ * `useSyncExternalStore` and friends can compare by reference. */
741
+ interface CheckoutFlowState {
742
+ phase: CheckoutPhase;
743
+ view: QuoteView | null;
744
+ counts: Record<string, number>;
745
+ busy: boolean;
746
+ issue: CheckoutIssue | null;
747
+ selectionError: string | null;
748
+ quotedKey: string | null;
749
+ stayKey: string | null;
750
+ /** A re-quote is pending: the shown price does not match the shown stay. */
751
+ pricePending: boolean;
752
+ /** Booking is blocked (stale price, in-flight mutation, issue, or documents pending). */
753
+ blocked: boolean;
754
+ chosenInsurance: QuoteInsuranceOption | undefined;
755
+ insuranceDocs: InsuranceDocsState;
756
+ documentsPending: boolean;
757
+ voucherApplied: string | null;
758
+ voucherError: string | null;
759
+ bookState: "idle" | "submitting" | "error";
760
+ confirmation: BookingConfirmation | null;
761
+ payBusyKey: string | null;
762
+ payError: boolean;
763
+ }
764
+ /** The actions a UI calls. Stable functions for the flow's lifetime. */
765
+ interface CheckoutFlowActions {
766
+ changeService(id: string, delta: 1 | -1): void;
767
+ selectInsurance(id: string | null): void;
768
+ openInsuranceDoc(url: string): void;
769
+ confirmInsuranceDocs(productKey: string, confirmed: boolean): void;
770
+ applyVoucher(code: string | null): void;
771
+ /** ⚠ USER-initiated only: creates a real booking. */
772
+ book(contact: BookingContactForm): void;
773
+ /** ⚠ USER-initiated only: redirects to the real payment provider. */
774
+ pay(key: string, option: PaymentScheduleView["options"][number]): void;
775
+ clearIssue(): void;
776
+ }
777
+ interface CheckoutFlow extends Store<CheckoutFlowState>, CheckoutFlowActions {
778
+ /** Feed the UI's current inputs. `stay` and `chosenPolicy` drive the
779
+ * re-quote; messages and callbacks are read at call time. */
780
+ update(options: CheckoutFlowOptions): void;
781
+ /** Arm the auto-quote. Idempotent; call once the UI is mounted. */
782
+ start(): void;
783
+ /** Cancel pending work and drop every in-flight result. */
784
+ dispose(): void;
785
+ }
786
+ /** What a UI binding hands to the rendering code: the state plus the actions. */
787
+ type CheckoutFlowApi = CheckoutFlowState & CheckoutFlowActions;
788
+ declare function createCheckoutFlow(initialOptions: CheckoutFlowOptions): CheckoutFlow;
789
+ //#endregion
790
+ //#region src/checkout/card-order.d.ts
791
+ declare function stabilizeCardOrder(view: QuoteView, order: Map<string, number>): QuoteView;
792
+ //#endregion
793
+ //#region src/checkout/validation.d.ts
794
+ /**
795
+ * Contact-form field validation — the checkout's data-quality behavior,
796
+ * framework-free.
797
+ *
798
+ * Extracted from the sites' checkout island (`CheckoutFlow.tsx`). The RULES live here so every site validates a booking
799
+ * contact the same way; the MESSAGES are copy and stay per-site — every
800
+ * function takes them as a parameter (the site's `CheckoutFlowCopy` object
801
+ * satisfies `ValidationMessages` structurally, so skins pass their merged copy
802
+ * straight through).
803
+ */
804
+ /** The subset of the checkout copy the validators read. A site's full copy
805
+ * object satisfies this structurally. */
806
+ interface ValidationMessages {
807
+ forenameRequired: string;
808
+ surnameRequired: string;
809
+ streetRequired: string;
810
+ cityRequired: string;
811
+ postalcodeRequired: string;
812
+ emailRequired: string;
813
+ emailInvalid: string;
814
+ phoneInvalid: string;
815
+ phoneNeedsDialCode: string;
816
+ postalcodeInvalidDE: string;
817
+ postalcodeInvalidAT: string;
818
+ postalcodeInvalidCH: string;
819
+ streetHouseNumberWarning: string;
820
+ }
821
+ declare function normalizePhone(raw: string, dial?: string): string;
822
+ declare function phoneError(raw: string, dial: string, c: ValidationMessages): string | undefined;
823
+ declare function emailError(raw: string, c: ValidationMessages): string | undefined;
824
+ declare function postalcodeError(raw: string, country: string, c: ValidationMessages): string | undefined;
825
+ declare function streetWarning(raw: string, c: ValidationMessages): string | undefined;
826
+ declare const VALIDATED_FIELDS: readonly ["forename", "surname", "email", "phone", "street", "postalcode", "city"];
827
+ /** Validate one named field. Takes the selected COUNTRY plus its calling code
828
+ * (`dial`): two validators depend on the country — the phone via the dial
829
+ * code, the PLZ via its format rule. The dial code is passed in rather than
830
+ * looked up because the ISO→dial mapping lives with the site's country
831
+ * register (its localized "Land" options), not here. */
832
+ declare function fieldError(name: string, value: string, country: string, dial: string, c: ValidationMessages): string | undefined;
833
+ /** Country options in the shape the site's country register provides —
834
+ * `[code, label]` pairs, a featured group plus the full ISO register. Kept
835
+ * structural so the site's own `CountryGroups` type satisfies it. */
836
+ interface CountryGroupsLike {
837
+ featured: ReadonlyArray<readonly [string, string]>;
838
+ all: ReadonlyArray<readonly [string, string]>;
839
+ }
840
+ declare function detectCountry(countries: CountryGroupsLike): string;
841
+ //#endregion
842
+ //#region src/checkout/structure.d.ts
843
+ /**
844
+ * The checkout's STRUCTURAL requirements — what must be on screen, in what
845
+ * order, regardless of who produced the markup.
846
+ *
847
+ * WHY THIS EXISTS. Headless holds the RULES (the § 7 VVG document gate, the
848
+ * stale-price booking guard) but no markup, so a site that renders its own
849
+ * checkout UI can silently drop a legal obligation — the § 312j BGB
850
+ * Button-Lösung wording, the insurance documents listed before the guest's
851
+ * confirmation, the price stated directly above the order button — and every
852
+ * unit test still passes. This module is a contract over the RENDERED result,
853
+ * checked by the platform's publish gate for imported and custom checkouts
854
+ * alike.
855
+ *
856
+ * Two halves:
857
+ * - `CHECKOUT_PARTS`: the `data-part` vocabulary. Imported components carry
858
+ * these attributes; a site's own markup must keep them so the gate (and
859
+ * a site's own CSS) can find the elements.
860
+ * - `CHECKOUT_STRUCTURE` + `assertCheckoutStructure(root)`: the requirements
861
+ * and a pure DOM check the smoke suite runs against the rendered checkout
862
+ * (a browser page or a jsdom document). Every requirement names the legal
863
+ * basis it exists for, so a failure message tells the reader WHY.
864
+ */
865
+ declare const CHECKOUT_PARTS: {
866
+ /** The one place the guest is told the price they are about to owe. */
867
+ readonly priceTotal: "checkout-price-total";
868
+ /** The § 312j order button. */
869
+ readonly submit: "checkout-submit";
870
+ /** The terms line (AGB + privacy triggers). */
871
+ readonly terms: "checkout-terms";
872
+ /** One link per mandatory insurance document (§ 7 VVG). */
873
+ readonly insuranceDocument: "checkout-insurance-document";
874
+ /** The guest's receipt confirmation for those documents. */
875
+ readonly insuranceConfirm: "checkout-insurance-confirm";
876
+ };
877
+ type CheckoutPart = (typeof CHECKOUT_PARTS)[keyof typeof CHECKOUT_PARTS];
878
+ /** German "kostenpflichtig bestellen"-class labels the courts have accepted
879
+ * for § 312j Abs. 3 BGB. The check is on the RENDERED button text, so a site
880
+ * may re-voice the label — but only within this class. */
881
+ declare const BUTTON_SOLUTION_PATTERN: RegExp;
882
+ declare const CHECKOUT_STRUCTURE: {
883
+ readonly "submit-present": {
884
+ readonly basis: "§ 312j Abs. 3 BGB (Button-Lösung)";
885
+ readonly description: "exactly one order button carries data-part=checkout-submit";
886
+ };
887
+ readonly "submit-wording": {
888
+ readonly basis: "§ 312j Abs. 3 BGB (Button-Lösung)";
889
+ readonly description: "the order button's visible text is a 'zahlungspflichtig buchen'-class label";
890
+ };
891
+ readonly "total-before-submit": {
892
+ readonly basis: "§ 312j Abs. 2 BGB (price stated immediately before the order)";
893
+ readonly description: "a data-part=checkout-price-total precedes the order button in DOM order";
894
+ };
895
+ readonly "terms-before-submit": {
896
+ readonly basis: "§ 312d BGB / Art. 13 DSGVO (terms + privacy notice before contracting)";
897
+ readonly description: "a data-part=checkout-terms precedes the order button";
898
+ };
899
+ readonly "insurance-docs-before-confirm": {
900
+ readonly basis: "§ 7 Abs. 1 VVG, § 4 VVG-InfoV (documents before the declaration)";
901
+ readonly description: "when an insurance confirmation is rendered, at least one insurance document precedes it in DOM order";
902
+ };
903
+ };
904
+ type CheckoutRequirement = keyof typeof CHECKOUT_STRUCTURE;
905
+ interface StructureViolation {
906
+ requirement: CheckoutRequirement;
907
+ basis: string;
908
+ detail: string;
909
+ }
910
+ declare function assertCheckoutStructure(root: ParentNode): StructureViolation[];
911
+ //#endregion
912
+ export { BOOKING_SNAPSHOT_KEY, BUTTON_SOLUTION_PATTERN, BookingConfirmation, BookingContactForm, BookingSnapshot, BookingSnapshotGuests, CHECKOUT_CONTEXT_KEY, CHECKOUT_PARTS, CHECKOUT_RETURN_PATH, CHECKOUT_STRUCTURE, CheckoutContext, type CheckoutFlow, type CheckoutFlowActions, type CheckoutFlowApi, type CheckoutFlowMessages, type CheckoutFlowOptions, type CheckoutFlowState, type CheckoutIssue, type CheckoutPart, type CheckoutPhase, type CountryGroupsLike, type InsuranceDocsState, InsurancePaymentLink, Listener, MULTI_RATE_CODES, NO_INSURANCE_DOCS, PaymentLine, PaymentScheduleView, PolicyBranch, PriceQuote, QuoteAddonService, QuoteCancellationPolicy, QuoteInsurance, QuoteInsuranceOption, QuoteLine, QuoteOccupancy, QuotePolicyRule, QuoteResult, QuoteSelectionResult, QuoteUnavailableCode, QuoteView, QuoteVoucher, SnapshotImage, type Stay, Store, type StructureViolation, Unsubscribe, VALIDATED_FIELDS, type ValidationMessages, WebsiteSdk, WritableStore, adaptGuestQuote, addQuoteService, assertCheckoutStructure, clearQuoteServices, createCheckoutFlow, createStore, describeSdkError, detectCountry, emailError, fetchQuote, fieldError, freeCancellationUntil, fullRefundRule, insuranceDocsPending, insuranceIdToReapply, insuranceProductKey, isRateConfigurationFault, isSubmittableOption, isoDate, logSdkFailure, normalizePhone, occupancyOf, payWithOption, phoneError, policyFreeCancellationUntil, postalcodeError, pricePolicyBranches, removeQuoteService, saveBookingSnapshot, saveCheckoutContext, selectQuoteInsurance, selectQuotePolicy, selectableInsuranceOptions, selectedInsuranceOption, shownInsuranceResources, stabilizeCardOrder, stayKeyOf, stayParams, streetWarning, submitBooking };