diff --git a/documentation/MODULE__AE_Events_Exhibitor_Leads.md b/documentation/MODULE__AE_Events_Exhibitor_Leads.md
index 73888d4c..f8d81239 100644
--- a/documentation/MODULE__AE_Events_Exhibitor_Leads.md
+++ b/documentation/MODULE__AE_Events_Exhibitor_Leads.md
@@ -260,3 +260,10 @@ Guard in [ae_tab__manage.svelte](src/routes/events/[event_id]/(leads)/leads/exhi
- Export endpoint: `GET /v3/action/event_exhibit/{id}/tracking_export` — requires `leads_api_access`
- Custom questions are stored per-exhibit in `leads_custom_questions_json` (not global)
- The exhibitor landing page link format: `/events/[event_id]/leads/exhibit/[exhibit_exhibit_id]/`
+
+
+## Old Files for Reference
+
+@backups/legacy/events_leads_v2/exhibit/[slug]/+page.svelte
+@backups/legacy/events_leads_v2/exhibit/[slug]/leads_manage.svelte
+@backups/legacy/events_leads_v2/exhibit/[slug]/leads_payment.svelte
\ No newline at end of file
diff --git a/documentation/TODO__Agents.md b/documentation/TODO__Agents.md
index 3e78e009..c657cd7e 100644
--- a/documentation/TODO__Agents.md
+++ b/documentation/TODO__Agents.md
@@ -48,7 +48,7 @@ Full audit: `src/routes/events/[event_id]/(leads)/` and `src/lib/ae_events/ae_ev
- Exhibit search/landing (`/leads/`) — SWR, local + API search, sort
- Exhibit detail page — 4-tab layout, sticky header with Add/List toggle, auto-refresh timer
- Tab 1 (Start): sign-in via shared passcode OR licensed user (email + passcode)
-- Tab 2 (Add): QR scan (rapid vs. qualify mode) + manual badge search; duplicate detection on both
+- Tab 2 (Add): QR scan (confirm mode — replaced rapid/qualify) + manual badge search; duplicate/re-enable detection on both
- Tab 3 (List): SWR lead list, licensee filter (All / My Leads), sort options, export button
- Tab 4 (Manage): admin tools, booth profile edit, passcode, license mgmt, custom questions config, app settings (refresh interval, clear IDB/localStorage, reload)
- Lead detail page: view/edit custom question responses, exhibitor notes (TipTap), priority/enable flags
@@ -64,8 +64,12 @@ Full audit: `src/routes/events/[event_id]/(leads)/` and `src/lib/ae_events/ae_ev
Opt-in model: `allow_tracking` must be explicitly `true` on the badge. Also added `allow_tracking`
and `agree_to_tc` to `ae_EventBadge` in `ae_types.ts`.
**Demo note:** ensure test badges have `allow_tracking = true` or no one can be added.
-- [ ] **Payment component** — `ae_comp__exhibit_payment.svelte` is a stub (Stripe placeholder only);
- omit from demo or hide the payment tab via "Show Payment Tab" toggle in Manage settings
+- [x] **Payment component** — `ae_comp__exhibit_payment.svelte` fully implemented (2026-03-27).
+ Reads Stripe config from `$ae_loc.site_cfg_json` (`stripe_publishable_key`, `stripe_btn_1/3/6/10_license`).
+ License tier selector (1/3/6/10 users) with `{#key}` remount pattern for Stripe web component.
+ 3 states: paid confirmation (priority=true), admin setup hint / "contact organizer" (no Stripe config),
+ payment form. `client_reference_id=exhibit_id`. TypeScript declaration in `app.d.ts`.
+ Stripe keys verified visible in `$ae_loc.site_cfg_json` on dev/demo site. Keys need validity check in Stripe dashboard.
- [ ] **End-to-end smoke test** — sign in with shared passcode, scan/search a badge, add a lead,
view detail, add notes/responses, export CSV; verify on mobile (Chrome/Safari PWA)
- [x] **Install prompt** — PWA install nudge implemented (2026-03-16). `pwa_install.svelte.ts`
@@ -81,7 +85,7 @@ Full audit: `src/routes/events/[event_id]/(leads)/` and `src/lib/ae_events/ae_ev
- [x] **Remote deploy script:** `aether_container_env/deploy.sh` — SSH-triggered from workstation via `npm run deploy:remote:test/prod`. Handles git pull (ff-only) + docker build + restart. Tested and working on test env. (2026-03-25)
- [x] **`.env.default` cleanup:** Removed 16 dead variables, added missing `AE_NETWORK_NAME`/`CONTAINER_DOZZLE`/`AE_DOZZLE_PORT`, parameterized all container names (`CONTAINER_MARIADB`, `CONTAINER_PMA`, `CONTAINER_AE_OPS`) with `:-default` fallbacks in compose. ("Dozzle" = log viewer container.) (2026-03-26)
- [ ] **Prod deploy:** Run `npm run deploy:remote:prod` (off-peak). Prerequisites: both repos pushed to Bitbucket ✓; verify `.env.prod` exists in `/srv/apps/prod_aether_app_sveltekit/` on Linode before running.
-- [ ] **Bitbucket → API token migration:** Bitbucket is deprecating app passwords — creation disabled 2025-09-09, existing passwords expire 2026-06-09. Migrate git remotes on workstation + Linode to use API tokens before then. See [Bitbucket API tokens docs](https://support.atlassian.com/bitbucket-cloud/docs/api-tokens/).
+- [x] **Bitbucket → SSH migration:** Switched all three repos (`aether_app_sveltekit`, `aether_container_env`, `aether_api_fastapi`) to SSH remotes (`git@bitbucket.org`) on workstation. App passwords deprecated — SSH unaffected. (2026-03-27)
- [ ] **Branch strategy cleanup:** All environments (test, prod, bak) currently pull from same branches. `deploy.sh` defaults are `ae_app_3x_llm` / `development` — acceptable for now but should establish proper branch separation (e.g. `main`/`master` for prod).
- [ ] **Tier 2 deploy (Gitea webhook):** Push-triggered deploys via Gitea webhook → listener on Linode → `deploy.sh`. Deferred until Gitea usage is more established.
diff --git a/src/app.d.ts b/src/app.d.ts
index 25e7c631..9b354164 100644
--- a/src/app.d.ts
+++ b/src/app.d.ts
@@ -22,3 +22,15 @@ declare global {
// eslint-disable-next-line no-var
var native_app: any;
}
+
+// Stripe Buy Button web component — needed so Svelte templates accept the element without TS errors.
+declare module 'svelte/elements' {
+ interface IntrinsicElements {
+ 'stripe-buy-button': {
+ 'buy-button-id': string;
+ 'publishable-key': string;
+ 'client-reference-id'?: string;
+ [attr: string]: any;
+ };
+ }
+}
diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+page.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+page.svelte
index afec9504..60b6f128 100644
--- a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+page.svelte
+++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+page.svelte
@@ -478,7 +478,7 @@ function toggle_manage_tab() {
Placeholder for Stripe integration.
++ Thank you for your payment. You have purchased + {$lq__exhibit_obj?.license_max ?? 0} user license(s) + for lead retrieval at this event. +
+ {#if ($lq__exhibit_obj?.leads_device_sm_qty ?? 0) > 0} ++ Rental device(s): {$lq__exhibit_obj?.leads_device_sm_qty}. + Pick them up at onsite registration. +
+ {:else} ++ No rental devices. Use your own device(s) with this service. +
+ {/if} +Stripe not configured for this site.
+
+ Add stripe_publishable_key,
+ stripe_btn_1_license,
+ stripe_btn_3_license,
+ stripe_btn_6_license, and
+ stripe_btn_10_license
+ to Site Config JSON to enable payment.
+
+ Online payment is not available at this time. Please contact the event organizer. +
+ {/if} + + {:else} + ++ Each person from your booth who will scan attendee badges needs their own user + license. You can use your own smartphone, tablet, or laptop — rental devices are + not required. +
+One license per team member who will scan badges.
++ Payment processed securely via Stripe. Verify quantities on the checkout + page. +
++ No payment button is configured for this tier. Contact the event organizer. +
+ {/if} +