feat: guest checkout, Stripe return flow, emailed QR tickets, per-event payment methods (v1.6.1-aio.8) #36

Merged
padreug merged 7 commits from feat/guest-checkout-email into main 2026-09-07 09:27:51 +00:00

7 commits

Author SHA1 Message Date
07124b36ae chore: ignore the data/ dir pytest creates
Some checks failed
lint.yml / chore: ignore the data/ dir pytest creates (pull_request) Failing after 0s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo
2026-09-06 19:57:25 +02:00
bebf7becc8 chore: run tests against the aio lnbits fork (PYTHONPATH), not PyPI lnbits
Some checks failed
lint.yml / chore: run tests against the aio lnbits fork (PYTHONPATH), not PyPI lnbits (pull_request) Failing after 0s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo
2026-09-06 19:56:55 +02:00
a67c6faff3 docs: guest checkout contract, upstream-candidates log; bump 1.6.1-aio.8
Some checks failed
lint.yml / docs: guest checkout contract, upstream-candidates log; bump 1.6.1-aio.8 (pull_request) Failing after 0s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo
2026-09-06 19:53:05 +02:00
c2d9a96239 feat: per-event payment methods (extra.payment_methods)
Organizers pick which rails an event accepts — Lightning, card (fiat) or
both — instead of a bare "allow fiat" toggle. `extra.payment_methods`
uses the field name upstream v2 (lnbits/events#64) introduces so the
eventual rebase merges cleanly; an empty list keeps the legacy rule
(Lightning always, fiat when allow_fiat), and allow_fiat stays the
fiat-currency carrier, kept in lockstep on save. The effective list is
published as the NIP-52 tag `tickets_payment_methods` so clients render
exactly the buttons the purchase endpoint will accept, and the buyer page
defaults to the first accepted rail (a card-only event never submits
"lightning").

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo
2026-09-06 19:53:05 +02:00
92642a1f24 feat: multipart ticket email with embedded QR, structured resend result
Port of upstream v1.6.8's delivery layer, wave-free: `_deliver_ticket_
notifications` sends text + HTML (the HTML embeds the ticket QR PNG from
this extension on the LNbits host — built from lnbits_baseurl on purpose,
since ticket_base_url may point at a separate web app) and returns a
`TicketResendResult` with per-channel attempted/sent/error. The SMTP
session runs via asyncio.to_thread so a slow relay cannot stall the event
loop while a batch of tickets settles. Resend keeps bypassing the
per-event email opt-in (organizer asked explicitly) and is email-only.
Our nsec-DM Nostr path is kept (upstream went NIP-05-only).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo
2026-09-06 19:53:05 +02:00
f77ad28bdd feat: return buyers to the calling app after Stripe, branded QR endpoint
`_resolve_frontend_root` honours `CreateTicket.frontend_url` when its
origin is one of LNBITS_CORS_ALLOWED_ORIGINS, the LNbits base URL or
LNBITS_CUSTOM_FRONTEND_URL (400 otherwise — a silent fallback would send
the buyer to the wrong app), and falls back to request.base_url as before.
Under that root the fiat path now parameterises the hosted checkout via
`extra["checkout"]` (lnbits StripeCheckoutOptions): success_url
`/events/{id}?checkout=success&tickets=<ids>`, cancel_url
`/events/{id}?checkout=cancelled`, customer_email, an event-named line
item and event_id/quantity/ticket_ids metadata. Ticket ids are minted
before the invoice so the success URL can carry them (the payment_hash
only exists afterwards). ticket_base_url on the rows uses the same root,
so the emailed link lands in the webapp when the webapp was the client.

Purchases are gated on `effective_payment_methods(event)` (checked after
the free-ticket short-circuit, which charges nothing on any rail).

New anonymous `GET /events/api/v1/qr/{ticket_id}` returns a PNG of
`ticket://<id>` — port of upstream v1.6.8's endpoint without ticket-image
compositing — built at error-correction H with the instance QR logo
(`lnbits_qr_logo`) pasted in the centre, matching the client-side QRs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo
2026-09-06 19:53:05 +02:00
db708cf0da feat: let a user_id ticket carry an email, accept frontend_url
`CreateTicket` no longer rejects `user_id` together with `name`/`email`
(the exclusion was a fork-only dispatch convenience from dfabcb8; nothing
needed it). `crud.create_ticket` stops blanking name/email when a user_id
is present, so logged-in webapp buyers can have their ticket emailed —
until now `_send_ticket_notification` short-circuited on the empty
address for every app purchase.

New optional `frontend_url` (absolute http(s) root, no query/fragment/..,
trailing slash stripped) lets a buyer-side client name the app the buyer
should be returned to and linked into from the ticket email; the origin
allow-list lives in views_api.

Also folds in the pending black reflow of migrations_fork.py.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo
2026-09-06 19:53:05 +02:00