feat: card rail via LNbits fiat providers (Stripe), per operator
A guest may pay a fiat-priced room by card when its owner has opted in and
LNbits core has a fiat provider for that user — resolved through
settings.get_fiat_providers_for_user(owner), the seam lnbits#67's per-user
Stripe Connect credentials will plug into; chatelet stores no credentials.
Both rails now go through create_payment_request: Lightning unchanged
(sats, deposit_sat), card charges the same deposit share of the fiat price
in the room's currency with extra.checkout parameterising the hosted
Stripe page — success/cancel return to {frontend}/chatelet/{room}?checkout=…
&booking=<id>, customer_email, line item, metadata. frontend_url is
allow-listed against the instance's trusted origins (ported from events)
and resolved before the hold so a refused rail never leaves a dead hold.
Core settles the Stripe webhook onto the same invoice queue, so
tasks.on_invoice_paid confirms card bookings unchanged.
BookingRequestData gains payment_method / fiat_provider / frontend_url;
BookingQuote gains fiat_payment_request / fiat_provider / is_fiat and a
nullable payment_request. RPC chatelet_booking_request passes the fields
through. min_lnbits_version → 1.4.1 (events' floor for these APIs).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
8557ce617e
commit
0dad30b648
11 changed files with 417 additions and 36 deletions
50
tests/test_frontend_root.py
Normal file
50
tests/test_frontend_root.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"""Hosted-checkout return root: only origins the instance trusts."""
|
||||
|
||||
import pytest
|
||||
from lnbits.settings import settings
|
||||
|
||||
from ..frontend import allowed_frontend_origins, resolve_frontend_root
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def lnbits_settings(monkeypatch):
|
||||
monkeypatch.setattr(settings, "lnbits_baseurl", "https://lnbits.example/")
|
||||
monkeypatch.setattr(
|
||||
settings, "lnbits_cors_allowed_origins", ["https://app.example"], raising=False
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
settings,
|
||||
"lnbits_custom_frontend_url",
|
||||
"https://Front.Example/login",
|
||||
raising=False,
|
||||
)
|
||||
|
||||
|
||||
def test_allowlist_collects_every_configured_origin(lnbits_settings):
|
||||
assert allowed_frontend_origins() == {
|
||||
"https://lnbits.example",
|
||||
"https://app.example",
|
||||
"https://front.example",
|
||||
}
|
||||
|
||||
|
||||
def test_absent_frontend_url_falls_back_to_the_request_host(lnbits_settings):
|
||||
root = resolve_frontend_root(None, "https://lnbits.example/")
|
||||
assert root == "https://lnbits.example"
|
||||
|
||||
|
||||
def test_absent_frontend_url_and_no_request_uses_instance_base(lnbits_settings):
|
||||
# The RPC door has no request host.
|
||||
assert resolve_frontend_root(None, None) == "https://lnbits.example"
|
||||
|
||||
|
||||
def test_allowed_origin_is_returned_without_trailing_slash(lnbits_settings):
|
||||
out = resolve_frontend_root(
|
||||
"https://app.example/chatelet/", "https://lnbits.example/"
|
||||
)
|
||||
assert out == "https://app.example/chatelet"
|
||||
|
||||
|
||||
def test_unlisted_origin_is_rejected_loudly(lnbits_settings):
|
||||
with pytest.raises(ValueError, match="frontend_url"):
|
||||
resolve_frontend_root("https://evil.example/x", "https://lnbits.example/")
|
||||
Loading…
Add table
Add a link
Reference in a new issue