feat: wire FX + deposit invoice into the booking hold (#3) #7

Merged
padreug merged 1 commit from feat/payment-settle into main 2026-07-18 23:22:51 +00:00
Owner

Makes the payment path real so the happy path completes end-to-end (testable on FakeWallet, no relays needed). Closes the code portion of #3 — see the manual-close note below.

What changed

  • _to_sats() — real FX via fiat_amount_as_satoshis (sat/sats pass through). Single conversion point; result becomes the canonical amount_sat, never recomputed downstream (source-of-truth rule).
  • api_request_booking — after the held write, creates a sats-denominated deposit invoice (amount locked at quote time, immune to FX drift before payment), tagged {tag: chatelet, booking_id} so tasks.on_invoice_paid matches the settle. On InvoiceError the hold is released (status=declined) so a dead hold can't block dates.
  • BookingQuote — new response: held booking + bolt11 + payment_hash.

Settlement (awaiting_payment → confirmed, dates hard-blocked, reservation republished) was already present in tasks.py and now fires on real payments.

Manual test (FakeWallet)

  1. Create + publish a room (currency sat to skip FX, or EUR to exercise it).
  2. POST /chatelet/api/v1/bookings → expect BookingQuote with a bolt11, booking awaiting_payment.
  3. Pay the bolt11 → booking flips to confirmed; re-request same dates → 409.

Out of scope (tracked)

  • Concurrency lock on check-then-hold (#4)
  • Guest-facing bolt11 delivery over NIP-17 (#5) — REST returns it directly for now
  • Relay plumbing (#2), nostr-transport RPCs (#1)

Why PR (not direct-to-main)

Money-handling extension (creates invoices, credits wallets) → PR category per workspace rules. Handing off merge to you via the Forgejo UI.

🤖 Generated with Claude Code

Makes the payment path real so the happy path completes end-to-end (testable on FakeWallet, no relays needed). Closes the code portion of #3 — see the manual-close note below. ## What changed - **`_to_sats()`** — real FX via `fiat_amount_as_satoshis` (sat/sats pass through). Single conversion point; result becomes the canonical `amount_sat`, never recomputed downstream (source-of-truth rule). - **`api_request_booking`** — after the `held` write, creates a **sats-denominated** deposit invoice (amount locked at quote time, immune to FX drift before payment), tagged `{tag: chatelet, booking_id}` so `tasks.on_invoice_paid` matches the settle. On `InvoiceError` the hold is released (`status=declined`) so a dead hold can't block dates. - **`BookingQuote`** — new response: held booking + bolt11 + payment_hash. Settlement (`awaiting_payment → confirmed`, dates hard-blocked, reservation republished) was already present in `tasks.py` and now fires on real payments. ## Manual test (FakeWallet) 1. Create + publish a room (currency `sat` to skip FX, or `EUR` to exercise it). 2. `POST /chatelet/api/v1/bookings` → expect `BookingQuote` with a bolt11, booking `awaiting_payment`. 3. Pay the bolt11 → booking flips to `confirmed`; re-request same dates → `409`. ## Out of scope (tracked) - Concurrency lock on check-then-hold (#4) - Guest-facing bolt11 delivery over NIP-17 (#5) — REST returns it directly for now - Relay plumbing (#2), nostr-transport RPCs (#1) ## Why PR (not direct-to-main) Money-handling extension (creates invoices, credits wallets) → PR category per workspace rules. Handing off merge to you via the Forgejo UI. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Booking requests now complete end-to-end over HTTP:

- _to_sats() converts fiat->sats via fiat_amount_as_satoshis (sat/sats
  pass through). This is the single conversion point; the result is the
  canonical amount_sat and is not recomputed downstream.
- api_request_booking creates a sats-denominated deposit invoice (locked
  amount, immune to FX drift before payment), tagged {tag:chatelet,
  booking_id} so tasks.on_invoice_paid matches the settle. On InvoiceError
  the hold is released (status=declined) so dead holds don't block dates.
- New BookingQuote response returns the held booking + bolt11 + hash.

Settlement (tasks.on_invoice_paid: awaiting_payment -> confirmed, dates
hard-blocked, reservation republished) was already in place and now fires
on real payments. Testable on FakeWallet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD
padreug deleted branch feat/payment-settle 2026-07-18 23:22:51 +00:00
Sign in to join this conversation.
No reviewers
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/chatelet!7
No description provided.