Free events (price_per_ticket=0) fail at checkout — LNbits rejects amountless invoices #17

Open
opened 2026-05-23 21:54:44 +00:00 by padreug · 0 comments
Owner

Problem

When an organizer creates an event with price_per_ticket = 0, the buy
flow still routes through create_payment_request to generate a
bolt11 invoice. LNbits doesn't support amountless invoices, so the
request fails and the buyer never gets a ticket.

This affects both single-ticket and multi-ticket purchases (multi
just multiplies 0 × N = 0, so same failure).

Proposed fix

When unit_price * quantity == 0 after promo application, skip the
invoice flow entirely
:

  1. Don't call create_payment_request.
  2. Generate a synthetic payment_hash (e.g. urlsafe_short_hash()
    stays consistent with the multi-ticket id scheme).
  3. Create the N ticket rows with paid = True immediately, shared
    payment_hash, distinct short-hash ids.
  4. Inline-run the set_ticket_paid counter update + Nostr republish
    for each row (or batch them through the same per-event lock).
  5. Send notifications (send_ticket_notification_in_background) the
    same way the invoice-paid path does.
  6. Return TicketPaymentRequest with payment_request = None,
    is_fiat = False, and the populated ticket_ids so the webapp
    knows it's a paid-no-payment ticket and jumps straight to the
    success screen.

Companion webapp work (aiolabs/webapp, app:activities)

The buy dialog should detect price_per_ticket * quantity == 0 and:

  • Relabel the CTA "Get free ticket" / "Get N free tickets".
  • After backend returns the ticket_ids without a payment_request,
    skip the invoice-display screen and go directly to the success
    modal.

Out of scope

  • Free-and-fiat events (allow_fiat=true, price=0) — meaningless;
    same skip-invoice path applies. No special handling needed.
  • Promo codes that bring price to 0 — same code path; the promo
    discount is applied before the unit_price * quantity == 0 check
    so this works automatically.

Test plan

  • Create event with price_per_ticket = 0. Buy 1 → ticket lands
    paid in My Tickets, no invoice, no wallet charge.
  • Buy 5 free → 5 rows, all paid, shared synthetic payment_hash.
    event.sold += 5, amount_tickets -= 5. Nostr event republished
    with new counters.
  • Event with price=100, promo code -100% → same flow, treats as
    free.
  • Capacity check still rejects overbooking on free events.
## Problem When an organizer creates an event with `price_per_ticket = 0`, the buy flow still routes through `create_payment_request` to generate a bolt11 invoice. LNbits doesn't support amountless invoices, so the request fails and the buyer never gets a ticket. This affects both single-ticket and multi-ticket purchases (multi just multiplies 0 × N = 0, so same failure). ## Proposed fix When `unit_price * quantity == 0` after promo application, **skip the invoice flow entirely**: 1. Don't call `create_payment_request`. 2. Generate a synthetic `payment_hash` (e.g. `urlsafe_short_hash()` — stays consistent with the multi-ticket id scheme). 3. Create the N ticket rows with `paid = True` immediately, shared `payment_hash`, distinct short-hash ids. 4. Inline-run the `set_ticket_paid` counter update + Nostr republish for each row (or batch them through the same per-event lock). 5. Send notifications (`send_ticket_notification_in_background`) the same way the invoice-paid path does. 6. Return `TicketPaymentRequest` with `payment_request = None`, `is_fiat = False`, and the populated `ticket_ids` so the webapp knows it's a paid-no-payment ticket and jumps straight to the success screen. ## Companion webapp work (`aiolabs/webapp`, `app:activities`) The buy dialog should detect `price_per_ticket * quantity == 0` and: - Relabel the CTA "Get free ticket" / "Get N free tickets". - After backend returns the ticket_ids without a `payment_request`, skip the invoice-display screen and go directly to the success modal. ## Out of scope - Free-and-fiat events (allow_fiat=true, price=0) — meaningless; same skip-invoice path applies. No special handling needed. - Promo codes that bring price to 0 — same code path; the promo discount is applied before the `unit_price * quantity == 0` check so this works automatically. ## Test plan - [ ] Create event with `price_per_ticket = 0`. Buy 1 → ticket lands paid in My Tickets, no invoice, no wallet charge. - [ ] Buy 5 free → 5 rows, all paid, shared synthetic payment_hash. `event.sold += 5`, `amount_tickets -= 5`. Nostr event republished with new counters. - [ ] Event with price=100, promo code -100% → same flow, treats as free. - [ ] Capacity check still rejects overbooking on free events.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/events#17
No description provided.