diff --git a/__init__.py b/__init__.py
index 6d151fb..b2e1139 100644
--- a/__init__.py
+++ b/__init__.py
@@ -67,10 +67,7 @@ def chatelet_start():
handle_availability,
handle_block_create,
handle_booking_get,
- handle_booking_list_mine,
handle_booking_request,
- handle_operator_get,
- handle_operator_update,
handle_room_create,
handle_room_get,
handle_room_list,
@@ -87,8 +84,6 @@ def chatelet_start():
register_rpc("chatelet_room_publish", handle_room_publish, AUTH_WALLET)
register_rpc("chatelet_block_create", handle_block_create, AUTH_WALLET)
register_rpc("chatelet_room_list_mine", handle_room_list_mine, AUTH_ACCOUNT)
- register_rpc("chatelet_operator_get", handle_operator_get, AUTH_WALLET)
- register_rpc("chatelet_operator_update", handle_operator_update, AUTH_WALLET)
# public (discovery + guest booking)
register_rpc("chatelet_room_list", handle_room_list, AUTH_NONE)
register_rpc("chatelet_room_get", handle_room_get, AUTH_NONE)
@@ -96,7 +91,6 @@ def chatelet_start():
register_rpc("chatelet_availability", handle_availability, AUTH_NONE)
register_rpc("chatelet_booking_request", handle_booking_request, AUTH_NONE)
register_rpc("chatelet_booking_get", handle_booking_get, AUTH_NONE)
- register_rpc("chatelet_booking_list_mine", handle_booking_list_mine, AUTH_NONE)
# tasks.py stamps extra["booking_id"] on settlement (see on_invoice_paid),
# so override the default link_extra_key ("link") to match.
diff --git a/config.json b/config.json
index 731995d..7a9cef8 100644
--- a/config.json
+++ b/config.json
@@ -6,7 +6,7 @@
"short_description": "Nostr-native room rentals (Airbnb-style) for LNbits",
"description": "",
"tile": "/chatelet/static/image/aio.png",
- "min_lnbits_version": "1.4.1",
+ "min_lnbits_version": "1.4.0",
"contributors": [
{
"name": "padreug",
diff --git a/crud.py b/crud.py
index d863e11..765d703 100644
--- a/crud.py
+++ b/crud.py
@@ -19,7 +19,6 @@ from .models import (
ChateletSettings,
CreateBlockData,
CreateRoomData,
- OperatorSettings,
Room,
RoomStatus,
)
@@ -49,30 +48,6 @@ async def update_settings(settings: ChateletSettings) -> ChateletSettings:
return settings
-# ---------------------------------------------------------------------------
-# Operator settings (per LNbits user — multi-tenant)
-# ---------------------------------------------------------------------------
-
-
-async def get_or_create_operator_settings(user_id: str) -> OperatorSettings:
- row = await db.fetchone(
- "SELECT * FROM chatelet.operator_settings WHERE user_id = :uid",
- {"uid": user_id},
- OperatorSettings,
- )
- if row:
- return row
- ops = OperatorSettings(user_id=user_id)
- await db.insert("chatelet.operator_settings", ops)
- return ops
-
-
-async def update_operator_settings(ops: OperatorSettings) -> OperatorSettings:
- ops.updated_at = datetime.now(timezone.utc)
- await db.update("chatelet.operator_settings", ops, "WHERE user_id = :user_id")
- return ops
-
-
# ---------------------------------------------------------------------------
# Rooms
# ---------------------------------------------------------------------------
@@ -139,18 +114,6 @@ async def get_booking_by_payment_hash(payment_hash: str) -> Booking | None:
)
-async def get_bookings_for_guest(guest_pubkey: str, limit: int = 200) -> list[Booking]:
- """A guest's own stays, newest check-in first (idx_bookings_guest_pubkey)."""
- return await db.fetchall(
- """
- SELECT * FROM chatelet.bookings WHERE guest_pubkey = :pk
- ORDER BY check_in DESC LIMIT :limit
- """,
- {"pk": guest_pubkey, "limit": limit},
- Booking,
- )
-
-
async def get_bookings_for_room(room_id: str) -> list[Booking]:
return await db.fetchall(
"SELECT * FROM chatelet.bookings WHERE room_id = :rid",
diff --git a/docs/data-model.md b/docs/data-model.md
index e842fc5..b49b732 100644
--- a/docs/data-model.md
+++ b/docs/data-model.md
@@ -53,19 +53,6 @@ replaces the same addressable event. Holds price (`amount`/`currency`/
published reservation object.
- **`expires_at`** — hold expiry (set while `held`/`awaiting_payment`).
-### `operator_settings` — per LNbits user (multi-tenant, m003)
-
-Every LNbits user may host rooms; what they decide for *all their rooms* lives
-here, keyed by user id and created on first read:
-
-| Field | Meaning |
-|---|---|
-| `accept_fiat` | operator wants card payments. Only *offered* when LNbits core also has a fiat provider for this user (`settings.get_fiat_providers_for_user`) and the room is fiat-priced — chatelet never stores provider credentials (lnbits#67 plugs per-user Stripe creds into that same call) |
-| `checkin_time`, `checkout_time`, `cancellation_policy` | house rules — shown to guests (`house_rules` on the public room view, `checkin_time`/`checkout_time` tags on the kind:30402 listing) and put in the check-in DM |
-
-Rooms resolve their operator via `get_wallet(room.wallet).user`. The old
-house-rule columns on `settings` are kept for old rows but no longer read.
-
### `blocks` — manual owner unavailability
Maintenance, personal use, off-season. Half-open `[start_date, end_date)`.
diff --git a/docs/event-flow.md b/docs/event-flow.md
index 5dc1cbf..8524ab4 100644
--- a/docs/event-flow.md
+++ b/docs/event-flow.md
@@ -25,13 +25,11 @@ flow runs over relays with no HTTP:
| `chatelet_room_create` / `_update` / `_publish` | wallet | operator room CRUD (ownership-checked) |
| `chatelet_block_create` | wallet | operator blocks a range |
| `chatelet_room_list_mine` | account | operator's rooms across their wallets |
-| `chatelet_operator_get` / `_update` | wallet | the caller's per-operator settings (house rules, card acceptance) |
-| `chatelet_room_list` / `_get` | none | public discovery (active rooms, wallet id stripped, owner's `house_rules` + `payment_methods` attached) |
+| `chatelet_room_list` / `_get` | none | public discovery (active rooms, wallet id stripped) |
| `chatelet_room_unavailable` | none | merged occupied/blocked spans over a window — the guest calendar feed (HTTP twin: `GET /api/v1/public/rooms/{id}/unavailable`) |
| `chatelet_availability` | none | is a range free + a quote |
-| `chatelet_booking_request` | none | guest requests a stay (guest id = signed `sender_pubkey`); `payment_method` `lightning` (default) or `fiat` + optional `fiat_provider` / `frontend_url` — card returns `fiat_payment_request` (hosted checkout URL) instead of a bolt11 |
+| `chatelet_booking_request` | none | guest requests a stay (guest id = signed `sender_pubkey`) |
| `chatelet_booking_get` | none | guest reads back their booking (ownership by `sender_pubkey`) |
-| `chatelet_booking_list_mine` | none | the caller's own bookings (`sender_pubkey`; HTTP twin `GET /api/v1/bookings/mine` uses the account's pubkey) |
Guest identity is the `sender_pubkey` the dispatcher lifts off the signed
kind-21000 event — unspoofable, and it means no separate `guest_pubkey` is
diff --git a/frontend.py b/frontend.py
deleted file mode 100644
index c1630f5..0000000
--- a/frontend.py
+++ /dev/null
@@ -1,47 +0,0 @@
-"""Where to send a guest back to after a hosted (Stripe) checkout.
-
-Ported from the events extension. The calling app names itself via
-`frontend_url`; we only honour origins the LNbits instance already trusts
-(the CORS allow-list, its own base URL, the configured custom frontend), and
-fail loud on anything else — a wrong root would strand the guest in the
-wrong app after paying. Transport-agnostic: the HTTP door passes the request
-base URL as fallback, the RPC door has none and falls back to the instance.
-"""
-
-from urllib.parse import urlsplit
-
-from lnbits.settings import settings
-
-
-def origin(url: str | None) -> str | None:
- if not url:
- return None
- parts = urlsplit(url.strip())
- if not parts.scheme or not parts.netloc:
- return None
- return f"{parts.scheme.lower()}://{parts.netloc.lower()}"
-
-
-def allowed_frontend_origins() -> set[str]:
- origins: set[str] = set()
- for candidate in [
- *getattr(settings, "lnbits_cors_allowed_origins", []),
- settings.lnbits_baseurl,
- getattr(settings, "lnbits_custom_frontend_url", None),
- ]:
- o = origin(candidate)
- if o:
- origins.add(o)
- return origins
-
-
-def resolve_frontend_root(
- frontend_url: str | None, fallback_base_url: str | None
-) -> str:
- """Root under which `/chatelet/{room_id}` resolves for the guest."""
- if not frontend_url:
- return (fallback_base_url or settings.lnbits_baseurl or "").rstrip("/")
- o = origin(frontend_url)
- if not o or o not in allowed_frontend_origins():
- raise ValueError("frontend_url origin is not allowed.")
- return frontend_url.rstrip("/")
diff --git a/migrations.py b/migrations.py
index 26fbc9a..4a6cb37 100644
--- a/migrations.py
+++ b/migrations.py
@@ -123,27 +123,3 @@ async def m002_room_checkin_instructions(db):
"ALTER TABLE chatelet.rooms ADD COLUMN checkin_instructions TEXT "
"NOT NULL DEFAULT '';"
)
-
-
-async def m003_operator_settings_and_guest_index(db):
- """Chatelet is multi-tenant: every LNbits user may host rooms. What an
- operator decides for *all their rooms* — house rules and whether they
- take card payments — lives here, keyed by LNbits user id, created lazily.
- The single `chatelet.settings` row keeps only instance-wide knobs; its
- old house-rule columns stay in place but are no longer read.
-
- Also indexes bookings by guest so a guest can list their own stays."""
- await db.execute(f"""
- CREATE TABLE chatelet.operator_settings (
- user_id TEXT PRIMARY KEY,
- accept_fiat BOOLEAN NOT NULL DEFAULT false,
- checkin_time TEXT NOT NULL DEFAULT '15:00',
- checkout_time TEXT NOT NULL DEFAULT '11:00',
- cancellation_policy TEXT NOT NULL DEFAULT '',
- created_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
- updated_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
- );
- """)
- await db.execute(
- "CREATE INDEX chatelet.idx_bookings_guest_pubkey ON bookings (guest_pubkey);"
- )
diff --git a/models.py b/models.py
index 0d49422..346b4e0 100644
--- a/models.py
+++ b/models.py
@@ -20,9 +20,8 @@ Design notes carried into the field definitions:
import json
from datetime import datetime, timezone
from enum import Enum
-from urllib.parse import urlsplit
-from pydantic import BaseModel, Field, validator
+from pydantic import BaseModel, Field
def _now() -> datetime:
@@ -66,28 +65,6 @@ OCCUPYING_STATUSES = {
# ---------------------------------------------------------------------------
-# Per-operator (LNbits user) choices that apply to all of that user's rooms.
-HOUSE_RULE_FIELDS = ("checkin_time", "checkout_time", "cancellation_policy")
-
-
-class UpdateOperatorSettings(BaseModel):
- accept_fiat: bool = False
- checkin_time: str = "15:00"
- checkout_time: str = "11:00"
- cancellation_policy: str = "" # shown to guests + in the check-in DM
-
-
-class OperatorSettings(UpdateOperatorSettings):
- """One row per operator user, created on first read. `accept_fiat` is the
- operator's *wish*; whether card is actually offered also depends on LNbits
- core having a fiat provider for that user (services.payment_methods_for_room)
- — chatelet never holds provider credentials."""
-
- user_id: str
- created_at: datetime = Field(default_factory=_now)
- updated_at: datetime = Field(default_factory=_now)
-
-
class ChateletSettings(BaseModel):
# LNbits account whose Nostr signer publishes listings/receipts on the
# castle's behalf. Resolved via lnbits.core.signers.resolve_signer so
@@ -97,11 +74,9 @@ class ChateletSettings(BaseModel):
relays: list[str] = Field(default_factory=list) # where we publish/subscribe
default_hold_minutes: int = 30 # how long a `held` booking survives unpaid
deposit_percent: int = 100 # 100 = full prepay; <100 = deposit + balance
- # Legacy (pre-m003): house rules are per operator now — see OperatorSettings.
- # Columns kept so old rows load; nothing reads them.
checkin_time: str = "15:00"
checkout_time: str = "11:00"
- cancellation_policy: str = ""
+ cancellation_policy: str = "" # free-form markdown, surfaced in listings/DMs
publish_availability: bool = True # mirror blocked dates to a public NIP-52 calendar
created_at: datetime = Field(default_factory=_now)
updated_at: datetime = Field(default_factory=_now)
@@ -168,27 +143,6 @@ class BookingRequestData(BaseModel):
num_guests: int = 1
guest_contact: str | None = None # optional email/phone/nostr note
message: str | None = None # free-form note to the host
- # Rail the guest wants to pay with. "fiat" needs the room owner to accept
- # card AND LNbits core to have a provider for them (services checks).
- payment_method: str = "lightning"
- fiat_provider: str | None = None # e.g. "stripe"; defaults to the owner's first
- # Where the hosted checkout should send the guest back (the calling app);
- # origin must be one the instance trusts — see frontend.resolve_frontend_root.
- frontend_url: str | None = Field(default=None, max_length=512)
-
- @validator("frontend_url")
- def validate_frontend_url(cls, v): # noqa: N805
- if v is None:
- return None
- v = v.strip()
- if not v:
- return None
- parts = urlsplit(v)
- if parts.scheme not in ("http", "https") or not parts.netloc:
- raise ValueError("frontend_url must be an absolute http(s) URL")
- if parts.query or parts.fragment or ".." in parts.path:
- raise ValueError("frontend_url must not contain a query, fragment or '..'")
- return v.rstrip("/")
class Booking(BaseModel):
@@ -241,24 +195,14 @@ class Block(BaseModel):
# ---------------------------------------------------------------------------
-def public_room_dict(
- room: Room,
- *,
- house_rules: dict | None = None,
- payment_methods: list[str] | None = None,
-) -> dict:
+def public_room_dict(room: Room) -> dict:
"""A Room as public JSON for guests — strips operator-private fields: the
wallet id, and the check-in instructions (address/gate code, delivered only
in the encrypted post-payment DM). Shared by the HTTP and Nostr-RPC guest
- doors so neither can leak them. `house_rules` / `payment_methods` come from
- the owner's OperatorSettings (services.public_room_view resolves them)."""
+ doors so neither can leak them."""
d = json.loads(room.json())
d.pop("wallet", None)
d.pop("checkin_instructions", None)
- if house_rules is not None:
- d["house_rules"] = house_rules
- if payment_methods is not None:
- d["payment_methods"] = payment_methods
return d
@@ -281,16 +225,6 @@ def public_booking_dict(booking: "Booking") -> dict:
return d
-def guest_booking_dict(booking: "Booking") -> dict:
- """A Booking for the guest who owns it (authenticated by pubkey on either
- door): everything public_booking_dict shows plus their own contact and
- guest count; the Lightning/Nostr plumbing stays internal."""
- d = json.loads(booking.json())
- for k in ("payment_hash", "request_event_id", "reservation_event_id"):
- d.pop(k, None)
- return d
-
-
class AvailabilityQuery(BaseModel):
room_id: str
check_in: str # YYYY-MM-DD inclusive
@@ -338,9 +272,5 @@ class BookingQuote(BaseModel):
computes what they owe."""
booking: Booking
- payment_request: str | None # bolt11 — None on the card rail
+ payment_request: str
payment_hash: str
- # Card rail: the provider's hosted checkout URL to send the guest to.
- fiat_payment_request: str | None = None
- fiat_provider: str | None = None
- is_fiat: bool = False
diff --git a/nostr/events.py b/nostr/events.py
index b558f0a..b0c9d8e 100644
--- a/nostr/events.py
+++ b/nostr/events.py
@@ -18,30 +18,15 @@ from .kinds import (
)
-def build_listing_event(
- room: Room,
- *,
- payment_methods: list[str] | None = None,
- house_rules: dict | None = None,
-) -> dict:
+def build_listing_event(room: Room) -> dict:
"""NIP-99 kind:30402 classified listing for a room. Public, signed by
- the operator's identity. `d` == room.id so re-publishing replaces.
-
- `payment_methods` (comma-joined, like events' tickets_payment_methods) and
- the check-in/out times ride as tags so a generic Nostr client can render
- the right pay buttons and house rules without speaking our RPC."""
+ the operator's identity. `d` == room.id so re-publishing replaces."""
tags = [
["d", room.id],
["title", room.title],
["price", str(room.price_amount), room.price_currency, room.price_frequency],
["status", "active"],
]
- if payment_methods:
- tags.append(["payment_methods", ",".join(payment_methods)])
- if house_rules:
- for key in ("checkin_time", "checkout_time"):
- if house_rules.get(key):
- tags.append([key, str(house_rules[key])])
if room.location:
tags.append(["location", room.location])
if room.geohash:
diff --git a/nostr/service.py b/nostr/service.py
index c1c1a51..fc17c75 100644
--- a/nostr/service.py
+++ b/nostr/service.py
@@ -160,17 +160,8 @@ def _checkin_message(booking, room, settings) -> str:
async def publish_listing(room: Room) -> str | None:
- """Publish/refresh a room's NIP-99 kind:30402 listing (public), carrying
- the owner's rails + house rules so the event matches the API view."""
- owner = await services.room_owner_id(room)
- ops = await crud.get_or_create_operator_settings(owner)
- return await _sign_and_publish(
- events.build_listing_event(
- room,
- payment_methods=services.payment_methods_for_room(room, owner, ops),
- house_rules=services.house_rules_dict(ops),
- )
- )
+ """Publish/refresh a room's NIP-99 kind:30402 listing (public)."""
+ return await _sign_and_publish(events.build_listing_event(room))
async def publish_reservation(booking: Booking) -> str | None:
diff --git a/services.py b/services.py
index 2fc9662..ea6e4f9 100644
--- a/services.py
+++ b/services.py
@@ -16,30 +16,21 @@ import asyncio
from collections import defaultdict
from datetime import date, datetime, timedelta, timezone
-from lnbits.core.crud.wallets import get_wallet
-from lnbits.core.models.payments import CreateInvoice
-from lnbits.core.services import create_payment_request
+from lnbits.core.services import create_invoice
from lnbits.exceptions import InvoiceError
from lnbits.helpers import urlsafe_short_hash
-from lnbits.settings import settings as lnbits_settings
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
from . import crud
-from .frontend import resolve_frontend_root
from .models import (
- HOUSE_RULE_FIELDS,
AvailabilityResult,
Booking,
BookingQuote,
BookingRequestData,
BookingStatus,
DateRange,
- OperatorSettings,
- Room,
RoomStatus,
UnavailableRanges,
- guest_booking_dict,
- public_room_dict,
)
# Per-room lock serializing the availability read + the `held` write, so two
@@ -70,97 +61,6 @@ async def to_sats(amount: float, currency: str) -> int:
return await fiat_amount_as_satoshis(amount, currency)
-# ---------------------------------------------------------------------------
-# Operators + the public room view
-# ---------------------------------------------------------------------------
-
-LIGHTNING = "lightning"
-FIAT = "fiat"
-
-
-def is_fiat_currency(currency: str) -> bool:
- return currency.lower() not in ("sat", "sats")
-
-
-def fiat_providers_for_user(user_id: str) -> list[str]:
- """Fiat providers LNbits core will let this user charge with. The one
- place chatelet consults core about card payments (lnbits#67's per-user
- Stripe creds land behind this call); module-level so tests can patch it —
- the pydantic settings object refuses monkeypatched methods."""
- return lnbits_settings.get_fiat_providers_for_user(user_id)
-
-
-async def room_owner_id(room: Room) -> str:
- """The LNbits user who operates a room (rooms belong to wallets)."""
- wallet = await get_wallet(room.wallet)
- if not wallet:
- raise NotFound("Room's wallet not found")
- return wallet.user
-
-
-def payment_methods_for_room(
- room: Room, owner_id: str, ops: OperatorSettings
-) -> list[str]:
- """Rails a guest may pay this room with. Card needs three things: the
- operator opted in, LNbits core has a fiat provider for *that user* (the
- single seam lnbits#67's per-user Stripe creds will plug into — chatelet
- never sees credentials), and a fiat-denominated price (core cannot bill
- a sat amount through a fiat provider)."""
- rails = [LIGHTNING]
- if (
- ops.accept_fiat
- and is_fiat_currency(room.price_currency)
- and fiat_providers_for_user(owner_id)
- ):
- rails.append(FIAT)
- return rails
-
-
-def house_rules_dict(ops: OperatorSettings) -> dict:
- return {k: getattr(ops, k) for k in HOUSE_RULE_FIELDS}
-
-
-async def public_room_view(room: Room) -> dict:
- """public_room_dict + the owner's house rules and rails. Used by both
- guest doors so a room looks the same over HTTP and RPC."""
- owner = await room_owner_id(room)
- ops = await crud.get_or_create_operator_settings(owner)
- return public_room_dict(
- room,
- house_rules=house_rules_dict(ops),
- payment_methods=payment_methods_for_room(room, owner, ops),
- )
-
-
-async def public_room_views(rooms: list[Room]) -> list[dict]:
- """Batch form: one owner/settings lookup per distinct wallet."""
- owners: dict[str, str] = {}
- ops_by_owner: dict[str, OperatorSettings] = {}
- out = []
- for room in rooms:
- if room.wallet not in owners:
- owners[room.wallet] = await room_owner_id(room)
- owner = owners[room.wallet]
- if owner not in ops_by_owner:
- ops_by_owner[owner] = await crud.get_or_create_operator_settings(owner)
- ops = ops_by_owner[owner]
- out.append(
- public_room_dict(
- room,
- house_rules=house_rules_dict(ops),
- payment_methods=payment_methods_for_room(room, owner, ops),
- )
- )
- return out
-
-
-async def list_guest_bookings(guest_pubkey: str) -> list[dict]:
- """The caller's own bookings (identity established by the door: LNbits
- account pubkey over HTTP, signed sender_pubkey over RPC)."""
- rows = await crud.get_bookings_for_guest(guest_pubkey)
- return [guest_booking_dict(b) for b in rows]
-
-
# A guest calendar asks for a year by default; cap the window so a bad client
# can't make us scan and ship an unbounded span.
DEFAULT_CALENDAR_DAYS = 365
@@ -227,35 +127,10 @@ async def get_availability(
)
-async def _resolve_rail(
- room: Room, data: BookingRequestData, base_url: str | None
-) -> tuple[str | None, str]:
- """(fiat provider or None for Lightning, frontend root for the return
- URLs). Providers come from LNbits core for the room *owner* — the seam
- lnbits#67's per-user Stripe creds will plug into."""
- method = (data.payment_method or LIGHTNING).lower()
- if method not in (LIGHTNING, FIAT):
- raise ValueError("Unknown payment method")
- owner = await room_owner_id(room)
- ops = await crud.get_or_create_operator_settings(owner)
- if method not in payment_methods_for_room(room, owner, ops):
- raise ValueError("Payment method not enabled for this room")
- if method == LIGHTNING:
- return None, ""
- providers = fiat_providers_for_user(owner)
- provider = data.fiat_provider or (providers[0] if providers else None)
- if not provider or provider not in providers:
- raise ValueError("No fiat payment provider configured")
- return provider, resolve_frontend_root(data.frontend_url, base_url)
-
-
-async def request_booking(
- data: BookingRequestData, *, base_url: str | None = None
-) -> BookingQuote:
+async def request_booking(data: BookingRequestData) -> BookingQuote:
"""Check-then-hold, then invoice. The `is_available` read + the `held`
write are the lock; TODO(#4) makes that pair atomic against a concurrent
- request. Returns the held booking + what confirms it: a bolt11, or on the
- card rail the provider's hosted-checkout URL."""
+ request. Returns the held booking + the bolt11 that will confirm it."""
room = await crud.get_room(data.room_id)
if not room or room.status != RoomStatus.active:
raise NotFound("Room not available")
@@ -266,10 +141,6 @@ async def request_booking(
if data.num_guests > room.max_guests:
raise ValueError(f"Max {room.max_guests} guests")
- # Rail + provider resolution happens before the hold so a refused rail
- # never leaves a dead hold behind.
- provider, frontend_root = await _resolve_rail(room, data, base_url)
-
# Compute the canonical amount up front (FX call) so the lock below wraps
# only the DB check + insert, never the slow network work.
settings = await crud.get_or_create_settings()
@@ -311,59 +182,30 @@ async def request_booking(
raise Unavailable("Those dates are no longer available")
await crud.create_booking(booking)
- # One invoice call for both rails (core forks on fiat_provider). Lightning
- # is sats-denominated (deposit_sat locked at quote time so FX drift can't
- # change what's owed); card charges the same deposit share of the fiat
- # price in the room's currency — core refuses sat units for fiat, which
- # payment_methods_for_room already rules out. tag+booking_id let
- # tasks.on_invoice_paid match the settlement back to this booking on
- # either rail, since core settles Stripe onto the same invoice queue.
- stay = f"{booking.check_in}→{booking.check_out} ({nights}n)"
- memo = f"Chatelet · {room.title} · {stay}"
- extra: dict = {"tag": "chatelet", "booking_id": booking.id}
- invoice = CreateInvoice(
- out=False, amount=booking.deposit_sat, unit="sat", memo=memo, extra=extra
- )
- if provider:
- back = f"{frontend_root}/chatelet/{room.id}"
- extra["checkout"] = {
- "success_url": f"{back}?checkout=success&booking={booking.id}",
- "cancel_url": f"{back}?checkout=cancelled&booking={booking.id}",
- "customer_email": (
- data.guest_contact
- if data.guest_contact and "@" in data.guest_contact
- else None
- ),
- "line_item_name": f"{room.title} · {stay}",
- "metadata": {"booking_id": booking.id, "room_id": room.id},
- }
- invoice = CreateInvoice(
- out=False,
- amount=round(price_fiat * settings.deposit_percent / 100, 2),
- unit=room.price_currency,
- fiat_provider=provider,
- memo=memo,
- extra=extra,
- )
+ # Sats-denominated (deposit_sat locked at quote time) so FX drift before
+ # payment can't change what's owed. tag+booking_id let
+ # tasks.on_invoice_paid match the settlement back to this booking.
try:
- payment = await create_payment_request(
- wallet_id=room.wallet, invoice_data=invoice
+ payment = await create_invoice(
+ wallet_id=room.wallet,
+ amount=booking.deposit_sat,
+ memo=(
+ f"Chatelet · {room.title} · "
+ f"{booking.check_in}→{booking.check_out} ({nights}n)"
+ ),
+ extra={"tag": "chatelet", "booking_id": booking.id},
)
- except (InvoiceError, ValueError) as exc:
+ except InvoiceError as exc:
booking.status = BookingStatus.declined # dead hold -> free the dates
await crud.update_booking(booking)
- raise BookingError(f"Could not create invoice: {exc}") from exc
+ raise BookingError(f"Could not create invoice: {exc.message}") from exc
booking.payment_hash = payment.payment_hash
booking.status = BookingStatus.awaiting_payment
await crud.update_booking(booking)
- payment_extra = getattr(payment, "extra", None) or {}
return BookingQuote(
booking=booking,
- payment_request=getattr(payment, "bolt11", None) or None,
+ payment_request=payment.bolt11,
payment_hash=payment.payment_hash,
- fiat_payment_request=payment_extra.get("fiat_payment_request"),
- fiat_provider=getattr(payment, "fiat_provider", None) or provider,
- is_fiat=provider is not None,
)
diff --git a/static/js/index.js b/static/js/index.js
index ac32887..e1867de 100644
--- a/static/js/index.js
+++ b/static/js/index.js
@@ -29,9 +29,6 @@ window.app = Vue.createApp({
settings: {},
settingsLoading: false,
- operator: {},
- operatorLoading: false,
-
roomsColumns: [
{name: 'title', label: 'Room', field: 'title', align: 'left'},
{
@@ -240,41 +237,11 @@ window.app = Vue.createApp({
} catch (err) {
this._err(err, 'Could not save settings')
}
- },
-
- // --- per-operator settings (house rules, card acceptance) ---
- async getOperator() {
- this.operatorLoading = true
- try {
- const {data} = await LNbits.api.request('GET', `${API}/operator`, this.adminkey)
- this.operator = data
- } catch (err) {
- this._err(err, 'Could not load your settings')
- } finally {
- this.operatorLoading = false
- }
- },
- async saveOperator() {
- this.operatorLoading = true
- try {
- const {accept_fiat, checkin_time, checkout_time, cancellation_policy} = this.operator
- const {data} = await LNbits.api.request(
- 'PUT', `${API}/operator`, this.adminkey,
- {accept_fiat, checkin_time, checkout_time, cancellation_policy}
- )
- this.operator = data
- Quasar.Notify.create({type: 'positive', message: 'Your settings saved'})
- } catch (err) {
- this._err(err, 'Could not save your settings')
- } finally {
- this.operatorLoading = false
- }
}
},
created() {
this.getRooms()
this.getSettings()
- this.getOperator()
}
})
diff --git a/tasks.py b/tasks.py
index 3529182..3532299 100644
--- a/tasks.py
+++ b/tasks.py
@@ -12,7 +12,7 @@ from lnbits.core.models import Payment
from lnbits.tasks import register_invoice_listener
from loguru import logger
-from . import crud, services
+from . import crud
from .models import BookingStatus
from .nostr import service as nostr
@@ -47,11 +47,9 @@ async def on_invoice_paid(payment: Payment):
# Best-effort: a publish failure must not undo a confirmed, paid booking.
try:
room = await crud.get_room(booking.room_id)
+ settings = await crud.get_or_create_settings()
if room:
- # House rules are the room owner's, not the instance's.
- owner = await services.room_owner_id(room)
- ops = await crud.get_or_create_operator_settings(owner)
- await nostr.send_checkin_dm(booking, room, ops)
+ await nostr.send_checkin_dm(booking, room, settings)
except Exception as exc: # noqa: BLE001
logger.warning(f"chatelet: check-in DM failed for {booking.id} (continuing): {exc}")
diff --git a/templates/chatelet/index.html b/templates/chatelet/index.html
index ede4635..cb4b15f 100644
--- a/templates/chatelet/index.html
+++ b/templates/chatelet/index.html
@@ -171,6 +171,19 @@