Compare commits

..

3 commits

Author SHA1 Message Date
c8602e0fbd fix: every ticket row gets a fresh short-hash id (no payment_hash reuse)
Previous commit reused the LNbits invoice payment_hash as the
first row's id, so a 3-ticket purchase ended up with one 64-hex
id and two short-hash ids — inconsistent and noisy in My Tickets.

Switch every row to urlsafe_short_hash. The shared payment_hash
column is the join key for invoice lookups (poll endpoint, ws
notifier, on_invoice_paid); rows never need to BE the payment
hash, they only need to point at it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 22:45:59 +02:00
d087bf383b feat: multi-ticket purchases as N rows sharing one payment_hash
Replaces the previous "one row, N seats via extra.quantity" model
with proper one-row-per-attendee semantics. Each attendee gets a
unique scannable id; the door PUT /register/{ticket_id} marks
them registered independently — so a buyer can purchase 3 tickets,
hand 2 QRs to friends arriving separately, and each attendee can
enter on their own schedule.

Schema (migrations_fork.py m002):
- ticket.payment_hash: new TEXT column shared across all rows of
  a multi-ticket purchase. Backfilled `payment_hash = id` for
  pre-migration rows (id WAS the payment_hash by invariant).

Wire:
- TicketPaymentRequest grows `ticket_ids: list[str]` so the
  webapp gets every scannable id back in the create response.
- POST /tickets/{event_id}/{payment_hash} polling endpoint now
  reports `ticket_ids` (every row) + keeps `ticket_id` for
  back-compat.
- api_ticket_create loops quantity times; the first row reuses
  payment_hash as id (preserves legacy `id == payment_hash`
  invariant for single-ticket purchases), the rest get
  urlsafe_short_hash() uuids.

Payment flow:
- on_invoice_paid fetches all rows by payment_hash and marks each
  paid via set_ticket_paid, which now increments event.sold by 1
  per row (was N per row via extra.quantity — simpler now). The
  per-event asyncio lock still serializes counter + republish so
  concurrent multi-ticket purchases for the same event don't
  reorder the published Nostr state.
- Each paid row triggers its own send_ticket_notification_in_
  background call — no-op for buyers without nostr_identifier /
  email, useful when the buyer set those on the row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 22:35:56 +02:00
5afc8885cf feat: multi-ticket purchase (quantity 1-10 on a single invoice)
CreateTicket gains a bounded `quantity` field (Field(default=1,
ge=1, le=10)). One ticket row represents N seats on a single
invoice — no schema migration needed; the row's TicketExtra
carries the multiplier.

- api_ticket_create scales the invoice amount by quantity (after
  promo discount applies) and persists `quantity` in
  TicketExtra.quantity.
- Capacity check pre-validates that the requested quantity fits in
  the remaining seats; returns 400 with the actual remaining count
  rather than 410 sold-out so the client can surface a useful
  message.
- set_ticket_paid reads ticket.extra.quantity and adjusts
  event.sold / event.amount_tickets by that amount. Pre-existing
  rows that don't carry quantity default to 1 (TicketExtra schema
  default).
- The per-event asyncio lock added earlier covers this correctly:
  two parallel multi-ticket purchases on the same event serialize
  on counter mutation + republish.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 22:09:23 +02:00

Diff content is not available