Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce57d08163 | ||
|
|
52304e0730 | ||
|
|
6664eebf5a | ||
|
|
2a2af81827 | ||
|
|
9b47f6323f | ||
|
|
a61807a257 |
@@ -119,6 +119,8 @@ 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."
|
||||
# Choose from bitcoin, mint, flamingo, freedom, salvador, autumn, monochrome, classic, 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"
|
||||
|
||||
######################################
|
||||
|
||||
@@ -306,7 +306,7 @@ async def update_payment_checking_id(
|
||||
await (conn or db).execute(
|
||||
f"""
|
||||
UPDATE apipayments
|
||||
SET checking_id = :new_id, updated_at = {db.timestamp_placeholder('now')}
|
||||
SET checking_id = :new_id, updated_at = {db.timestamp_placeholder("now")}
|
||||
WHERE checking_id = :old_id
|
||||
""", # noqa: S608
|
||||
{
|
||||
@@ -399,7 +399,6 @@ async def get_payment_count_stats(
|
||||
user_id: str | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> list[PaymentCountStat]:
|
||||
|
||||
if not filters:
|
||||
filters = Filters()
|
||||
extra_stmts = []
|
||||
@@ -432,7 +431,6 @@ async def get_daily_stats(
|
||||
user_id: str | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> tuple[list[PaymentDailyStats], list[PaymentDailyStats]]:
|
||||
|
||||
if not filters:
|
||||
filters = Filters()
|
||||
|
||||
@@ -482,7 +480,6 @@ async def get_wallets_stats(
|
||||
user_id: str | None = None,
|
||||
conn: Connection | None = None,
|
||||
) -> list[PaymentWalletStats]:
|
||||
|
||||
if not filters:
|
||||
filters = Filters()
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ from .payments import (
|
||||
PaymentState,
|
||||
PaymentWalletStats,
|
||||
SettleInvoice,
|
||||
UpdatePaymentExtra,
|
||||
)
|
||||
from .tinyurl import TinyURL
|
||||
from .users import (
|
||||
@@ -90,6 +91,7 @@ __all__ = [
|
||||
"SimpleStatus",
|
||||
"TinyURL",
|
||||
"UpdateBalance",
|
||||
"UpdatePaymentExtra",
|
||||
"UpdateSuperuserPassword",
|
||||
"UpdateUser",
|
||||
"UpdateUserPassword",
|
||||
|
||||
@@ -35,6 +35,11 @@ class PaymentExtra(BaseModel):
|
||||
lnurl_response: str | None = None
|
||||
|
||||
|
||||
class UpdatePaymentExtra(BaseModel):
|
||||
payment_hash: str
|
||||
extra: dict = Field(default_factory=dict)
|
||||
|
||||
|
||||
class PayInvoice(BaseModel):
|
||||
payment_request: str
|
||||
description: str | None = None
|
||||
|
||||
@@ -133,10 +133,6 @@ async def create_fiat_invoice(
|
||||
raise ValueError(
|
||||
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":
|
||||
raise ValueError("Fiat provider cannot be used with satoshis.")
|
||||
|
||||
@@ -34,6 +34,7 @@ from lnbits.core.models import (
|
||||
PaymentWalletStats,
|
||||
SettleInvoice,
|
||||
SimpleStatus,
|
||||
UpdatePaymentExtra,
|
||||
)
|
||||
from lnbits.core.models.payments import UpdatePaymentLabels
|
||||
from lnbits.core.models.users import AccountId
|
||||
@@ -297,6 +298,38 @@ async def api_update_payment_labels(
|
||||
return SimpleStatus(success=True, message="Payment labels updated.")
|
||||
|
||||
|
||||
@payment_router.patch(
|
||||
"/extra",
|
||||
name="Update payment extra",
|
||||
description="Append new extra metadata to a payment.",
|
||||
response_model=Payment,
|
||||
)
|
||||
async def api_update_payment_extra(
|
||||
data: UpdatePaymentExtra,
|
||||
key_type: WalletTypeInfo = Depends(require_admin_key),
|
||||
) -> Payment:
|
||||
payment = await get_standalone_payment(
|
||||
data.payment_hash, wallet_id=key_type.wallet.id
|
||||
)
|
||||
if payment is None:
|
||||
raise HTTPException(HTTPStatus.NOT_FOUND, "Payment does not exist.")
|
||||
if not payment.success:
|
||||
raise HTTPException(
|
||||
HTTPStatus.BAD_REQUEST, "Payment extra can only be updated after success."
|
||||
)
|
||||
|
||||
duplicate_keys = sorted(set(payment.extra).intersection(data.extra))
|
||||
if duplicate_keys:
|
||||
raise HTTPException(
|
||||
HTTPStatus.BAD_REQUEST,
|
||||
f"Extra keys already exist: {', '.join(duplicate_keys)}.",
|
||||
)
|
||||
|
||||
payment.extra.update(data.extra)
|
||||
await update_payment(payment)
|
||||
return payment
|
||||
|
||||
|
||||
@payment_router.get("/fee-reserve")
|
||||
async def api_payments_fee_reserve(invoice: str = Query("invoice")) -> JSONResponse:
|
||||
invoice_obj = bolt11.decode(invoice)
|
||||
|
||||
+3
-11
@@ -300,6 +300,7 @@ class ThemesSettings(LNbitsSettings):
|
||||
lnbits_default_card_rounded: bool = Field(default=True)
|
||||
lnbits_default_card_gradient: bool = Field(default=True)
|
||||
lnbits_default_card_shadow: bool = Field(default=False)
|
||||
lnbits_default_burger_menu_background: bool = Field(default=True)
|
||||
|
||||
|
||||
class OpsSettings(LNbitsSettings):
|
||||
@@ -770,8 +771,6 @@ class FiatProvidersSettings(
|
||||
SquareFiatProvider,
|
||||
RevolutFiatProvider,
|
||||
):
|
||||
fiat_providers_admin_only: bool = Field(default=True)
|
||||
|
||||
def is_fiat_provider_enabled(self, provider: str | None) -> bool:
|
||||
"""
|
||||
Checks if a specific fiat provider is enabled.
|
||||
@@ -792,15 +791,6 @@ class FiatProvidersSettings(
|
||||
"""
|
||||
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 = []
|
||||
if self.stripe_enabled and (
|
||||
not self.stripe_limits.allowed_users
|
||||
@@ -1292,6 +1282,7 @@ class PublicSettings(BaseModel):
|
||||
default_card_rounded: bool = Field(alias="defaultCardRounded")
|
||||
default_card_gradient: bool = Field(alias="defaultCardGradient")
|
||||
default_card_shadow: bool = Field(alias="defaultCardShadow")
|
||||
default_burger_menu_background: bool = Field(alias="defaultBurgerMenuBackground")
|
||||
denomination: str | None = Field()
|
||||
extensions: list[str] = Field()
|
||||
allowed_currencies: list[str] = Field(alias="allowedCurrencies")
|
||||
@@ -1355,6 +1346,7 @@ class PublicSettings(BaseModel):
|
||||
defaultCardRounded=settings.lnbits_default_card_rounded,
|
||||
defaultCardGradient=settings.lnbits_default_card_gradient,
|
||||
defaultCardShadow=settings.lnbits_default_card_shadow,
|
||||
defaultBurgerMenuBackground=settings.lnbits_default_burger_menu_background,
|
||||
denomination=settings.lnbits_denomination,
|
||||
extensions=list(settings.lnbits_installed_extensions_ids),
|
||||
allowedCurrencies=settings.lnbits_allowed_currencies,
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -395,6 +395,13 @@ 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));
|
||||
}
|
||||
|
||||
body.no-burger-background .q-drawer {
|
||||
background-color: transparent !important;
|
||||
background-image: none !important;
|
||||
backdrop-filter: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
:root {
|
||||
--size: 100px;
|
||||
--gap: 25px;
|
||||
|
||||
@@ -490,6 +490,8 @@ window.localisation.en = {
|
||||
toggle_card_gradient: 'Toggle gradient on cards',
|
||||
card_shadow: 'Card Shadow',
|
||||
toggle_card_shadow: 'Toggle shadow on cards',
|
||||
burger_menu_background: 'Burger Menu Background',
|
||||
toggle_burger_menu_background: 'Toggle burger menu background',
|
||||
language: 'Language',
|
||||
assets: 'Assets',
|
||||
max_asset_size_mb: 'Max Asset Size (MB)',
|
||||
|
||||
@@ -452,7 +452,9 @@ window.app.component('username-password', {
|
||||
confirmationMethod: 'code',
|
||||
confirmationEmail: '',
|
||||
confirmationCode: this.invitationCode || '',
|
||||
showConfirmationCode: false
|
||||
showConfirmationCode: false,
|
||||
showPwd: false,
|
||||
showPwdRepeat: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -12,21 +12,6 @@ window.app.component('lnbits-admin-fiat-providers', {
|
||||
}
|
||||
},
|
||||
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() {
|
||||
return (
|
||||
this.formData?.stripe_payment_webhook_url ||
|
||||
|
||||
@@ -69,6 +69,14 @@ window.app.component('lnbits-theme', {
|
||||
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) {
|
||||
this.$q.localStorage.set('lnbits.mobileSimple', val)
|
||||
if (val === true) {
|
||||
@@ -150,6 +158,9 @@ window.app.component('lnbits-theme', {
|
||||
if (this.g.cardShadowChoice === true) {
|
||||
document.body.classList.add('card-shadow')
|
||||
}
|
||||
if (this.g.burgerMenuChoice !== true) {
|
||||
document.body.classList.add('no-burger-background')
|
||||
}
|
||||
if (this.g.bgimageChoice !== '') {
|
||||
document.body.classList.add('bg-image')
|
||||
document.body.style.setProperty(
|
||||
|
||||
@@ -29,6 +29,10 @@ window.g = Vue.reactive({
|
||||
SETTINGS.defaultCardGradient
|
||||
),
|
||||
cardShadowChoice: localStore('lnbits.cardShadow', SETTINGS.defaultCardShadow),
|
||||
burgerMenuChoice: localStore(
|
||||
'lnbits.burgerMenu',
|
||||
SETTINGS.defaultBurgerMenuBackground
|
||||
),
|
||||
reactionChoice: localStore('lnbits.reactions', SETTINGS.defaultReaction),
|
||||
bgimageChoice: localStore(
|
||||
'lnbits.backgroundImage',
|
||||
|
||||
@@ -732,7 +732,8 @@ window.PageAccount = {
|
||||
darkChoice: this.g.settings.defaultDark,
|
||||
cardRoundedChoice: this.g.settings.defaultCardRounded,
|
||||
cardGradientChoice: this.g.settings.defaultCardGradient,
|
||||
cardShadowChoice: this.g.settings.defaultCardShadow
|
||||
cardShadowChoice: this.g.settings.defaultCardShadow,
|
||||
burgerMenuChoice: this.g.settings.defaultBurgerMenuBackground
|
||||
}
|
||||
this.siteCustomisationChanged(defaults)
|
||||
}
|
||||
|
||||
@@ -70,3 +70,12 @@ body.card-shadow.body--dark {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -774,7 +774,13 @@ include('components/lnbits-error.vue') %}
|
||||
v-model="password"
|
||||
name="password"
|
||||
:label="$t('password') + ' *'"
|
||||
type="password"
|
||||
:type="showPwd ? 'text' : 'password'"
|
||||
><template v-slot:append>
|
||||
<q-icon
|
||||
:name="showPwd ? 'visibility' : 'visibility_off'"
|
||||
class="cursor-pointer"
|
||||
@click="showPwd = !showPwd"
|
||||
/> </template
|
||||
></q-input>
|
||||
<div class="row justify-end">
|
||||
<q-btn
|
||||
@@ -803,16 +809,28 @@ include('components/lnbits-error.vue') %}
|
||||
filled
|
||||
v-model="password"
|
||||
:label="$t('password') + ' *'"
|
||||
type="password"
|
||||
:type="showPwd ? 'text' : '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
|
||||
dense
|
||||
filled
|
||||
v-model="passwordRepeat"
|
||||
:label="$t('password_repeat') + ' *'"
|
||||
type="password"
|
||||
:type="showPwdRepeat ? 'text' : '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>
|
||||
<div
|
||||
v-if="confirmationMethodsCount > 1"
|
||||
@@ -925,16 +943,28 @@ include('components/lnbits-error.vue') %}
|
||||
filled
|
||||
v-model="password"
|
||||
:label="$t('password') + ' *'"
|
||||
type="password"
|
||||
:type="showPwd ? 'text' : '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
|
||||
dense
|
||||
filled
|
||||
v-model="passwordRepeat"
|
||||
:label="$t('password_repeat') + ' *'"
|
||||
type="password"
|
||||
:type="showPwdRepeat ? 'text' : '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>
|
||||
<div class="row justify-end">
|
||||
<q-btn
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template id="lnbits-admin-fiat-providers">
|
||||
<h6 class="q-my-none q-mb-sm row items-center q-gutter-sm">
|
||||
<h6 class="q-my-none q-mb-sm">
|
||||
<span v-text="$t('fiat_providers')"></span>
|
||||
<q-btn
|
||||
round
|
||||
@@ -7,18 +7,6 @@
|
||||
@click="hideInputToggle = !hideInputToggle"
|
||||
:icon="hideInputToggle ? 'visibility_off' : 'visibility'"
|
||||
></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>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@@ -58,11 +46,7 @@
|
||||
<q-input
|
||||
filled
|
||||
class="q-mt-md"
|
||||
type="text"
|
||||
:input-style="secretInputStyle"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
:type="hideInputToggle ? 'password' : 'text'"
|
||||
v-model="formData.stripe_api_secret_key"
|
||||
:label="$t('secret_key')"
|
||||
></q-input>
|
||||
@@ -124,11 +108,7 @@
|
||||
<q-input
|
||||
filled
|
||||
class="q-mt-md"
|
||||
type="text"
|
||||
:input-style="secretInputStyle"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
:type="hideInputToggle ? 'password' : 'text'"
|
||||
v-model="formData.stripe_webhook_signing_secret"
|
||||
:label="$t('signing_secret')"
|
||||
:hint="$t('signing_secret_hint')"
|
||||
@@ -345,22 +325,14 @@
|
||||
<q-input
|
||||
filled
|
||||
class="q-mt-md"
|
||||
type="text"
|
||||
:input-style="secretInputStyle"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
:type="hideInputToggle ? 'password' : 'text'"
|
||||
v-model="formData.paypal_client_id"
|
||||
:label="$t('client_id')"
|
||||
></q-input>
|
||||
<q-input
|
||||
filled
|
||||
class="q-mt-md"
|
||||
type="text"
|
||||
:input-style="secretInputStyle"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
:type="hideInputToggle ? 'password' : 'text'"
|
||||
v-model="formData.paypal_client_secret"
|
||||
:label="$t('secret_key')"
|
||||
></q-input>
|
||||
@@ -422,11 +394,7 @@
|
||||
<q-input
|
||||
filled
|
||||
class="q-mt-md"
|
||||
type="text"
|
||||
:input-style="secretInputStyle"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
:type="hideInputToggle ? 'password' : 'text'"
|
||||
v-model="formData.paypal_webhook_id"
|
||||
:label="$t('webhook_id')"
|
||||
:hint="$t('webhook_id_hint')"
|
||||
@@ -643,11 +611,7 @@
|
||||
<q-input
|
||||
filled
|
||||
class="q-mt-md"
|
||||
type="text"
|
||||
:input-style="secretInputStyle"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
:type="hideInputToggle ? 'password' : 'text'"
|
||||
v-model="formData.square_access_token"
|
||||
:label="$t('access_token')"
|
||||
></q-input>
|
||||
@@ -724,11 +688,7 @@
|
||||
<q-input
|
||||
filled
|
||||
class="q-mt-md"
|
||||
type="text"
|
||||
:input-style="secretInputStyle"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
:type="hideInputToggle ? 'password' : 'text'"
|
||||
v-model="formData.square_webhook_signature_key"
|
||||
:label="$t('signing_secret')"
|
||||
:hint="$t('square_webhook_signature_key_hint')"
|
||||
@@ -940,11 +900,7 @@
|
||||
<q-input
|
||||
filled
|
||||
class="q-mt-md"
|
||||
type="text"
|
||||
:input-style="secretInputStyle"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
:type="hideInputToggle ? 'password' : 'text'"
|
||||
v-model="formData.revolut_api_secret_key"
|
||||
label="API secret key"
|
||||
></q-input>
|
||||
|
||||
@@ -320,6 +320,15 @@
|
||||
>
|
||||
</q-toggle>
|
||||
</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>
|
||||
</q-card-section>
|
||||
|
||||
@@ -601,6 +601,30 @@
|
||||
</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="col-4">
|
||||
<span v-text="$t('toggle_darkmode')"></span>
|
||||
|
||||
Generated
+3
-3
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 2.4.0 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiohappyeyeballs"
|
||||
@@ -3394,7 +3394,7 @@ version = "5.1.2"
|
||||
description = "Call stack profiler for Python. Shows you why your code is slow!"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{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"},
|
||||
@@ -5110,4 +5110,4 @@ migration = ["psycopg2-binary"]
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.10,<3.13"
|
||||
content-hash = "4dd6ccfc459064fb5335531d4ceb9c0f228279b1c95d0911e895232a51b90006"
|
||||
content-hash = "4050934800e6dfcc5242d1847d3db69eb6e91d634c09368a28255ad3c908b568"
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "lnbits"
|
||||
version = "1.5.4"
|
||||
version = "1.5.5-rc1"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
||||
authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }]
|
||||
@@ -52,6 +52,7 @@ dependencies = [
|
||||
"python-dotenv~=1.2.1",
|
||||
"greenlet~=3.3.0",
|
||||
"urllib3>=2.7.0",
|
||||
"pyinstrument>=5.1.2",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
@@ -84,7 +85,6 @@ dev = [
|
||||
"types-mock~=5.2.0.20250924",
|
||||
"mock~=5.2.0",
|
||||
"grpcio-tools~=1.76.0",
|
||||
"pyinstrument>=5.1.2",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
|
||||
@@ -6,7 +6,7 @@ import pytest
|
||||
from fastapi import HTTPException
|
||||
from pydantic import ValidationError
|
||||
|
||||
from lnbits.core.crud.payments import create_payment, get_payments
|
||||
from lnbits.core.crud.payments import create_payment, get_payment, get_payments
|
||||
from lnbits.core.models import Account, CreateInvoice, PaymentFilters, PaymentState
|
||||
from lnbits.core.models.payments import CancelInvoice, CreatePayment, SettleInvoice
|
||||
from lnbits.core.models.users import AccountId
|
||||
@@ -218,6 +218,164 @@ async def test_payment_api_fee_reserve_and_hold_invoice_actions(mocker):
|
||||
cancel_mock.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_payment_extra_update_appends_new_keys(
|
||||
client,
|
||||
to_wallet,
|
||||
adminkey_headers_to,
|
||||
):
|
||||
payment_hash = uuid4().hex
|
||||
checking_id = await _create_payment(
|
||||
to_wallet.id,
|
||||
amount_msat=1_000,
|
||||
payment_hash=payment_hash,
|
||||
tag="splitpayments",
|
||||
)
|
||||
|
||||
response = await client.patch(
|
||||
"/api/v1/payments/extra",
|
||||
headers=adminkey_headers_to,
|
||||
json={
|
||||
"payment_hash": payment_hash,
|
||||
"extra": {"child": "daughter", "compliance_note": "reviewed"},
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
extra = response.json()["extra"]
|
||||
assert extra["tag"] == "splitpayments"
|
||||
assert extra["child"] == "daughter"
|
||||
assert extra["compliance_note"] == "reviewed"
|
||||
|
||||
payment = await get_payment(checking_id)
|
||||
assert payment.extra == extra
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_payment_extra_update_creates_extra_when_missing(
|
||||
client,
|
||||
to_wallet,
|
||||
adminkey_headers_to,
|
||||
):
|
||||
payment_hash = uuid4().hex
|
||||
checking_id = await _create_payment(
|
||||
to_wallet.id,
|
||||
amount_msat=1_000,
|
||||
payment_hash=payment_hash,
|
||||
)
|
||||
|
||||
response = await client.patch(
|
||||
"/api/v1/payments/extra",
|
||||
headers=adminkey_headers_to,
|
||||
json={"payment_hash": payment_hash, "extra": {"note": "reviewed"}},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["extra"] == {"note": "reviewed"}
|
||||
|
||||
payment = await get_payment(checking_id)
|
||||
assert payment.extra == {"note": "reviewed"}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_payment_extra_update_rejects_existing_keys(
|
||||
client,
|
||||
to_wallet,
|
||||
adminkey_headers_to,
|
||||
):
|
||||
payment_hash = uuid4().hex
|
||||
checking_id = await _create_payment(
|
||||
to_wallet.id,
|
||||
amount_msat=1_000,
|
||||
payment_hash=payment_hash,
|
||||
tag="original",
|
||||
)
|
||||
|
||||
response = await client.patch(
|
||||
"/api/v1/payments/extra",
|
||||
headers=adminkey_headers_to,
|
||||
json={"payment_hash": payment_hash, "extra": {"tag": "overwritten"}},
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json()["detail"] == "Extra keys already exist: tag."
|
||||
|
||||
payment = await get_payment(checking_id)
|
||||
assert payment.extra == {"tag": "original"}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_payment_extra_update_requires_admin_key(
|
||||
client,
|
||||
to_wallet,
|
||||
inkey_headers_to,
|
||||
):
|
||||
payment_hash = uuid4().hex
|
||||
await _create_payment(
|
||||
to_wallet.id,
|
||||
amount_msat=1_000,
|
||||
payment_hash=payment_hash,
|
||||
)
|
||||
|
||||
response = await client.patch(
|
||||
"/api/v1/payments/extra",
|
||||
headers=inkey_headers_to,
|
||||
json={"payment_hash": payment_hash, "extra": {"note": "invoice key"}},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json()["detail"] == "Invalid adminkey."
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_payment_extra_update_is_wallet_scoped(
|
||||
client,
|
||||
from_wallet,
|
||||
adminkey_headers_to,
|
||||
):
|
||||
payment_hash = uuid4().hex
|
||||
await _create_payment(
|
||||
from_wallet.id,
|
||||
amount_msat=1_000,
|
||||
payment_hash=payment_hash,
|
||||
)
|
||||
|
||||
response = await client.patch(
|
||||
"/api/v1/payments/extra",
|
||||
headers=adminkey_headers_to,
|
||||
json={"payment_hash": payment_hash, "extra": {"note": "wrong wallet"}},
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
assert response.json()["detail"] == "Payment does not exist."
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_payment_extra_update_requires_successful_payment(
|
||||
client,
|
||||
to_wallet,
|
||||
adminkey_headers_to,
|
||||
):
|
||||
payment_hash = uuid4().hex
|
||||
await _create_payment(
|
||||
to_wallet.id,
|
||||
amount_msat=1_000,
|
||||
payment_hash=payment_hash,
|
||||
status=PaymentState.PENDING,
|
||||
)
|
||||
|
||||
response = await client.patch(
|
||||
"/api/v1/payments/extra",
|
||||
headers=adminkey_headers_to,
|
||||
json={"payment_hash": payment_hash, "extra": {"note": "too early"}},
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert (
|
||||
response.json()["detail"] == "Payment extra can only be updated after success."
|
||||
)
|
||||
|
||||
|
||||
async def _create_payment(
|
||||
wallet_id: str,
|
||||
*,
|
||||
|
||||
@@ -76,7 +76,6 @@ class MockHTTPClient:
|
||||
def fiat_provider_test_settings(settings: Settings):
|
||||
original_lnbits_running = settings.lnbits_running
|
||||
original_allowed_currencies = settings.lnbits_allowed_currencies
|
||||
original_fiat_providers_admin_only = settings.fiat_providers_admin_only
|
||||
original_paypal_enabled = settings.paypal_enabled
|
||||
original_square_enabled = settings.square_enabled
|
||||
original_square_api_endpoint = settings.square_api_endpoint
|
||||
@@ -96,14 +95,12 @@ def fiat_provider_test_settings(settings: Settings):
|
||||
original_revolut_webhook_signing_secret = settings.revolut_webhook_signing_secret
|
||||
original_revolut_limits = settings.revolut_limits.copy(deep=True)
|
||||
settings.lnbits_allowed_currencies = []
|
||||
settings.fiat_providers_admin_only = False
|
||||
settings.paypal_enabled = False
|
||||
settings.square_enabled = False
|
||||
settings.revolut_enabled = False
|
||||
yield
|
||||
settings.lnbits_running = original_lnbits_running
|
||||
settings.lnbits_allowed_currencies = original_allowed_currencies
|
||||
settings.fiat_providers_admin_only = original_fiat_providers_admin_only
|
||||
settings.paypal_enabled = original_paypal_enabled
|
||||
settings.square_enabled = original_square_enabled
|
||||
settings.square_api_endpoint = original_square_api_endpoint
|
||||
@@ -220,39 +217,6 @@ async def test_create_wallet_fiat_invoice_allowed_users(
|
||||
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
|
||||
async def test_create_wallet_fiat_invoice_fiat_limits_fail(
|
||||
to_wallet: Wallet, settings: Settings, mocker: MockerFixture
|
||||
|
||||
@@ -37,12 +37,14 @@ def test_dict_to_settings_parses_known_values():
|
||||
{
|
||||
"lnbits_site_title": "Test Title",
|
||||
"lnbits_service_fee": 5,
|
||||
"lnbits_default_burger_menu_background": False,
|
||||
"ignored_field": "ignored",
|
||||
}
|
||||
)
|
||||
|
||||
assert parsed.lnbits_site_title == "Test Title"
|
||||
assert parsed.lnbits_service_fee == 5
|
||||
assert parsed.lnbits_default_burger_menu_background is False
|
||||
assert not hasattr(parsed, "ignored_field")
|
||||
|
||||
|
||||
|
||||
@@ -232,6 +232,14 @@ def test_installed_extensions_settings_activate_and_deactivate_paths():
|
||||
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():
|
||||
installed = InstalledExtensionsSettings(
|
||||
lnbits_extensions_redirects=[
|
||||
|
||||
@@ -1275,7 +1275,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "lnbits"
|
||||
version = "1.5.4"
|
||||
version = "1.5.5rc1"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiosqlite" },
|
||||
@@ -1302,6 +1302,7 @@ dependencies = [
|
||||
{ name = "protobuf" },
|
||||
{ name = "pycryptodomex" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyinstrument" },
|
||||
{ name = "pyjwt" },
|
||||
{ name = "pyln-client" },
|
||||
{ name = "pynostr" },
|
||||
@@ -1347,7 +1348,6 @@ dev = [
|
||||
{ name = "openai" },
|
||||
{ name = "openapi-spec-validator" },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "pyinstrument" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "pytest-httpserver" },
|
||||
@@ -1388,6 +1388,7 @@ requires-dist = [
|
||||
{ name = "psycopg2-binary", marker = "extra == 'migration'", specifier = "~=2.9.11" },
|
||||
{ name = "pycryptodomex", specifier = "~=3.23.0" },
|
||||
{ name = "pydantic", specifier = "~=1.10.26" },
|
||||
{ name = "pyinstrument", specifier = ">=5.1.2" },
|
||||
{ name = "pyjwt", specifier = "~=2.12.0" },
|
||||
{ name = "pyln-client", specifier = "~=25.12.0" },
|
||||
{ name = "pynostr", specifier = "~=0.7.0" },
|
||||
@@ -1423,7 +1424,6 @@ dev = [
|
||||
{ name = "openai", specifier = "~=2.14.0" },
|
||||
{ name = "openapi-spec-validator", specifier = "~=0.7.2" },
|
||||
{ name = "pre-commit", specifier = "~=4.5.1" },
|
||||
{ name = "pyinstrument", specifier = ">=5.1.2" },
|
||||
{ name = "pytest", specifier = "~=9.0.2" },
|
||||
{ name = "pytest-cov", specifier = "~=7.0.0" },
|
||||
{ name = "pytest-httpserver", specifier = "~=1.1.3" },
|
||||
|
||||
Reference in New Issue
Block a user