feat: background tasks — settle on payment, expire holds
Payment (not any nostr event) is the commit point: the invoice listener promotes held/awaiting_payment bookings to confirmed, hard-blocks the dates, and republishes the encrypted reservation object. expire_holds_loop sweeps lapsed holds every minute so abandoned requests free their dates. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD
This commit is contained in:
parent
d5df9ab662
commit
43cfa5d921
1 changed files with 59 additions and 0 deletions
59
tasks.py
Normal file
59
tasks.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"""Background tasks: settle paid bookings, expire stale holds.
|
||||
|
||||
Payment is what promotes a booking from `held`/`awaiting_payment` to
|
||||
`confirmed` — not any Nostr event. We listen on the LNbits invoice
|
||||
dispatcher (same pattern as lnurlp/spirekeeper), and separately sweep
|
||||
expired holds so abandoned requests free their dates.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from lnbits.core.models import Payment
|
||||
from lnbits.tasks import register_invoice_listener
|
||||
from loguru import logger
|
||||
|
||||
from . import crud
|
||||
from .models import BookingStatus
|
||||
from .nostr import service as nostr
|
||||
|
||||
|
||||
async def wait_for_paid_invoices():
|
||||
invoice_queue: asyncio.Queue = asyncio.Queue()
|
||||
register_invoice_listener(invoice_queue, "ext_chatelet")
|
||||
while True:
|
||||
payment = await invoice_queue.get()
|
||||
await on_invoice_paid(payment)
|
||||
|
||||
|
||||
async def on_invoice_paid(payment: Payment):
|
||||
if not payment.extra or payment.extra.get("tag") != "chatelet":
|
||||
return
|
||||
booking = await crud.get_booking_by_payment_hash(payment.payment_hash)
|
||||
if not booking:
|
||||
logger.warning(f"chatelet: paid invoice with no matching booking: {payment.payment_hash}")
|
||||
return
|
||||
if booking.status not in (BookingStatus.held, BookingStatus.awaiting_payment):
|
||||
return # already settled / cancelled — ignore duplicate settle
|
||||
|
||||
# Payment confirms the booking: hard-block the dates, clear the hold,
|
||||
# publish the updated reservation object + send check-in details.
|
||||
booking.status = BookingStatus.confirmed
|
||||
booking.expires_at = None
|
||||
await crud.update_booking(booking)
|
||||
booking.reservation_event_id = await nostr.publish_reservation(booking)
|
||||
await crud.update_booking(booking)
|
||||
# TODO(checkin): DM check-in details (address, gate code, times) to the
|
||||
# guest via NIP-17 giftwrap once relay plumbing lands.
|
||||
logger.info(f"chatelet: booking {booking.id} confirmed")
|
||||
|
||||
|
||||
async def expire_holds_loop():
|
||||
"""Sweep lapsed holds every minute so abandoned requests free dates."""
|
||||
while True:
|
||||
try:
|
||||
expired = await crud.expire_stale_holds()
|
||||
for b in expired:
|
||||
logger.debug(f"chatelet: hold {b.id} expired, dates freed")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.error(f"chatelet: hold-expiry sweep failed: {exc}")
|
||||
await asyncio.sleep(60)
|
||||
Loading…
Add table
Add a link
Reference in a new issue