Companion to aiolabs/events#31. Free events (price 0 / 100%-off promo)
now come back from POST /tickets/{event_id} as paid=true with the row
ids inline and no payment_request — the backend issued them
already-paid, no invoice to settle.
Previously the composable's `!paymentRequest` guard treated any
invoice-less response as fiat and threw "This event uses fiat
checkout", so free tickets were unbuyable.
- TicketPurchaseInvoice gains `paid` + `ticketIds`; TicketApiService
maps them.
- purchaseTicketForEvent short-circuits on `invoice.paid`: skip the QR /
payment-poll and go straight to the ticket-QR success state. The fiat
error now only fires for an actual fiat (not-paid) response.
- The ticket-QR rendering (refresh owned tickets, one QR per row, toast)
is extracted into a shared finalizePurchasedTickets() used by both the
Lightning-poll path and the free path.
- PurchaseTicketDialog: for free events drop the payment-method selector
and price line, show "Free", and label the CTA "Get ticket".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>