From cbd4159f72f641caf30822ca430d10a75c7a77db Mon Sep 17 00:00:00 2001 From: Padreug Date: Sun, 6 Sep 2026 19:46:16 +0200 Subject: [PATCH] docs(events): guest checkout, fiat return flow and ticket delivery contract Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo --- .../events-module/guest-checkout.md | 76 +++++++++++++++++++ docs/02-modules/index.md | 7 ++ 2 files changed, 83 insertions(+) create mode 100644 docs/02-modules/events-module/guest-checkout.md diff --git a/docs/02-modules/events-module/guest-checkout.md b/docs/02-modules/events-module/guest-checkout.md new file mode 100644 index 0000000..25c31f7 --- /dev/null +++ b/docs/02-modules/events-module/guest-checkout.md @@ -0,0 +1,76 @@ +# Events — guest checkout, fiat return flow and ticket delivery + +Buyers can purchase **paid** tickets without an LNbits account. This note is +the wire contract between the webapp and the `aiolabs/events` extension +(≥ v1.6.1-aio.8) for that flow. Free tickets still require a login +(events#29: no anonymous free issuance). + +## Identity on `POST /events/api/v1/tickets/{event_id}` + +| Buyer | Body fields | Auth header | +|---|---|---| +| Logged in | `user_id` + `email` (delivery address, prefilled from the account, editable) | `Authorization: Bearer ` | +| Guest | `name` + `email` | none (the endpoint is anonymous; the instance `X-API-KEY` is always sent) | + +Every purchase also sends `frontend_url` — this app's root +(`window.location.origin + import.meta.env.BASE_URL`, no trailing slash; +`src/modules/events/lib/frontendRoot.ts`). The extension only accepts it when +its origin is on the LNbits `LNBITS_CORS_ALLOWED_ORIGINS` allowlist (or the +LNbits base / custom-frontend origin); otherwise the request fails with +`frontend_url origin is not allowed.` and the dialog shows that message. + +`payment_method` is `lightning` or `fiat`. The event's enabled rails come from +the NIP-52 tag `tickets_payment_methods` (`"lightning,fiat"`); when the tag is +absent the legacy rule applies (Lightning always, fiat iff +`tickets_allow_fiat`). See `lib/paymentMethods.ts`. A rail that is off is +rejected by the backend with `Payment method not enabled for this event.` + +## Return from the fiat provider + +The extension composes both Stripe URLs under `frontend_url`: + +| Purpose | URL | +|---|---| +| success | `/events/{event_id}?checkout=success&tickets=` | +| cancel | `/events/{event_id}?checkout=cancelled` | + +Before redirecting (same tab, `window.location.assign`), the purchase dialog +writes `localStorage['events:pending-checkout']` = +`{ eventId, paymentHash, ticketIds, email, ts }` (`lib/pendingCheckout.ts`). +`EventDetailPage` strips the query on arrival, then: + +- `success` → opens `CheckoutReturnDialog`, which polls the anonymous + `GET /events/api/v1/tickets/{ticket_id}` for every id every 2 s (60 s budget, + retry button) until all are `paid`, then renders one QR per ticket, the + "emailed to" note (email from the localStorage record) and a link to the + ticket page. Logged-in buyers also get `useOwnedTickets().refresh()`. +- `cancelled` → info toast, record cleared. Nothing was charged; the + extension purges unpaid rows after 24 h. + +## Public ticket page + +`/events/ticket/:ticketId` (`views/TicketPage.vue`, `requiresAuth: false`) is +where the emailed link lands. It reads the `PublicTicket` (no id / email / +user_id in the payload; the id in the URL is the door credential, exactly as +in the LNbits UI), resolves the event name (Nostr store → scoped relay query +→ public REST, which 410s on sold-out events and is treated as non-fatal) and +renders the QR. + +## QR rendering + +All `ticket://` QRs (purchase success, return dialog, ticket page, My +Tickets) go through `lib/ticketQr.ts`: error-correction level H with the +brand logo (`@brand/logo.png`) centred at ≤ 20 % width on a white pad, so the +in-app image matches the PNG the extension embeds in the ticket email +(`GET /events/api/v1/qr/{ticket_id}`). Canvas failures fall back to the plain +QR. + +## Organizer side + +`CreateEventDialog` publishes `extra.payment_methods` (Lightning / Card +checkboxes; at least one) and keeps `allow_fiat` in sync for older readers. +Card is disabled with a tooltip when the organizer's LNbits user has no fiat +provider (`useFiatProviders().hasAnyProvider`). + +`resendTicketEmail` returns the structured `TicketResendResult` +(`{ ticket, email: {attempted, sent, error}, nostr: {...} }`). diff --git a/docs/02-modules/index.md b/docs/02-modules/index.md index 8b0fda2..a91b2bb 100644 --- a/docs/02-modules/index.md +++ b/docs/02-modules/index.md @@ -182,6 +182,13 @@ interface ModulePlugin { **See:** [[market-module/index|📖 Market Module Documentation]] +### **Events Module** 🎟️ +**Purpose:** Ticketed events (Nostr NIP-52 feed + LNbits events extension) +**Location:** `src/modules/events/` +**Dependencies:** `['base']` + +**See:** [[events-module/guest-checkout|📖 Guest checkout, fiat return flow and ticket delivery]] + ## Module Development ### **Creating a New Module**