Compare commits

..
Author SHA1 Message Date
Vlad Stan a963b61cc8 fix: lint 2026-05-22 11:52:32 +03:00
Arc d66e22a83e Admin only toggle + remove annoying password browser thing 2026-05-21 14:18:28 +01:00
Arc 882d861f71 coming soon for sbscriptions 2026-05-21 13:56:09 +01:00
Vlad Stan 186aefad22 fix: api test 2026-05-21 14:21:49 +03:00
Vlad Stan 7badf98bb7 fix: unit tests 2026-05-21 14:12:43 +03:00
Vlad Stan 2ee098ddb1 fix: subscription_request_id 2026-05-21 14:12:43 +03:00
Vlad Stan 3723b6510d refactor: better ID check 2026-05-21 14:12:43 +03:00
Vlad Stan 03161f8e9b feat: handle subscriptions 2026-05-21 14:12:43 +03:00
Vlad Stan 5b7f9fa255 fix: remove customer_id 2026-05-21 14:12:43 +03:00
Vlad Stan 0c74adc494 feat: find customer by email 2026-05-21 14:12:43 +03:00
Vlad Stan ea6965905e fix: find customer by email 2026-05-21 14:12:43 +03:00
Vlad Stan ea8228fc84 feat: create customer id from email if missing 2026-05-21 14:12:43 +03:00
Vlad Stan 49adec6488 chore: bundle 2026-05-21 14:12:43 +03:00
ArcandVlad Stan c122bb45f4 Do the separator 🎵 2026-05-21 14:12:43 +03:00
751bd42169 feat: Adds revolut checkout and subscriptions (#3968)
Co-authored-by: alan <alan@lnbits.com>
2026-05-21 14:12:43 +03:00
Vlad Stan c4ee3a7c6b feat: introduce external_id 2026-05-21 14:12:43 +03:00
Vlad Stan 9a4e55f469 refactor: move private method to the bottom 2026-05-21 14:12:43 +03:00
Vlad Stan f45736e70e refactor: extract method 2026-05-21 14:12:43 +03:00
Vlad Stan ef39d02bd2 fix: simplify expected event types 2026-05-21 14:12:43 +03:00
Vlad Stan d66c7f7992 fix: plan 2026-05-21 14:12:43 +03:00
Vlad Stan 2abedccbc8 feat: first subscription 2026-05-21 14:12:43 +03:00
Vlad Stan cc498a7969 fix: webhook url for signature check 2026-05-21 14:12:43 +03:00
Vlad Stan cd57bf66fe fix: lint 2026-05-21 14:12:43 +03:00
Vlad Stan d921af3476 feat: basic square integration 2026-05-21 14:12:43 +03:00
27 changed files with 140 additions and 196 deletions
-2
View File
@@ -119,8 +119,6 @@ LNBITS_SITE_TAGLINE="Open Source Lightning Payments Platform"
LNBITS_SITE_DESCRIPTION="The world's most powerful suite of bitcoin tools. Run for yourself, for others, or as part of a stack." LNBITS_SITE_DESCRIPTION="The world's most powerful suite of bitcoin tools. Run for yourself, for others, or as part of a stack."
# Choose from bitcoin, mint, flamingo, freedom, salvador, autumn, monochrome, classic, cyber # Choose from bitcoin, mint, flamingo, freedom, salvador, autumn, monochrome, classic, cyber
LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador, cyber" LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador, cyber"
# Toggle the background styling on burger menus / drawers
# LNBITS_DEFAULT_BURGER_MENU_BACKGROUND=true
# LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg" # LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg"
###################################### ######################################
+4
View File
@@ -133,6 +133,10 @@ async def create_fiat_invoice(
raise ValueError( raise ValueError(
f"Fiat provider '{fiat_provider_name}' is not enabled.", f"Fiat provider '{fiat_provider_name}' is not enabled.",
) )
if settings.fiat_providers_admin_only:
wallet = await get_wallet(wallet_id, conn=conn)
if not wallet or not settings.is_admin_user(wallet.user):
raise ValueError("Fiat providers are available to admins only.")
if invoice_data.unit == "sat": if invoice_data.unit == "sat":
raise ValueError("Fiat provider cannot be used with satoshis.") raise ValueError("Fiat provider cannot be used with satoshis.")
+3 -7
View File
@@ -20,7 +20,7 @@ from lnbits.decorators import (
check_first_install, check_first_install,
check_user_exists, check_user_exists,
) )
from lnbits.helpers import check_callback_url, extension_id_from_path, template_renderer from lnbits.helpers import check_callback_url, template_renderer
from lnbits.settings import settings from lnbits.settings import settings
from ..crud import get_user from ..crud import get_user
@@ -198,9 +198,7 @@ admin_ui_checks = [Depends(check_admin), Depends(check_admin_ui)]
async def index( async def index(
request: Request, user: User = Depends(check_user_exists) request: Request, user: User = Depends(check_user_exists)
) -> HTMLResponse: ) -> HTMLResponse:
return template_renderer( return template_renderer().TemplateResponse(
extension_id=extension_id_from_path(request.url.path)
).TemplateResponse(
request, request,
"base.html", "base.html",
{ {
@@ -213,9 +211,7 @@ async def index(
@generic_router.get("/node/public") @generic_router.get("/node/public")
@generic_router.get("/first_install", dependencies=[Depends(check_first_install)]) @generic_router.get("/first_install", dependencies=[Depends(check_first_install)])
async def index_public(request: Request) -> HTMLResponse: async def index_public(request: Request) -> HTMLResponse:
return template_renderer( return template_renderer().TemplateResponse(request, "base.html", {"public": True})
extension_id=extension_id_from_path(request.url.path)
).TemplateResponse(request, "base.html", {"public": True})
@generic_router.get("/uuidv4/{hex_value}") @generic_router.get("/uuidv4/{hex_value}")
+1 -47
View File
@@ -52,45 +52,7 @@ def static_url_for(static: str, path: str) -> str:
return f"/{static}/{path}?v={settings.server_startup_time}" return f"/{static}/{path}?v={settings.server_startup_time}"
def extension_id_from_path(path: str) -> str | None: def template_renderer(additional_folders: list | None = None) -> Jinja2Templates:
parts = [part for part in path.split("/") if part]
if not parts:
return None
if len(parts) >= 3 and parts[0] == "upgrades":
return parts[2]
ext_id = parts[0]
ext_i18n_dir = Path(
settings.lnbits_extensions_path, "extensions", ext_id, "static", "i18n"
)
if ext_i18n_dir.is_dir():
return ext_id
return None
def extension_i18n_urls(extension_id: str | None) -> list[str]:
if not extension_id:
return []
i18n_dir = Path(
settings.lnbits_extensions_path, "extensions", extension_id, "static", "i18n"
)
if not i18n_dir.is_dir():
return []
files = [file.name for file in i18n_dir.glob("*.js") if file.is_file()]
return [
static_url_for(f"{extension_id}/static", f"i18n/{filename}")
for filename in sorted(files, key=lambda name: (name != "en.js", name))
]
def template_renderer(
additional_folders: list | None = None,
extension_id: str | None = None,
) -> Jinja2Templates:
folders = [ folders = [
"lnbits/templates", "lnbits/templates",
settings.extension_builder_working_dir_path.as_posix(), settings.extension_builder_working_dir_path.as_posix(),
@@ -124,14 +86,6 @@ def template_renderer(
t.env.globals["INCLUDED_CSS"] = vendor_files["css"] t.env.globals["INCLUDED_CSS"] = vendor_files["css"]
t.env.globals["INCLUDED_COMPONENTS"] = vendor_files["components"] t.env.globals["INCLUDED_COMPONENTS"] = vendor_files["components"]
if not extension_id and additional_folders:
for folder in additional_folders:
parts = Path(folder).parts
if parts and parts[-1] == "templates" and len(parts) >= 2:
extension_id = parts[-2]
break
t.env.globals["INCLUDED_EXTENSION_I18N"] = extension_i18n_urls(extension_id)
# backwards compatibility for extensions (tpos) # backwards compatibility for extensions (tpos)
t.env.globals["LNBITS_DENOMINATION"] = settings.lnbits_denomination t.env.globals["LNBITS_DENOMINATION"] = settings.lnbits_denomination
+11 -3
View File
@@ -300,7 +300,6 @@ class ThemesSettings(LNbitsSettings):
lnbits_default_card_rounded: bool = Field(default=True) lnbits_default_card_rounded: bool = Field(default=True)
lnbits_default_card_gradient: bool = Field(default=True) lnbits_default_card_gradient: bool = Field(default=True)
lnbits_default_card_shadow: bool = Field(default=False) lnbits_default_card_shadow: bool = Field(default=False)
lnbits_default_burger_menu_background: bool = Field(default=True)
class OpsSettings(LNbitsSettings): class OpsSettings(LNbitsSettings):
@@ -771,6 +770,8 @@ class FiatProvidersSettings(
SquareFiatProvider, SquareFiatProvider,
RevolutFiatProvider, RevolutFiatProvider,
): ):
fiat_providers_admin_only: bool = Field(default=True)
def is_fiat_provider_enabled(self, provider: str | None) -> bool: def is_fiat_provider_enabled(self, provider: str | None) -> bool:
""" """
Checks if a specific fiat provider is enabled. Checks if a specific fiat provider is enabled.
@@ -791,6 +792,15 @@ class FiatProvidersSettings(
""" """
Returns a list of fiat payment methods allowed for the user. Returns a list of fiat payment methods allowed for the user.
""" """
if self.fiat_providers_admin_only:
if not self.is_admin_user(user_id):
return []
return [
provider
for provider in ["stripe", "paypal", "square", "revolut"]
if self.is_fiat_provider_enabled(provider)
]
allowed_providers = [] allowed_providers = []
if self.stripe_enabled and ( if self.stripe_enabled and (
not self.stripe_limits.allowed_users not self.stripe_limits.allowed_users
@@ -1282,7 +1292,6 @@ class PublicSettings(BaseModel):
default_card_rounded: bool = Field(alias="defaultCardRounded") default_card_rounded: bool = Field(alias="defaultCardRounded")
default_card_gradient: bool = Field(alias="defaultCardGradient") default_card_gradient: bool = Field(alias="defaultCardGradient")
default_card_shadow: bool = Field(alias="defaultCardShadow") default_card_shadow: bool = Field(alias="defaultCardShadow")
default_burger_menu_background: bool = Field(alias="defaultBurgerMenuBackground")
denomination: str | None = Field() denomination: str | None = Field()
extensions: list[str] = Field() extensions: list[str] = Field()
allowed_currencies: list[str] = Field(alias="allowedCurrencies") allowed_currencies: list[str] = Field(alias="allowedCurrencies")
@@ -1346,7 +1355,6 @@ class PublicSettings(BaseModel):
defaultCardRounded=settings.lnbits_default_card_rounded, defaultCardRounded=settings.lnbits_default_card_rounded,
defaultCardGradient=settings.lnbits_default_card_gradient, defaultCardGradient=settings.lnbits_default_card_gradient,
defaultCardShadow=settings.lnbits_default_card_shadow, defaultCardShadow=settings.lnbits_default_card_shadow,
defaultBurgerMenuBackground=settings.lnbits_default_burger_menu_background,
denomination=settings.lnbits_denomination, denomination=settings.lnbits_denomination,
extensions=list(settings.lnbits_installed_extensions_ids), extensions=list(settings.lnbits_installed_extensions_ids),
allowedCurrencies=settings.lnbits_allowed_currencies, allowedCurrencies=settings.lnbits_allowed_currencies,
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
-7
View File
@@ -395,13 +395,6 @@ body.card-shadow.body--dark .q-card:not(.q-dialog .q-card, .lnbits__dialog-card,
filter: drop-shadow(0 12px 28px rgba(0, 0, 0, 0.45)); filter: drop-shadow(0 12px 28px rgba(0, 0, 0, 0.45));
} }
body.no-burger-background .q-drawer {
background-color: transparent !important;
background-image: none !important;
backdrop-filter: none !important;
box-shadow: none !important;
}
:root { :root {
--size: 100px; --size: 100px;
--gap: 25px; --gap: 25px;
-2
View File
@@ -490,8 +490,6 @@ window.localisation.en = {
toggle_card_gradient: 'Toggle gradient on cards', toggle_card_gradient: 'Toggle gradient on cards',
card_shadow: 'Card Shadow', card_shadow: 'Card Shadow',
toggle_card_shadow: 'Toggle shadow on cards', toggle_card_shadow: 'Toggle shadow on cards',
burger_menu_background: 'Burger Menu Background',
toggle_burger_menu_background: 'Toggle burger menu background',
language: 'Language', language: 'Language',
assets: 'Assets', assets: 'Assets',
max_asset_size_mb: 'Max Asset Size (MB)', max_asset_size_mb: 'Max Asset Size (MB)',
+1 -3
View File
@@ -452,9 +452,7 @@ window.app.component('username-password', {
confirmationMethod: 'code', confirmationMethod: 'code',
confirmationEmail: '', confirmationEmail: '',
confirmationCode: this.invitationCode || '', confirmationCode: this.invitationCode || '',
showConfirmationCode: false, showConfirmationCode: false
showPwd: false,
showPwdRepeat: false
} }
}, },
methods: { methods: {
@@ -12,6 +12,21 @@ window.app.component('lnbits-admin-fiat-providers', {
} }
}, },
computed: { computed: {
fiatProvidersAllUsers: {
get() {
return this.formData?.fiat_providers_admin_only === false
},
set(value) {
this.formData.fiat_providers_admin_only = !value
this.formData.touch = null
}
},
fiatProviderAccessLabel() {
return this.fiatProvidersAllUsers ? 'All users' : 'Admins only'
},
secretInputStyle() {
return this.hideInputToggle ? {'-webkit-text-security': 'disc'} : {}
},
stripeWebhookUrl() { stripeWebhookUrl() {
return ( return (
this.formData?.stripe_payment_webhook_url || this.formData?.stripe_payment_webhook_url ||
@@ -69,14 +69,6 @@ window.app.component('lnbits-theme', {
document.body.classList.remove('card-shadow') document.body.classList.remove('card-shadow')
} }
}, },
'g.burgerMenuChoice'(val) {
this.$q.localStorage.set('lnbits.burgerMenu', val)
if (val === true) {
document.body.classList.remove('no-burger-background')
} else {
document.body.classList.add('no-burger-background')
}
},
'g.mobileSimple'(val) { 'g.mobileSimple'(val) {
this.$q.localStorage.set('lnbits.mobileSimple', val) this.$q.localStorage.set('lnbits.mobileSimple', val)
if (val === true) { if (val === true) {
@@ -158,9 +150,6 @@ window.app.component('lnbits-theme', {
if (this.g.cardShadowChoice === true) { if (this.g.cardShadowChoice === true) {
document.body.classList.add('card-shadow') document.body.classList.add('card-shadow')
} }
if (this.g.burgerMenuChoice !== true) {
document.body.classList.add('no-burger-background')
}
if (this.g.bgimageChoice !== '') { if (this.g.bgimageChoice !== '') {
document.body.classList.add('bg-image') document.body.classList.add('bg-image')
document.body.style.setProperty( document.body.style.setProperty(
-4
View File
@@ -29,10 +29,6 @@ window.g = Vue.reactive({
SETTINGS.defaultCardGradient SETTINGS.defaultCardGradient
), ),
cardShadowChoice: localStore('lnbits.cardShadow', SETTINGS.defaultCardShadow), cardShadowChoice: localStore('lnbits.cardShadow', SETTINGS.defaultCardShadow),
burgerMenuChoice: localStore(
'lnbits.burgerMenu',
SETTINGS.defaultBurgerMenuBackground
),
reactionChoice: localStore('lnbits.reactions', SETTINGS.defaultReaction), reactionChoice: localStore('lnbits.reactions', SETTINGS.defaultReaction),
bgimageChoice: localStore( bgimageChoice: localStore(
'lnbits.backgroundImage', 'lnbits.backgroundImage',
+1 -2
View File
@@ -732,8 +732,7 @@ window.PageAccount = {
darkChoice: this.g.settings.defaultDark, darkChoice: this.g.settings.defaultDark,
cardRoundedChoice: this.g.settings.defaultCardRounded, cardRoundedChoice: this.g.settings.defaultCardRounded,
cardGradientChoice: this.g.settings.defaultCardGradient, cardGradientChoice: this.g.settings.defaultCardGradient,
cardShadowChoice: this.g.settings.defaultCardShadow, cardShadowChoice: this.g.settings.defaultCardShadow
burgerMenuChoice: this.g.settings.defaultBurgerMenuBackground
} }
this.siteCustomisationChanged(defaults) this.siteCustomisationChanged(defaults)
} }
-9
View File
@@ -70,12 +70,3 @@ body.card-shadow.body--dark {
filter: drop-shadow(0 12px 28px rgba(0, 0, 0, 0.45)); filter: drop-shadow(0 12px 28px rgba(0, 0, 0, 0.45));
} }
} }
body.no-burger-background {
.q-drawer {
background-color: transparent !important;
background-image: none !important;
backdrop-filter: none !important;
box-shadow: none !important;
}
}
+1 -3
View File
@@ -90,9 +90,7 @@
window.g.isPublicPage = false window.g.isPublicPage = false
{% endif %} {% endif %}
</script> </script>
{% endif %} {% for url in INCLUDED_EXTENSION_I18N %} {% endif %}
<script src="{{ url }}"></script>
{% endfor %}
<!-- app init --> <!-- app init -->
<script> <script>
window.app = Vue.createApp({ window.app = Vue.createApp({
+5 -35
View File
@@ -774,13 +774,7 @@ include('components/lnbits-error.vue') %}
v-model="password" v-model="password"
name="password" name="password"
:label="$t('password') + ' *'" :label="$t('password') + ' *'"
:type="showPwd ? 'text' : 'password'" type="password"
><template v-slot:append>
<q-icon
:name="showPwd ? 'visibility' : 'visibility_off'"
class="cursor-pointer"
@click="showPwd = !showPwd"
/> </template
></q-input> ></q-input>
<div class="row justify-end"> <div class="row justify-end">
<q-btn <q-btn
@@ -809,28 +803,16 @@ include('components/lnbits-error.vue') %}
filled filled
v-model="password" v-model="password"
:label="$t('password') + ' *'" :label="$t('password') + ' *'"
:type="showPwd ? 'text' : 'password'" type="password"
:rules="[val => !val || val.length >= 8 || $t('invalid_password')]" :rules="[val => !val || val.length >= 8 || $t('invalid_password')]"
><template v-slot:append>
<q-icon
:name="showPwd ? 'visibility' : 'visibility_off'"
class="cursor-pointer"
@click="showPwd = !showPwd"
/> </template
></q-input> ></q-input>
<q-input <q-input
dense dense
filled filled
v-model="passwordRepeat" v-model="passwordRepeat"
:label="$t('password_repeat') + ' *'" :label="$t('password_repeat') + ' *'"
:type="showPwdRepeat ? 'text' : 'password'" type="password"
:rules="[val => !val || val.length >= 8 || $t('invalid_password')]" :rules="[val => !val || val.length >= 8 || $t('invalid_password')]"
><template v-slot:append>
<q-icon
:name="showPwdRepeat ? 'visibility' : 'visibility_off'"
class="cursor-pointer"
@click="showPwdRepeat = !showPwdRepeat"
/> </template
></q-input> ></q-input>
<div <div
v-if="confirmationMethodsCount > 1" v-if="confirmationMethodsCount > 1"
@@ -943,28 +925,16 @@ include('components/lnbits-error.vue') %}
filled filled
v-model="password" v-model="password"
:label="$t('password') + ' *'" :label="$t('password') + ' *'"
:type="showPwd ? 'text' : 'password'" type="password"
:rules="[val => !val || val.length >= 8 || $t('invalid_password')]" :rules="[val => !val || val.length >= 8 || $t('invalid_password')]"
><template v-slot:append>
<q-icon
:name="showPwd ? 'visibility' : 'visibility_off'"
class="cursor-pointer"
@click="showPwd = !showPwd"
/> </template
></q-input> ></q-input>
<q-input <q-input
dense dense
filled filled
v-model="passwordRepeat" v-model="passwordRepeat"
:label="$t('password_repeat') + ' *'" :label="$t('password_repeat') + ' *'"
:type="showPwdRepeat ? 'text' : 'password'" type="password"
:rules="[val => !val || val.length >= 8 || $t('invalid_password')]" :rules="[val => !val || val.length >= 8 || $t('invalid_password')]"
><template v-slot:append>
<q-icon
:name="showPwdRepeat ? 'visibility' : 'visibility_off'"
class="cursor-pointer"
@click="showPwdRepeat = !showPwdRepeat"
/> </template
></q-input> ></q-input>
<div class="row justify-end"> <div class="row justify-end">
<q-btn <q-btn
@@ -1,5 +1,5 @@
<template id="lnbits-admin-fiat-providers"> <template id="lnbits-admin-fiat-providers">
<h6 class="q-my-none q-mb-sm"> <h6 class="q-my-none q-mb-sm row items-center q-gutter-sm">
<span v-text="$t('fiat_providers')"></span> <span v-text="$t('fiat_providers')"></span>
<q-btn <q-btn
round round
@@ -7,6 +7,18 @@
@click="hideInputToggle = !hideInputToggle" @click="hideInputToggle = !hideInputToggle"
:icon="hideInputToggle ? 'visibility_off' : 'visibility'" :icon="hideInputToggle ? 'visibility_off' : 'visibility'"
></q-btn> ></q-btn>
<q-toggle
dense
size="sm"
color="warning"
v-model="fiatProvidersAllUsers"
:label="fiatProviderAccessLabel"
>
<q-tooltip>
If enabled for all users, your users may pass a memo that suspends your
account with your fiat providers
</q-tooltip>
</q-toggle>
</h6> </h6>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
@@ -46,7 +58,11 @@
<q-input <q-input
filled filled
class="q-mt-md" class="q-mt-md"
:type="hideInputToggle ? 'password' : 'text'" type="text"
:input-style="secretInputStyle"
autocomplete="off"
autocapitalize="off"
spellcheck="false"
v-model="formData.stripe_api_secret_key" v-model="formData.stripe_api_secret_key"
:label="$t('secret_key')" :label="$t('secret_key')"
></q-input> ></q-input>
@@ -108,7 +124,11 @@
<q-input <q-input
filled filled
class="q-mt-md" class="q-mt-md"
:type="hideInputToggle ? 'password' : 'text'" type="text"
:input-style="secretInputStyle"
autocomplete="off"
autocapitalize="off"
spellcheck="false"
v-model="formData.stripe_webhook_signing_secret" v-model="formData.stripe_webhook_signing_secret"
:label="$t('signing_secret')" :label="$t('signing_secret')"
:hint="$t('signing_secret_hint')" :hint="$t('signing_secret_hint')"
@@ -325,14 +345,22 @@
<q-input <q-input
filled filled
class="q-mt-md" class="q-mt-md"
:type="hideInputToggle ? 'password' : 'text'" type="text"
:input-style="secretInputStyle"
autocomplete="off"
autocapitalize="off"
spellcheck="false"
v-model="formData.paypal_client_id" v-model="formData.paypal_client_id"
:label="$t('client_id')" :label="$t('client_id')"
></q-input> ></q-input>
<q-input <q-input
filled filled
class="q-mt-md" class="q-mt-md"
:type="hideInputToggle ? 'password' : 'text'" type="text"
:input-style="secretInputStyle"
autocomplete="off"
autocapitalize="off"
spellcheck="false"
v-model="formData.paypal_client_secret" v-model="formData.paypal_client_secret"
:label="$t('secret_key')" :label="$t('secret_key')"
></q-input> ></q-input>
@@ -394,7 +422,11 @@
<q-input <q-input
filled filled
class="q-mt-md" class="q-mt-md"
:type="hideInputToggle ? 'password' : 'text'" type="text"
:input-style="secretInputStyle"
autocomplete="off"
autocapitalize="off"
spellcheck="false"
v-model="formData.paypal_webhook_id" v-model="formData.paypal_webhook_id"
:label="$t('webhook_id')" :label="$t('webhook_id')"
:hint="$t('webhook_id_hint')" :hint="$t('webhook_id_hint')"
@@ -611,7 +643,11 @@
<q-input <q-input
filled filled
class="q-mt-md" class="q-mt-md"
:type="hideInputToggle ? 'password' : 'text'" type="text"
:input-style="secretInputStyle"
autocomplete="off"
autocapitalize="off"
spellcheck="false"
v-model="formData.square_access_token" v-model="formData.square_access_token"
:label="$t('access_token')" :label="$t('access_token')"
></q-input> ></q-input>
@@ -688,7 +724,11 @@
<q-input <q-input
filled filled
class="q-mt-md" class="q-mt-md"
:type="hideInputToggle ? 'password' : 'text'" type="text"
:input-style="secretInputStyle"
autocomplete="off"
autocapitalize="off"
spellcheck="false"
v-model="formData.square_webhook_signature_key" v-model="formData.square_webhook_signature_key"
:label="$t('signing_secret')" :label="$t('signing_secret')"
:hint="$t('square_webhook_signature_key_hint')" :hint="$t('square_webhook_signature_key_hint')"
@@ -900,7 +940,11 @@
<q-input <q-input
filled filled
class="q-mt-md" class="q-mt-md"
:type="hideInputToggle ? 'password' : 'text'" type="text"
:input-style="secretInputStyle"
autocomplete="off"
autocapitalize="off"
spellcheck="false"
v-model="formData.revolut_api_secret_key" v-model="formData.revolut_api_secret_key"
label="API secret key" label="API secret key"
></q-input> ></q-input>
@@ -320,15 +320,6 @@
> >
</q-toggle> </q-toggle>
</div> </div>
<div class="col-12 col-sm-6 col-lg-2">
<q-toggle
type="bool"
v-model="formData.lnbits_default_burger_menu_background"
color="primary"
:label="$t('burger_menu_background')"
>
</q-toggle>
</div>
</div> </div>
</div> </div>
</q-card-section> </q-card-section>
-24
View File
@@ -601,30 +601,6 @@
</div> </div>
</div> </div>
<div class="row q-mb-md">
<div class="col-4">
<span v-text="$t('burger_menu_background')"></span>
</div>
<div class="col-8">
<q-toggle
dense
flat
round
icon="menu_open"
v-model="g.burgerMenuChoice"
@update:model-value="
siteCustomisationChanged({burgerMenuChoice: $event})
"
>
<q-tooltip
><span
v-text="$t('toggle_burger_menu_background')"
></span
></q-tooltip>
</q-toggle>
</div>
</div>
<div class="row q-mb-md"> <div class="row q-mb-md">
<div class="col-4"> <div class="col-4">
<span v-text="$t('toggle_darkmode')"></span> <span v-text="$t('toggle_darkmode')"></span>
Generated
+3 -3
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. # This file is automatically @generated by Poetry 2.4.0 and should not be changed by hand.
[[package]] [[package]]
name = "aiohappyeyeballs" name = "aiohappyeyeballs"
@@ -3394,7 +3394,7 @@ version = "5.1.2"
description = "Call stack profiler for Python. Shows you why your code is slow!" description = "Call stack profiler for Python. Shows you why your code is slow!"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"] groups = ["dev"]
files = [ files = [
{file = "pyinstrument-5.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f224fe80ba288a00980af298d3808219f9d246fd95b4f91729c9c33a0dc54fe6"}, {file = "pyinstrument-5.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f224fe80ba288a00980af298d3808219f9d246fd95b4f91729c9c33a0dc54fe6"},
{file = "pyinstrument-5.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7df09fc0d5b72daf48b73cdf07738761bff7f656c81aff686b3ccdd7d2abe236"}, {file = "pyinstrument-5.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7df09fc0d5b72daf48b73cdf07738761bff7f656c81aff686b3ccdd7d2abe236"},
@@ -5110,4 +5110,4 @@ migration = ["psycopg2-binary"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.10,<3.13" python-versions = ">=3.10,<3.13"
content-hash = "4050934800e6dfcc5242d1847d3db69eb6e91d634c09368a28255ad3c908b568" content-hash = "4dd6ccfc459064fb5335531d4ceb9c0f228279b1c95d0911e895232a51b90006"
+1 -1
View File
@@ -52,7 +52,6 @@ dependencies = [
"python-dotenv~=1.2.1", "python-dotenv~=1.2.1",
"greenlet~=3.3.0", "greenlet~=3.3.0",
"urllib3>=2.7.0", "urllib3>=2.7.0",
"pyinstrument>=5.1.2",
] ]
[project.scripts] [project.scripts]
@@ -85,6 +84,7 @@ dev = [
"types-mock~=5.2.0.20250924", "types-mock~=5.2.0.20250924",
"mock~=5.2.0", "mock~=5.2.0",
"grpcio-tools~=1.76.0", "grpcio-tools~=1.76.0",
"pyinstrument>=5.1.2",
] ]
[tool.uv] [tool.uv]
+36
View File
@@ -76,6 +76,7 @@ class MockHTTPClient:
def fiat_provider_test_settings(settings: Settings): def fiat_provider_test_settings(settings: Settings):
original_lnbits_running = settings.lnbits_running original_lnbits_running = settings.lnbits_running
original_allowed_currencies = settings.lnbits_allowed_currencies original_allowed_currencies = settings.lnbits_allowed_currencies
original_fiat_providers_admin_only = settings.fiat_providers_admin_only
original_paypal_enabled = settings.paypal_enabled original_paypal_enabled = settings.paypal_enabled
original_square_enabled = settings.square_enabled original_square_enabled = settings.square_enabled
original_square_api_endpoint = settings.square_api_endpoint original_square_api_endpoint = settings.square_api_endpoint
@@ -95,12 +96,14 @@ def fiat_provider_test_settings(settings: Settings):
original_revolut_webhook_signing_secret = settings.revolut_webhook_signing_secret original_revolut_webhook_signing_secret = settings.revolut_webhook_signing_secret
original_revolut_limits = settings.revolut_limits.copy(deep=True) original_revolut_limits = settings.revolut_limits.copy(deep=True)
settings.lnbits_allowed_currencies = [] settings.lnbits_allowed_currencies = []
settings.fiat_providers_admin_only = False
settings.paypal_enabled = False settings.paypal_enabled = False
settings.square_enabled = False settings.square_enabled = False
settings.revolut_enabled = False settings.revolut_enabled = False
yield yield
settings.lnbits_running = original_lnbits_running settings.lnbits_running = original_lnbits_running
settings.lnbits_allowed_currencies = original_allowed_currencies settings.lnbits_allowed_currencies = original_allowed_currencies
settings.fiat_providers_admin_only = original_fiat_providers_admin_only
settings.paypal_enabled = original_paypal_enabled settings.paypal_enabled = original_paypal_enabled
settings.square_enabled = original_square_enabled settings.square_enabled = original_square_enabled
settings.square_api_endpoint = original_square_api_endpoint settings.square_api_endpoint = original_square_api_endpoint
@@ -217,6 +220,39 @@ async def test_create_wallet_fiat_invoice_allowed_users(
assert user.fiat_providers == ["revolut"] assert user.fiat_providers == ["revolut"]
@pytest.mark.anyio
async def test_fiat_providers_admin_only_default(to_user: User, settings: Settings):
original_admin_users = list(settings.lnbits_admin_users)
try:
settings.fiat_providers_admin_only = True
settings.stripe_enabled = True
user = await get_user(to_user.id)
assert user
assert user.fiat_providers == []
settings.lnbits_admin_users.append(to_user.id)
user = await get_user(to_user.id)
assert user
assert user.fiat_providers == ["stripe"]
finally:
settings.lnbits_admin_users = original_admin_users
@pytest.mark.anyio
async def test_create_wallet_fiat_invoice_admin_only_rejects_non_admin(
to_wallet: Wallet, settings: Settings
):
settings.fiat_providers_admin_only = True
settings.stripe_enabled = True
invoice_data = CreateInvoice(
unit="USD", amount=1.0, memo="Test", fiat_provider="stripe"
)
with pytest.raises(ValueError, match="available to admins only"):
await payments.create_fiat_invoice(to_wallet.id, invoice_data)
@pytest.mark.anyio @pytest.mark.anyio
async def test_create_wallet_fiat_invoice_fiat_limits_fail( async def test_create_wallet_fiat_invoice_fiat_limits_fail(
to_wallet: Wallet, settings: Settings, mocker: MockerFixture to_wallet: Wallet, settings: Settings, mocker: MockerFixture
-2
View File
@@ -37,14 +37,12 @@ def test_dict_to_settings_parses_known_values():
{ {
"lnbits_site_title": "Test Title", "lnbits_site_title": "Test Title",
"lnbits_service_fee": 5, "lnbits_service_fee": 5,
"lnbits_default_burger_menu_background": False,
"ignored_field": "ignored", "ignored_field": "ignored",
} }
) )
assert parsed.lnbits_site_title == "Test Title" assert parsed.lnbits_site_title == "Test Title"
assert parsed.lnbits_service_fee == 5 assert parsed.lnbits_service_fee == 5
assert parsed.lnbits_default_burger_menu_background is False
assert not hasattr(parsed, "ignored_field") assert not hasattr(parsed, "ignored_field")
-8
View File
@@ -232,14 +232,6 @@ def test_installed_extensions_settings_activate_and_deactivate_paths():
assert installed.find_extension_redirect("/.well-known/lnurlp", []) is None assert installed.find_extension_redirect("/.well-known/lnurlp", []) is None
def test_public_settings_include_burger_menu_background(settings: Settings):
settings.lnbits_default_burger_menu_background = False
public_settings = PublicSettings.from_settings(settings)
assert public_settings.default_burger_menu_background is False
def test_installed_extensions_settings_detects_conflicting_redirects(): def test_installed_extensions_settings_detects_conflicting_redirects():
installed = InstalledExtensionsSettings( installed = InstalledExtensionsSettings(
lnbits_extensions_redirects=[ lnbits_extensions_redirects=[
Generated
+2 -2
View File
@@ -1302,7 +1302,6 @@ dependencies = [
{ name = "protobuf" }, { name = "protobuf" },
{ name = "pycryptodomex" }, { name = "pycryptodomex" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "pyinstrument" },
{ name = "pyjwt" }, { name = "pyjwt" },
{ name = "pyln-client" }, { name = "pyln-client" },
{ name = "pynostr" }, { name = "pynostr" },
@@ -1348,6 +1347,7 @@ dev = [
{ name = "openai" }, { name = "openai" },
{ name = "openapi-spec-validator" }, { name = "openapi-spec-validator" },
{ name = "pre-commit" }, { name = "pre-commit" },
{ name = "pyinstrument" },
{ name = "pytest" }, { name = "pytest" },
{ name = "pytest-cov" }, { name = "pytest-cov" },
{ name = "pytest-httpserver" }, { name = "pytest-httpserver" },
@@ -1388,7 +1388,6 @@ requires-dist = [
{ name = "psycopg2-binary", marker = "extra == 'migration'", specifier = "~=2.9.11" }, { name = "psycopg2-binary", marker = "extra == 'migration'", specifier = "~=2.9.11" },
{ name = "pycryptodomex", specifier = "~=3.23.0" }, { name = "pycryptodomex", specifier = "~=3.23.0" },
{ name = "pydantic", specifier = "~=1.10.26" }, { name = "pydantic", specifier = "~=1.10.26" },
{ name = "pyinstrument", specifier = ">=5.1.2" },
{ name = "pyjwt", specifier = "~=2.12.0" }, { name = "pyjwt", specifier = "~=2.12.0" },
{ name = "pyln-client", specifier = "~=25.12.0" }, { name = "pyln-client", specifier = "~=25.12.0" },
{ name = "pynostr", specifier = "~=0.7.0" }, { name = "pynostr", specifier = "~=0.7.0" },
@@ -1424,6 +1423,7 @@ dev = [
{ name = "openai", specifier = "~=2.14.0" }, { name = "openai", specifier = "~=2.14.0" },
{ name = "openapi-spec-validator", specifier = "~=0.7.2" }, { name = "openapi-spec-validator", specifier = "~=0.7.2" },
{ name = "pre-commit", specifier = "~=4.5.1" }, { name = "pre-commit", specifier = "~=4.5.1" },
{ name = "pyinstrument", specifier = ">=5.1.2" },
{ name = "pytest", specifier = "~=9.0.2" }, { name = "pytest", specifier = "~=9.0.2" },
{ name = "pytest-cov", specifier = "~=7.0.0" }, { name = "pytest-cov", specifier = "~=7.0.0" },
{ name = "pytest-httpserver", specifier = "~=1.1.3" }, { name = "pytest-httpserver", specifier = "~=1.1.3" },