feat: republish endpoints + polling + multi-ticket via N-rows model #16

Merged
padreug merged 7 commits from tickets-nostr-sync into main 2026-05-23 21:11:38 +00:00
Showing only changes of commit 902bafe7f2 - Show all commits

feat: POST /tickets/{event_id}/{payment_hash} polling endpoint

The webapp's useTicketPurchase polls this every 2s after firing
Pay with Wallet (or after presenting the QR) to confirm payment
before advancing to the ticket-QR success state. Without this
endpoint the post-payment poll loop returns 404 indefinitely and
the buyer never sees their ticket land — even though set_ticket_paid
fired on the invoice listener and the row is correctly marked paid
in the DB.

Returns {paid: bool, ticket_id?: str}. A missing or cross-event
ticket returns paid: false rather than 404 so the poll loop doesn't
need to special-case the not-yet-created race.

The WebSocket at /tickets/ws/{payment_hash} is more efficient for
push notifications — this POST is the fallback for clients that
can't open a relay-side socket.

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

View file

@ -623,6 +623,29 @@ async def api_ticket_create(
)
@tickets_api_router.post("/{event_id}/{payment_hash}")
async def api_ticket_payment_status(event_id: str, payment_hash: str) -> dict:
"""Poll-style payment confirmation for a pending ticket.
The webapp's `useTicketPurchase` polls this every 2s after firing
`Pay with Wallet` (or after presenting the QR for an external
wallet) until `paid: true` comes back, then advances to the
ticket-QR success state. The companion WebSocket at
`/tickets/ws/{payment_hash}` is more efficient for pushes this
endpoint is the fallback for clients that can't open a relay-side
socket.
Returns `{paid: bool, ticket_id?: str}` so the client can hand off
to the ticket-detail flow without an extra GET. A missing /
cross-event ticket returns `paid: false` rather than 404 so the
poll loop doesn't have to special-case the not-yet-created race.
"""
ticket = await get_ticket(payment_hash)
if not ticket or ticket.event != event_id:
return {"paid": False}
return {"paid": ticket.paid, "ticket_id": ticket.id}
@tickets_api_router.websocket("/ws/{payment_hash}")
async def websocket_endpoint(payment_hash: str, websocket: WebSocket) -> None:
await websocket.accept()