# TIDE-3053 — Expose payment URL in self-contained booking wizard

## Goal

Make the Tide PSP payment flow usable from the **self-contained** booking-wizard build (`selfcontained/booking-wizard`). Until this change the wrapper had no way to opt into payment-link generation, even though the underlying React component already supported it. Also give the embedding host page a way to **see and act on** the `paymentUrl` returned by Tide instead of being force-redirected.

## Background — how the payment flow already worked in the underlying component

These settings already exist on `Settings` (see [src/booking-wizard/types.ts](src/booking-wizard/types.ts)):

- `generatePaymentUrl` — master switch. When true, the booking call sends `returnPaymentUrl: true` and Tide responds with a `paymentUrl` (PSP URL).
- `skipPaymentWithAgent` — when an agent is selected, suppress the redirect anyway (B2B flow).

The booking-request builder at [src/booking-wizard/features/booking/selectors.ts:321-325](src/booking-wizard/features/booking/selectors.ts:321) computes `returnPaymentUrl` from those two settings + the selected agent. The summary page at [src/booking-wizard/features/summary/summary.tsx](src/booking-wizard/features/summary/summary.tsx) then submits the booking, gets a `paymentUrl` back, and (previously) auto-redirected to it.

The self-contained wrapper at [selfcontained/booking-wizard/index.tsx](selfcontained/booking-wizard/index.tsx) reads HTML attributes off the inner `<div>` and builds a `Settings` object — but it never read any payment-related attributes, so `generatePaymentUrl` defaulted to `false` and the whole chain was inert.

## Changes in this branch

### 1. `selfcontained/booking-wizard/index.tsx`

Read three new HTML attributes from the settings element and pass them through to the `Settings` context:

| Attribute              | Type                   | Meaning                                                                                                                                                      |
| ---------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `generatePaymentUrl`   | presence-based boolean | Enables PSP payment-link generation                                                                                                                          |
| `skipPaymentWithAgent` | presence-based boolean | Skips the payment redirect when an agent is selected                                                                                                         |
| `paymentRedirectUrl`   | string                 | URL Tide passes to the PSP as the "return after payment" URL. If omitted, falls back to the previous default (built from `basePath` + booking query string). |

### 2. `src/booking-wizard/types.ts`

Added `paymentRedirectUrl?: string` to `Settings`.

### 3. `src/booking-wizard/features/summary/summary.tsx`

Two behavior changes (apply to both the routed and self-contained wizard builds):

a. **Configurable PSP return URL.** When `settings.paymentRedirectUrl` is set, it is used verbatim as `bookRequest.payload.redirectUrl`. Otherwise the previous `${protocol}//${host}${basePath}?${bookingQueryString}` construction is used. This lets the host pass a URL that includes a `{bookingNr}` placeholder for Tide to substitute server-side (see "Open question" below).

b. **`paymentUrl` is exposed via a `CustomEvent`.** When the booking response contains a `paymentUrl`, a cancelable `CustomEvent` named `tide-booking-payment-url` is dispatched on the `#tide-booking` element (or `window` as fallback) before the auto-redirect. Detail payload:

```ts
{
  paymentUrl: string;
  bookingNumber: string;
}
```

The event bubbles and is cancelable. If a listener calls `e.preventDefault()`, the component will **not** auto-redirect to `paymentUrl` — the host page is then responsible for whatever happens next (open in new tab, modal, custom redirect, etc.). When nothing listens (or the listener doesn't `preventDefault`), the existing behavior is preserved: `window.location.href = paymentUrl`.

## How a host page consumes this

```html
<div id="tide-booking">
  <div
    officeId="1"
    apiUrl="..."
    apiKey="..."
    productCode="..."
    generatePaymentUrl
    paymentRedirectUrl="https://example.com/confirmation?bookingNr={bookingNr}"></div>
</div>

<script src="dist/bundle.js"></script>
<script>
  document.getElementById('tide-booking').addEventListener('tide-booking-payment-url', (e) => {
    console.log(e.detail.paymentUrl, e.detail.bookingNumber);
    // Option A: do nothing → component auto-redirects to e.detail.paymentUrl
    // Option B: e.preventDefault(); window.open(e.detail.paymentUrl, '_blank'); etc.
  });
</script>
```

## Open question to confirm with the Tide backend team

The user-confirmed behavior is: **Tide's API uses the `redirectUrl` string verbatim — it does not append `bookingNr` automatically.** In the Sitecore integration, a `{bookingNr}` (or similar) placeholder is included in the `redirectUrl` and Tide substitutes the actual booking number server-side before handing the URL to the PSP.

We need confirmation of:

1. The exact placeholder syntax Tide's API supports (`{bookingNr}`? `{{bookingNr}}`? something else?).
2. Whether other tokens are supported (e.g. `{officeId}`, `{transactionId}`).

The wizard already handles the return: [booking-self-contained.tsx:132](src/booking-wizard/features/booking/booking-self-contained.tsx:132) reads `bookingNr` from the URL on load and jumps to the confirmation step. So once the placeholder syntax is confirmed and the host page sends e.g. `?bookingNr={bookingNr}`, the resume flow should work end-to-end.

## Not done in this PR (deliberately)

- **No UI changes.** The summary spinner / "Redirecting to payment provider…" label is unchanged. There is no UI for the host to "review the URL" before redirecting — that's expected to be host-side.
- **No end-to-end test against a real PSP-configured Tide office.** Needs verification.
- **Search-results booking flow is untouched.** [src/search-results/components/book-packaging-entry/index.tsx:184](src/search-results/components/book-packaging-entry/index.tsx:184) still does an unconditional `window.location.href = paymentUrl`. If consistent behavior across both booking surfaces is desired, the same `CustomEvent` should be added there (it already has its own `generatePaymentUrl` flag on a separate context type).
- **Type declaration for the custom event.** Consumers using TypeScript will currently see `e.detail` as `any` because the event isn't declared in a `WindowEventMap` augmentation. Consider adding a `.d.ts` if the host site is in TS.

## Files touched

- `selfcontained/booking-wizard/index.tsx`
- `src/booking-wizard/types.ts`
- `src/booking-wizard/features/summary/summary.tsx`
- `MEMORY.md` (this file)
