Compare commits
17
Commits
v1.3.0-rc5
...
v1.3.0-rc7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3252ce4dc | ||
|
|
c312d70e63 | ||
|
|
36fc911b88 | ||
|
|
672a5b3a4d | ||
|
|
c0b33560bb | ||
|
|
15b6b1d512 | ||
|
|
67c92a79cf | ||
|
|
0d4751d6e0 | ||
|
|
213bde61a8 | ||
|
|
59553a6e1e | ||
|
|
918add5e6e | ||
|
|
2375f7bcf8 | ||
|
|
5021570f68 | ||
|
|
0d91d5b5be | ||
|
|
49f02a5b77 | ||
|
|
95b9dabb60 | ||
|
|
bc0087ec36 |
@@ -21,7 +21,6 @@ runs:
|
|||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ inputs.python-version }}
|
python-version: ${{ inputs.python-version }}
|
||||||
cache: "pip"
|
|
||||||
|
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@v6
|
uses: astral-sh/setup-uv@v6
|
||||||
|
|||||||
@@ -53,3 +53,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
make: checkbundle
|
make: checkbundle
|
||||||
npm: true
|
npm: true
|
||||||
|
|
||||||
|
poetry:
|
||||||
|
uses: ./.github/workflows/poetry.yml
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
name: poetry
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
python-version:
|
||||||
|
description: "python version"
|
||||||
|
type: string
|
||||||
|
default: "3.10"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
poetry:
|
||||||
|
name: run poetry install to check lock file
|
||||||
|
runs-on: "ubuntu-24.04"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: set up python ${{ inputs.python-version }}
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: ${{ inputs.python-version }}
|
||||||
|
- name: install poetry
|
||||||
|
run: |
|
||||||
|
curl -sSL https://install.python-poetry.org | python3 -
|
||||||
|
poetry install
|
||||||
@@ -61,3 +61,4 @@ dist
|
|||||||
|
|
||||||
# jetbrains
|
# jetbrains
|
||||||
.idea
|
.idea
|
||||||
|
.venv
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ exclude: '^lnbits/static/bundle.*|^docs/.*|^lnbits/static/vendor/.*|^lnbits/exte
|
|||||||
|
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.3.0
|
rev: v6.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
@@ -14,16 +14,16 @@ repos:
|
|||||||
- id: mixed-line-ending
|
- id: mixed-line-ending
|
||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 24.2.0
|
rev: 25.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.3.2
|
rev: v0.12.10
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [ --fix, --exit-non-zero-on-fix ]
|
args: [ --fix, --exit-non-zero-on-fix ]
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
- repo: https://github.com/rbubley/mirrors-prettier
|
||||||
rev: "v4.0.0-alpha.8"
|
rev: v3.6.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
types_or: [css, javascript, html, json]
|
types_or: [css, javascript, html, json]
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ RUN ls -l /usr/local/bin/boltzd
|
|||||||
RUN apt-get update && apt-get -y upgrade && \
|
RUN apt-get update && apt-get -y upgrade && \
|
||||||
apt-get install -y netcat-openbsd
|
apt-get install -y netcat-openbsd
|
||||||
|
|
||||||
|
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
ENV PATH="/root/.local/bin:$PATH"
|
||||||
|
|
||||||
# Reinstall dependencies just in case (needed for CMD usage)
|
# Reinstall dependencies just in case (needed for CMD usage)
|
||||||
RUN uv sync --all-extras
|
RUN uv sync --all-extras
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from .funding_source import (
|
|||||||
get_balance_delta,
|
get_balance_delta,
|
||||||
switch_to_voidwallet,
|
switch_to_voidwallet,
|
||||||
)
|
)
|
||||||
from .lnurl import fetch_lnurl_pay_request, get_pr_from_lnurl
|
from .lnurl import fetch_lnurl_pay_request, get_pr_from_lnurl, perform_withdraw
|
||||||
from .notifications import enqueue_admin_notification, send_payment_notification
|
from .notifications import enqueue_admin_notification, send_payment_notification
|
||||||
from .payments import (
|
from .payments import (
|
||||||
calculate_fiat_amounts,
|
calculate_fiat_amounts,
|
||||||
@@ -57,6 +57,7 @@ __all__ = [
|
|||||||
"get_payments_daily_stats",
|
"get_payments_daily_stats",
|
||||||
"get_pr_from_lnurl",
|
"get_pr_from_lnurl",
|
||||||
"pay_invoice",
|
"pay_invoice",
|
||||||
|
"perform_withdraw",
|
||||||
"send_payment_notification",
|
"send_payment_notification",
|
||||||
"service_fee",
|
"service_fee",
|
||||||
"settle_hold_invoice",
|
"settle_hold_invoice",
|
||||||
|
|||||||
@@ -73,11 +73,13 @@ async def uninstall_extension(ext_id: str):
|
|||||||
async def activate_extension(ext: Extension):
|
async def activate_extension(ext: Extension):
|
||||||
core_app_extra.register_new_ext_routes(ext)
|
core_app_extra.register_new_ext_routes(ext)
|
||||||
await update_installed_extension_state(ext_id=ext.code, active=True)
|
await update_installed_extension_state(ext_id=ext.code, active=True)
|
||||||
|
await start_extension_background_work(ext.code)
|
||||||
|
|
||||||
|
|
||||||
async def deactivate_extension(ext_id: str):
|
async def deactivate_extension(ext_id: str):
|
||||||
settings.deactivate_extension_paths(ext_id)
|
settings.deactivate_extension_paths(ext_id)
|
||||||
await update_installed_extension_state(ext_id=ext_id, active=False)
|
await update_installed_extension_state(ext_id=ext_id, active=False)
|
||||||
|
await stop_extension_background_work(ext_id)
|
||||||
|
|
||||||
|
|
||||||
async def stop_extension_background_work(ext_id: str) -> bool:
|
async def stop_extension_background_work(ext_id: str) -> bool:
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ from lnurl import (
|
|||||||
LnurlPayActionResponse,
|
LnurlPayActionResponse,
|
||||||
LnurlPayResponse,
|
LnurlPayResponse,
|
||||||
LnurlResponseException,
|
LnurlResponseException,
|
||||||
|
LnurlSuccessResponse,
|
||||||
|
LnurlWithdrawResponse,
|
||||||
execute_pay_request,
|
execute_pay_request,
|
||||||
|
execute_withdraw,
|
||||||
handle,
|
handle,
|
||||||
)
|
)
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -15,10 +18,36 @@ from loguru import logger
|
|||||||
from lnbits.core.crud import update_wallet
|
from lnbits.core.crud import update_wallet
|
||||||
from lnbits.core.models import CreateLnurlPayment, Wallet
|
from lnbits.core.models import CreateLnurlPayment, Wallet
|
||||||
from lnbits.core.models.lnurl import StoredPayLink
|
from lnbits.core.models.lnurl import StoredPayLink
|
||||||
|
from lnbits.helpers import check_callback_url
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
|
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
|
||||||
|
|
||||||
|
|
||||||
|
async def perform_withdraw(lnurl: str, payment_request: str) -> None:
|
||||||
|
"""
|
||||||
|
Perform an LNURL withdraw to the given LNURL-withdraw link.
|
||||||
|
:param lnurl: The LNURL-withdraw link. bech32 or lud17 format.
|
||||||
|
:param payment_request: The BOLT11 payment request to pay.
|
||||||
|
:raises LnurlResponseException: If the LNURL-withdraw process fails.
|
||||||
|
"""
|
||||||
|
res = await handle(lnurl, user_agent=settings.user_agent, timeout=10)
|
||||||
|
if isinstance(res, LnurlErrorResponse):
|
||||||
|
raise LnurlResponseException(res.reason)
|
||||||
|
if not isinstance(res, LnurlWithdrawResponse):
|
||||||
|
raise LnurlResponseException("Invalid LNURL-withdraw response.")
|
||||||
|
try:
|
||||||
|
check_callback_url(res.callback)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise LnurlResponseException(f"Invalid callback URL: {exc!s}") from exc
|
||||||
|
res2 = await execute_withdraw(
|
||||||
|
res, payment_request, user_agent=settings.user_agent, timeout=10
|
||||||
|
)
|
||||||
|
if isinstance(res2, LnurlErrorResponse):
|
||||||
|
raise LnurlResponseException(res2.reason)
|
||||||
|
if not isinstance(res2, LnurlSuccessResponse):
|
||||||
|
raise LnurlResponseException("Invalid LNURL-withdraw success response.")
|
||||||
|
|
||||||
|
|
||||||
async def get_pr_from_lnurl(
|
async def get_pr_from_lnurl(
|
||||||
lnurl: str, amount_msat: int, comment: str | None = None
|
lnurl: str, amount_msat: int, comment: str | None = None
|
||||||
) -> str:
|
) -> str:
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ async def send_payment_notification(wallet: Wallet, payment: Payment):
|
|||||||
|
|
||||||
async def send_ws_payment_notification(wallet: Wallet, payment: Payment):
|
async def send_ws_payment_notification(wallet: Wallet, payment: Payment):
|
||||||
# TODO: websocket message should be a clean payment model
|
# TODO: websocket message should be a clean payment model
|
||||||
# await websocket_manager.send_data(payment.json(), wallet.inkey)
|
# await websocket_manager.send(wallet.inkey, payment.json())
|
||||||
# TODO: figure out why we send the balance with the payment here.
|
# TODO: figure out why we send the balance with the payment here.
|
||||||
# cleaner would be to have a separate message for the balance
|
# cleaner would be to have a separate message for the balance
|
||||||
# and send it with the id of the wallet so wallets can subscribe to it
|
# and send it with the id of the wallet so wallets can subscribe to it
|
||||||
@@ -288,12 +288,11 @@ async def send_ws_payment_notification(wallet: Wallet, payment: Payment):
|
|||||||
"payment": json.loads(payment.json()),
|
"payment": json.loads(payment.json()),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await websocket_manager.send_data(payment_notification, wallet.inkey)
|
await websocket_manager.send(wallet.inkey, payment_notification)
|
||||||
await websocket_manager.send_data(payment_notification, wallet.adminkey)
|
await websocket_manager.send(wallet.adminkey, payment_notification)
|
||||||
|
await websocket_manager.send(
|
||||||
await websocket_manager.send_data(
|
|
||||||
json.dumps({"pending": payment.pending, "status": payment.status}),
|
|
||||||
payment.payment_hash,
|
payment.payment_hash,
|
||||||
|
json.dumps({"pending": payment.pending, "status": payment.status}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,65 @@
|
|||||||
from fastapi import WebSocket
|
from asyncio import Queue
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from fastapi import WebSocket, WebSocketDisconnect
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from lnbits.settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class WebsocketConnection:
|
||||||
|
item_id: str
|
||||||
|
websocket: WebSocket
|
||||||
|
receive_queue: Queue[str]
|
||||||
|
|
||||||
|
|
||||||
class WebsocketConnectionManager:
|
class WebsocketConnectionManager:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.active_connections: list[WebSocket] = []
|
self.active_connections: list[WebsocketConnection] = []
|
||||||
|
|
||||||
async def connect(self, websocket: WebSocket, item_id: str):
|
async def connect(self, item_id: str, websocket: WebSocket) -> WebsocketConnection:
|
||||||
logger.debug(f"Websocket connected to {item_id}")
|
logger.debug(f"Websocket connected to {item_id}")
|
||||||
await websocket.accept()
|
await websocket.accept()
|
||||||
self.active_connections.append(websocket)
|
conn = WebsocketConnection(
|
||||||
|
item_id=item_id,
|
||||||
|
websocket=websocket,
|
||||||
|
receive_queue=Queue(),
|
||||||
|
)
|
||||||
|
self.active_connections.append(conn)
|
||||||
|
return conn
|
||||||
|
|
||||||
def disconnect(self, websocket: WebSocket):
|
async def listen(self, conn: WebsocketConnection) -> None:
|
||||||
self.active_connections.remove(websocket)
|
while settings.lnbits_running:
|
||||||
|
try:
|
||||||
|
data = await conn.websocket.receive_text()
|
||||||
|
logger.debug(f"WS received data from {conn.item_id}: {data}")
|
||||||
|
conn.receive_queue.put_nowait(data)
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
for _conn in self.active_connections:
|
||||||
|
if _conn.websocket == conn.websocket:
|
||||||
|
self.active_connections.remove(_conn)
|
||||||
|
logger.debug(f"WS disconnected from {conn.item_id}")
|
||||||
|
break # out of the listen and the fastapi route
|
||||||
|
|
||||||
async def send_data(self, message: str, item_id: str):
|
def get_connections(self, item_id: str) -> list[WebsocketConnection]:
|
||||||
for connection in self.active_connections:
|
conns = []
|
||||||
if connection.path_params["item_id"] == item_id:
|
for conn in self.active_connections:
|
||||||
await connection.send_text(message)
|
if conn.item_id == item_id:
|
||||||
|
conns.append(conn)
|
||||||
|
return conns
|
||||||
|
|
||||||
|
def has_connection(self, item_id: str) -> bool:
|
||||||
|
return len(self.get_connections(item_id)) > 0
|
||||||
|
|
||||||
|
async def send(self, item_id: str, data: str) -> None:
|
||||||
|
for conn in self.get_connections(item_id):
|
||||||
|
await conn.websocket.send_text(data)
|
||||||
|
|
||||||
|
|
||||||
websocket_manager = WebsocketConnectionManager()
|
websocket_manager = WebsocketConnectionManager()
|
||||||
|
|
||||||
|
|
||||||
async def websocket_updater(item_id: str, data: str):
|
# deprecated import and use `websocket_manager.send()` instead
|
||||||
return await websocket_manager.send_data(data, item_id)
|
async def websocket_updater(item_id: str, data: str) -> None:
|
||||||
|
return await websocket_manager.send(item_id, data)
|
||||||
|
|||||||
@@ -53,8 +53,7 @@
|
|||||||
<div class="q-pa-md">
|
<div class="q-pa-md">
|
||||||
<lnbits-qrcode
|
<lnbits-qrcode
|
||||||
:value="wallet.adminkey"
|
:value="wallet.adminkey"
|
||||||
:options="{ width: 250 }"
|
:show-buttons="false"
|
||||||
class="rounded-borders"
|
|
||||||
></lnbits-qrcode>
|
></lnbits-qrcode>
|
||||||
</div>
|
</div>
|
||||||
</q-popup-proxy>
|
</q-popup-proxy>
|
||||||
@@ -86,8 +85,7 @@
|
|||||||
<div class="q-pa-md">
|
<div class="q-pa-md">
|
||||||
<lnbits-qrcode
|
<lnbits-qrcode
|
||||||
:value="wallet.inkey"
|
:value="wallet.inkey"
|
||||||
:options="{ width: 250 }"
|
:show-buttons="false"
|
||||||
class="rounded-borders"
|
|
||||||
></lnbits-qrcode>
|
></lnbits-qrcode>
|
||||||
</div>
|
</div>
|
||||||
</q-popup-proxy>
|
</q-popup-proxy>
|
||||||
|
|||||||
@@ -328,13 +328,10 @@
|
|||||||
<q-card v-if="selectedRelease" class="q-pa-lg lnbits__dialog-card">
|
<q-card v-if="selectedRelease" class="q-pa-lg lnbits__dialog-card">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div v-if="selectedRelease.paymentRequest">
|
<div v-if="selectedRelease.paymentRequest">
|
||||||
<a :href="'lightning:' + selectedRelease.paymentRequest">
|
<lnbits-qrcode
|
||||||
<q-responsive :ratio="1" class="q-mx-xl">
|
:value="'lightning:' + selectedRelease.paymentRequest.toUpperCase()"
|
||||||
<lnbits-qrcode
|
:href="'lightning:' + selectedRelease.paymentRequest"
|
||||||
:value="'lightning:' + selectedRelease.paymentRequest.toUpperCase()"
|
></lnbits-qrcode>
|
||||||
></lnbits-qrcode>
|
|
||||||
</q-responsive>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<q-spinner-bars color="primary" size="2.55em"></q-spinner-bars>
|
<q-spinner-bars color="primary" size="2.55em"></q-spinner-bars>
|
||||||
@@ -702,13 +699,10 @@
|
|||||||
<q-card-section v-if="selectedExtension.payToEnable.showQRCode">
|
<q-card-section v-if="selectedExtension.payToEnable.showQRCode">
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<div v-if="selectedExtension.payToEnable.paymentRequest" class="col">
|
<div v-if="selectedExtension.payToEnable.paymentRequest" class="col">
|
||||||
<a
|
<lnbits-qrcode
|
||||||
|
:value="'lightning:' + selectedExtension.payToEnable.paymentRequest.toUpperCase()"
|
||||||
:href="'lightning:' + selectedExtension.payToEnable.paymentRequest"
|
:href="'lightning:' + selectedExtension.payToEnable.paymentRequest"
|
||||||
>
|
></lnbits-qrcode>
|
||||||
<lnbits-qrcode
|
|
||||||
:value="'lightning:' + selectedExtension.payToEnable.paymentRequest.toUpperCase()"
|
|
||||||
></lnbits-qrcode>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="col">
|
<div v-else class="col">
|
||||||
<q-spinner-bars color="primary" size="2.55em"></q-spinner-bars>
|
<q-spinner-bars color="primary" size="2.55em"></q-spinner-bars>
|
||||||
|
|||||||
@@ -209,16 +209,6 @@
|
|||||||
></payment-list>
|
></payment-list>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
<div id="hiddenQrCodeContainer" style="display: none">
|
|
||||||
<lnbits-qrcode
|
|
||||||
v-if="receive.fiatPaymentReq"
|
|
||||||
:value="receive.fiatPaymentReq"
|
|
||||||
></lnbits-qrcode>
|
|
||||||
<lnbits-qrcode
|
|
||||||
v-else
|
|
||||||
:value="'lightning:' + (this.receive.paymentReq || '').toUpperCase()"
|
|
||||||
></lnbits-qrcode>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% if HIDE_API %}
|
{% if HIDE_API %}
|
||||||
<div class="col-12 col-md-4 q-gutter-y-md">
|
<div class="col-12 col-md-4 q-gutter-y-md">
|
||||||
@@ -277,13 +267,15 @@
|
|||||||
:label="$t('drain_funds')"
|
:label="$t('drain_funds')"
|
||||||
>
|
>
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section class="text-center">
|
<q-card-section>
|
||||||
<a href="lightning:{{wallet.lnurlwithdraw_full}}">
|
<lnbits-qrcode
|
||||||
<lnbits-qrcode
|
value="lightning:{{wallet.lnurlwithdraw_full}}"
|
||||||
:value="lightning:{{wallet.lnurlwithdraw_full}}"
|
href="lightning:{{wallet.lnurlwithdraw_full}}"
|
||||||
></lnbits-qrcode>
|
></lnbits-qrcode>
|
||||||
</a>
|
<p
|
||||||
<p v-text="$t('drain_funds_desc')"></p>
|
class="text-center"
|
||||||
|
v-text="$t('drain_funds_desc')"
|
||||||
|
></p>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
@@ -411,21 +403,15 @@
|
|||||||
:label="$t('export_to_phone')"
|
:label="$t('export_to_phone')"
|
||||||
>
|
>
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section class="text-center">
|
<q-card-section>
|
||||||
<p v-text="$t('export_to_phone_desc')"></p>
|
<p
|
||||||
|
class="text-center"
|
||||||
|
v-text="$t('export_to_phone_desc')"
|
||||||
|
></p>
|
||||||
<lnbits-qrcode
|
<lnbits-qrcode
|
||||||
:value="`${baseUrl}wallet?usr=${g.user.id}&wal=${g.wallet.id}`"
|
:value="`${baseUrl}wallet?usr=${g.user.id}&wal=${g.wallet.id}`"
|
||||||
></lnbits-qrcode>
|
></lnbits-qrcode>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<span v-text="exportWalletQR"></span>
|
|
||||||
<q-card-actions class="flex-center q-pb-md">
|
|
||||||
<q-btn
|
|
||||||
outline
|
|
||||||
color="grey"
|
|
||||||
:label="$t('copy_wallet_url')"
|
|
||||||
@click="copyText(`${baseUrl}wallet?usr=${g.user.id}&wal=${g.wallet.id}`)"
|
|
||||||
></q-btn>
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
@@ -862,18 +848,19 @@
|
|||||||
v-else-if="receive.paymentReq && receive.lnurl == null"
|
v-else-if="receive.paymentReq && receive.lnurl == null"
|
||||||
class="q-pa-lg q-pt-xl lnbits__dialog-card"
|
class="q-pa-lg q-pt-xl lnbits__dialog-card"
|
||||||
>
|
>
|
||||||
<div class="text-center q-mb-lg">
|
<lnbits-qrcode
|
||||||
<a
|
v-if="receive.fiatPaymentReq"
|
||||||
v-if="receive.fiatPaymentReq"
|
:show-buttons="false"
|
||||||
:href="receive.fiatPaymentReq"
|
:href="receive.fiatPaymentReq"
|
||||||
target="_blank"
|
:value="receive.fiatPaymentReq"
|
||||||
>
|
>
|
||||||
<div v-html="invoiceQrCode"></div>
|
</lnbits-qrcode>
|
||||||
</a>
|
<lnbits-qrcode
|
||||||
<a v-else :href="'lightning:' + receive.paymentReq">
|
v-else
|
||||||
<div v-html="invoiceQrCode"></div>
|
:href="'lightning:' + receive.paymentReq"
|
||||||
</a>
|
:value="'lightning:' + receive.paymentReq"
|
||||||
</div>
|
>
|
||||||
|
</lnbits-qrcode>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="q-my-md">
|
<h3 class="q-my-md">
|
||||||
<span v-text="formattedAmount"></span>
|
<span v-text="formattedAmount"></span>
|
||||||
@@ -898,12 +885,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn
|
|
||||||
outline
|
|
||||||
color="grey"
|
|
||||||
@click="copyText(receive.fiatPaymentReq || receive.paymentReq)"
|
|
||||||
:label="$t('copy_invoice')"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
<q-btn
|
||||||
v-close-popup
|
v-close-popup
|
||||||
flat
|
flat
|
||||||
|
|||||||
@@ -6,12 +6,8 @@ from fastapi import (
|
|||||||
Depends,
|
Depends,
|
||||||
HTTPException,
|
HTTPException,
|
||||||
)
|
)
|
||||||
from lnurl import (
|
from lnurl import LnurlResponseException
|
||||||
LnurlResponseException,
|
|
||||||
LnurlSuccessResponse,
|
|
||||||
)
|
|
||||||
from lnurl import execute_login as lnurlauth
|
from lnurl import execute_login as lnurlauth
|
||||||
from lnurl import execute_withdraw as lnurl_withdraw
|
|
||||||
from lnurl import handle as lnurl_handle
|
from lnurl import handle as lnurl_handle
|
||||||
from lnurl.models import (
|
from lnurl.models import (
|
||||||
LnurlAuthResponse,
|
LnurlAuthResponse,
|
||||||
@@ -22,7 +18,7 @@ from lnurl.models import (
|
|||||||
)
|
)
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.core.models import CreateLnurlWithdraw, Payment
|
from lnbits.core.models import Payment
|
||||||
from lnbits.core.models.lnurl import CreateLnurlPayment, LnurlScan
|
from lnbits.core.models.lnurl import CreateLnurlPayment, LnurlScan
|
||||||
from lnbits.decorators import (
|
from lnbits.decorators import (
|
||||||
WalletTypeInfo,
|
WalletTypeInfo,
|
||||||
@@ -138,38 +134,3 @@ async def api_payments_pay_lnurl(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return payment
|
return payment
|
||||||
|
|
||||||
|
|
||||||
@lnurl_router.post(
|
|
||||||
"/api/v1/payments/{payment_request}/pay-with-nfc", status_code=HTTPStatus.OK
|
|
||||||
)
|
|
||||||
async def api_payment_pay_with_nfc(
|
|
||||||
payment_request: str,
|
|
||||||
lnurl_data: CreateLnurlWithdraw,
|
|
||||||
) -> LnurlErrorResponse | LnurlSuccessResponse:
|
|
||||||
if not lnurl_data.lnurl_w.lud17:
|
|
||||||
return LnurlErrorResponse(reason="LNURL-withdraw lud17 not provided.")
|
|
||||||
try:
|
|
||||||
url = lnurl_data.lnurl_w.lud17
|
|
||||||
res = await lnurl_handle(url, user_agent=settings.user_agent, timeout=10)
|
|
||||||
except (LnurlResponseException, Exception) as exc:
|
|
||||||
return LnurlErrorResponse(reason=str(exc))
|
|
||||||
|
|
||||||
if not isinstance(res, LnurlWithdrawResponse):
|
|
||||||
return LnurlErrorResponse(reason="Invalid LNURL-withdraw response.")
|
|
||||||
try:
|
|
||||||
check_callback_url(res.callback)
|
|
||||||
except ValueError as exc:
|
|
||||||
return LnurlErrorResponse(reason=f"Invalid callback URL: {exc!s}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
res2 = await lnurl_withdraw(
|
|
||||||
res, payment_request, user_agent=settings.user_agent, timeout=10
|
|
||||||
)
|
|
||||||
except (LnurlResponseException, Exception) as exc:
|
|
||||||
logger.warning(exc)
|
|
||||||
return LnurlErrorResponse(reason=str(exc))
|
|
||||||
if not isinstance(res2, LnurlSuccessResponse | LnurlErrorResponse):
|
|
||||||
return LnurlErrorResponse(reason="Invalid LNURL-withdraw response.")
|
|
||||||
|
|
||||||
return res2
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from lnbits.core.crud.payments import (
|
|||||||
from lnbits.core.models import (
|
from lnbits.core.models import (
|
||||||
CancelInvoice,
|
CancelInvoice,
|
||||||
CreateInvoice,
|
CreateInvoice,
|
||||||
|
CreateLnurlWithdraw,
|
||||||
DecodePayment,
|
DecodePayment,
|
||||||
KeyType,
|
KeyType,
|
||||||
Payment,
|
Payment,
|
||||||
@@ -29,6 +30,7 @@ from lnbits.core.models import (
|
|||||||
PaymentHistoryPoint,
|
PaymentHistoryPoint,
|
||||||
PaymentWalletStats,
|
PaymentWalletStats,
|
||||||
SettleInvoice,
|
SettleInvoice,
|
||||||
|
SimpleStatus,
|
||||||
)
|
)
|
||||||
from lnbits.core.models.users import User
|
from lnbits.core.models.users import User
|
||||||
from lnbits.db import Filters, Page
|
from lnbits.db import Filters, Page
|
||||||
@@ -59,6 +61,7 @@ from ..services import (
|
|||||||
fee_reserve_total,
|
fee_reserve_total,
|
||||||
get_payments_daily_stats,
|
get_payments_daily_stats,
|
||||||
pay_invoice,
|
pay_invoice,
|
||||||
|
perform_withdraw,
|
||||||
settle_hold_invoice,
|
settle_hold_invoice,
|
||||||
update_pending_payment,
|
update_pending_payment,
|
||||||
update_pending_payments,
|
update_pending_payments,
|
||||||
@@ -359,3 +362,23 @@ async def api_payments_cancel(
|
|||||||
detail="Payment does not exist or does not belong to this wallet.",
|
detail="Payment does not exist or does not belong to this wallet.",
|
||||||
)
|
)
|
||||||
return await cancel_hold_invoice(payment)
|
return await cancel_hold_invoice(payment)
|
||||||
|
|
||||||
|
|
||||||
|
@payment_router.post("/{payment_request}/pay-with-nfc")
|
||||||
|
async def api_payment_pay_with_nfc(
|
||||||
|
payment_request: str,
|
||||||
|
lnurl_data: CreateLnurlWithdraw,
|
||||||
|
) -> SimpleStatus:
|
||||||
|
if not lnurl_data.lnurl_w.lud17:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail="LNURL-withdraw lud17 not provided.",
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await perform_withdraw(lnurl_data.lnurl_w.lud17, payment_request)
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST, detail=str(exc)
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
return SimpleStatus(success=True, message="Payment sent with NFC.")
|
||||||
|
|||||||
@@ -1,33 +1,20 @@
|
|||||||
from fastapi import (
|
from fastapi import APIRouter, WebSocket
|
||||||
APIRouter,
|
|
||||||
WebSocket,
|
|
||||||
WebSocketDisconnect,
|
|
||||||
)
|
|
||||||
|
|
||||||
from lnbits.settings import settings
|
from ..services import websocket_manager
|
||||||
|
|
||||||
from ..services import (
|
|
||||||
websocket_manager,
|
|
||||||
websocket_updater,
|
|
||||||
)
|
|
||||||
|
|
||||||
websocket_router = APIRouter(prefix="/api/v1/ws", tags=["Websocket"])
|
websocket_router = APIRouter(prefix="/api/v1/ws", tags=["Websocket"])
|
||||||
|
|
||||||
|
|
||||||
@websocket_router.websocket("/{item_id}")
|
@websocket_router.websocket("/{item_id}")
|
||||||
async def websocket_connect(websocket: WebSocket, item_id: str):
|
async def websocket_connect(websocket: WebSocket, item_id: str) -> None:
|
||||||
await websocket_manager.connect(websocket, item_id)
|
conn = await websocket_manager.connect(item_id, websocket)
|
||||||
try:
|
await websocket_manager.listen(conn)
|
||||||
while settings.lnbits_running:
|
|
||||||
await websocket.receive_text()
|
|
||||||
except WebSocketDisconnect:
|
|
||||||
websocket_manager.disconnect(websocket)
|
|
||||||
|
|
||||||
|
|
||||||
@websocket_router.post("/{item_id}")
|
@websocket_router.post("/{item_id}")
|
||||||
async def websocket_update_post(item_id: str, data: str):
|
async def websocket_update_post(item_id: str, data: str):
|
||||||
try:
|
try:
|
||||||
await websocket_updater(item_id, data)
|
await websocket_manager.send(item_id, data)
|
||||||
return {"sent": True, "data": data}
|
return {"sent": True, "data": data}
|
||||||
except Exception:
|
except Exception:
|
||||||
return {"sent": False, "data": data}
|
return {"sent": False, "data": data}
|
||||||
@@ -36,7 +23,7 @@ async def websocket_update_post(item_id: str, data: str):
|
|||||||
@websocket_router.get("/{item_id}/{data}")
|
@websocket_router.get("/{item_id}/{data}")
|
||||||
async def websocket_update_get(item_id: str, data: str):
|
async def websocket_update_get(item_id: str, data: str):
|
||||||
try:
|
try:
|
||||||
await websocket_updater(item_id, data)
|
await websocket_manager.send(item_id, data)
|
||||||
return {"sent": True, "data": data}
|
return {"sent": True, "data": data}
|
||||||
except Exception:
|
except Exception:
|
||||||
return {"sent": False, "data": data}
|
return {"sent": False, "data": data}
|
||||||
|
|||||||
+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
@@ -115,6 +115,7 @@ window.localisation.en = {
|
|||||||
payment_check: 'Check payment',
|
payment_check: 'Check payment',
|
||||||
not_enough_funds: 'Not enough funds!',
|
not_enough_funds: 'Not enough funds!',
|
||||||
search_by_tag_memo_amount: 'Search by tag, memo, amount',
|
search_by_tag_memo_amount: 'Search by tag, memo, amount',
|
||||||
|
search: 'Search',
|
||||||
invoice_waiting: 'Invoice waiting to be paid',
|
invoice_waiting: 'Invoice waiting to be paid',
|
||||||
payment_received: 'Payment Received',
|
payment_received: 'Payment Received',
|
||||||
payment_sent: 'Payment Sent',
|
payment_sent: 'Payment Sent',
|
||||||
|
|||||||
@@ -24,7 +24,11 @@ window.app.component('lnbits-qrcode-lnurl', {
|
|||||||
const bech32 = NostrTools.nip19.encodeBytes('lnurl', bytes)
|
const bech32 = NostrTools.nip19.encodeBytes('lnurl', bytes)
|
||||||
this.lnurl = `lightning:${bech32.toUpperCase()}`
|
this.lnurl = `lightning:${bech32.toUpperCase()}`
|
||||||
} else if (this.tab == 'lud17') {
|
} else if (this.tab == 'lud17') {
|
||||||
this.lnurl = this.url.replace('https://', this.prefix + '://')
|
if (this.url.startsWith('http://')) {
|
||||||
|
this.lnurl = this.url.replace('http://', this.prefix + '://')
|
||||||
|
} else {
|
||||||
|
this.lnurl = this.url.replace('https://', this.prefix + '://')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.$emit('update:lnurl', this.lnurl)
|
this.$emit('update:lnurl', this.lnurl)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,19 +9,123 @@ window.app.component('lnbits-qrcode', {
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
options: Object
|
nfc: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
showButtons: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
href: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
type: Number,
|
||||||
|
default: 3
|
||||||
|
},
|
||||||
|
maxWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 450
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
type: String,
|
||||||
|
default: LNBITS_QR_LOGO
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
custom: {
|
nfcTagWriting: false,
|
||||||
margin: 3,
|
nfcSupported: typeof NDEFReader != 'undefined'
|
||||||
width: 350,
|
|
||||||
size: 350,
|
|
||||||
logo: LNBITS_QR_LOGO
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
methods: {
|
||||||
this.custom = {...this.custom, ...this.options}
|
clickQrCode(event) {
|
||||||
|
if (this.href === '') {
|
||||||
|
this.copyText(this.value)
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async writeNfcTag() {
|
||||||
|
try {
|
||||||
|
if (!this.nfcSupported) {
|
||||||
|
throw {
|
||||||
|
toString: function () {
|
||||||
|
return 'NFC not supported on this device or browser.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ndef = new NDEFReader()
|
||||||
|
|
||||||
|
this.nfcTagWriting = true
|
||||||
|
this.$q.notify({
|
||||||
|
message: 'Tap your NFC tag to write the LNURL-withdraw link to it.'
|
||||||
|
})
|
||||||
|
|
||||||
|
await ndef.write({
|
||||||
|
records: [{recordType: 'url', data: this.value, lang: 'en'}]
|
||||||
|
})
|
||||||
|
|
||||||
|
this.nfcTagWriting = false
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'NFC tag written successfully.'
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
this.nfcTagWriting = false
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
message: error
|
||||||
|
? error.toString()
|
||||||
|
: 'An unexpected error has occurred.'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
downloadSVG() {
|
||||||
|
const filename = 'qrcode.svg'
|
||||||
|
const svg = this.$refs.qrCode.$el
|
||||||
|
if (!svg) {
|
||||||
|
console.error('SVG element not found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize the SVG content
|
||||||
|
const serializer = new XMLSerializer()
|
||||||
|
let source = serializer.serializeToString(svg)
|
||||||
|
|
||||||
|
// Add SVG namespace if not present
|
||||||
|
if (!source.match(/^<svg[^>]+xmlns="http:\/\/www\.w3\.org\/2000\/svg"/)) {
|
||||||
|
source = source.replace(
|
||||||
|
/^<svg/,
|
||||||
|
'<svg xmlns="http://www.w3.org/2000/svg"'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add XML declaration
|
||||||
|
source = '<?xml version="1.0" standalone="no"?>\n' + source
|
||||||
|
|
||||||
|
// Convert to Blob and trigger download
|
||||||
|
const blob = new Blob([source], {type: 'image/svg+xml;charset=utf-8'})
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = filename
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
document.body.removeChild(link)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$refs.qrCode.$el.style.maxWidth = this.maxWidth + 'px'
|
||||||
|
this.$refs.qrCode.$el.setAttribute('width', '100%')
|
||||||
|
this.$refs.qrCode.$el.removeAttribute('height')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ function confettiStars() {
|
|||||||
setTimeout(shoot, 200)
|
setTimeout(shoot, 200)
|
||||||
}
|
}
|
||||||
!(function (t, e) {
|
!(function (t, e) {
|
||||||
!(function t(e, n, a, i) {
|
;(!(function t(e, n, a, i) {
|
||||||
var o = !!(
|
var o = !!(
|
||||||
e.Worker &&
|
e.Worker &&
|
||||||
e.Blob &&
|
e.Blob &&
|
||||||
@@ -248,12 +248,12 @@ function confettiStars() {
|
|||||||
function e(e, n) {
|
function e(e, n) {
|
||||||
t.postMessage({options: e || {}, callback: n})
|
t.postMessage({options: e || {}, callback: n})
|
||||||
}
|
}
|
||||||
;(t.init = function (e) {
|
;((t.init = function (e) {
|
||||||
var n = e.transferControlToOffscreen()
|
var n = e.transferControlToOffscreen()
|
||||||
t.postMessage({canvas: n}, [n])
|
t.postMessage({canvas: n}, [n])
|
||||||
}),
|
}),
|
||||||
(t.fire = function (n, a, i) {
|
(t.fire = function (n, a, i) {
|
||||||
if (g) return e(n, null), g
|
if (g) return (e(n, null), g)
|
||||||
var o = Math.random().toString(36).slice(2)
|
var o = Math.random().toString(36).slice(2)
|
||||||
return (g = l(function (a) {
|
return (g = l(function (a) {
|
||||||
function r(e) {
|
function r(e) {
|
||||||
@@ -264,15 +264,15 @@ function confettiStars() {
|
|||||||
i(),
|
i(),
|
||||||
a())
|
a())
|
||||||
}
|
}
|
||||||
t.addEventListener('message', r),
|
;(t.addEventListener('message', r),
|
||||||
e(n, o),
|
e(n, o),
|
||||||
(m[o] = r.bind(null, {data: {callback: o}}))
|
(m[o] = r.bind(null, {data: {callback: o}})))
|
||||||
}))
|
}))
|
||||||
}),
|
}),
|
||||||
(t.reset = function () {
|
(t.reset = function () {
|
||||||
for (var e in (t.postMessage({reset: !0}), m))
|
for (var e in (t.postMessage({reset: !0}), m))
|
||||||
m[e](), delete m[e]
|
(m[e](), delete m[e])
|
||||||
})
|
}))
|
||||||
})(h)
|
})(h)
|
||||||
}
|
}
|
||||||
return h
|
return h
|
||||||
@@ -328,12 +328,12 @@ function confettiStars() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
function k(t) {
|
function k(t) {
|
||||||
;(t.width = document.documentElement.clientWidth),
|
;((t.width = document.documentElement.clientWidth),
|
||||||
(t.height = document.documentElement.clientHeight)
|
(t.height = document.documentElement.clientHeight))
|
||||||
}
|
}
|
||||||
function I(t) {
|
function I(t) {
|
||||||
var e = t.getBoundingClientRect()
|
var e = t.getBoundingClientRect()
|
||||||
;(t.width = e.width), (t.height = e.height)
|
;((t.width = e.width), (t.height = e.height))
|
||||||
}
|
}
|
||||||
function T(t, e, n, o, r) {
|
function T(t, e, n, o, r) {
|
||||||
var c,
|
var c,
|
||||||
@@ -342,10 +342,10 @@ function confettiStars() {
|
|||||||
d = t.getContext('2d'),
|
d = t.getContext('2d'),
|
||||||
f = l(function (e) {
|
f = l(function (e) {
|
||||||
function l() {
|
function l() {
|
||||||
;(c = s = null), d.clearRect(0, 0, o.width, o.height), r(), e()
|
;((c = s = null), d.clearRect(0, 0, o.width, o.height), r(), e())
|
||||||
}
|
}
|
||||||
;(c = b.frame(function e() {
|
;((c = b.frame(function e() {
|
||||||
!a ||
|
;(!a ||
|
||||||
(o.width === i.width && o.height === i.height) ||
|
(o.width === i.width && o.height === i.height) ||
|
||||||
((o.width = t.width = i.width), (o.height = t.height = i.height)),
|
((o.width = t.width = i.width), (o.height = t.height = i.height)),
|
||||||
o.width ||
|
o.width ||
|
||||||
@@ -354,7 +354,7 @@ function confettiStars() {
|
|||||||
d.clearRect(0, 0, o.width, o.height),
|
d.clearRect(0, 0, o.width, o.height),
|
||||||
(u = u.filter(function (t) {
|
(u = u.filter(function (t) {
|
||||||
return (function (t, e) {
|
return (function (t, e) {
|
||||||
;(e.x += Math.cos(e.angle2D) * e.velocity + e.drift),
|
;((e.x += Math.cos(e.angle2D) * e.velocity + e.drift),
|
||||||
(e.y += Math.sin(e.angle2D) * e.velocity + e.gravity),
|
(e.y += Math.sin(e.angle2D) * e.velocity + e.gravity),
|
||||||
(e.wobble += 0.1),
|
(e.wobble += 0.1),
|
||||||
(e.velocity *= e.decay),
|
(e.velocity *= e.decay),
|
||||||
@@ -363,7 +363,7 @@ function confettiStars() {
|
|||||||
(e.tiltCos = Math.cos(e.tiltAngle)),
|
(e.tiltCos = Math.cos(e.tiltAngle)),
|
||||||
(e.random = Math.random() + 5),
|
(e.random = Math.random() + 5),
|
||||||
(e.wobbleX = e.x + 10 * e.scalar * Math.cos(e.wobble)),
|
(e.wobbleX = e.x + 10 * e.scalar * Math.cos(e.wobble)),
|
||||||
(e.wobbleY = e.y + 10 * e.scalar * Math.sin(e.wobble))
|
(e.wobbleY = e.y + 10 * e.scalar * Math.sin(e.wobble)))
|
||||||
var n = e.tick++ / e.totalTicks,
|
var n = e.tick++ / e.totalTicks,
|
||||||
a = e.x + e.random * e.tiltCos,
|
a = e.x + e.random * e.tiltCos,
|
||||||
i = e.y + e.random * e.tiltSin,
|
i = e.y + e.random * e.tiltSin,
|
||||||
@@ -393,12 +393,12 @@ function confettiStars() {
|
|||||||
2 * Math.PI
|
2 * Math.PI
|
||||||
)
|
)
|
||||||
: (function (t, e, n, a, i, o, r, l, c) {
|
: (function (t, e, n, a, i, o, r, l, c) {
|
||||||
t.save(),
|
;(t.save(),
|
||||||
t.translate(e, n),
|
t.translate(e, n),
|
||||||
t.rotate(o),
|
t.rotate(o),
|
||||||
t.scale(a, i),
|
t.scale(a, i),
|
||||||
t.arc(0, 0, 1, r, l, c),
|
t.arc(0, 0, 1, r, l, c),
|
||||||
t.restore()
|
t.restore())
|
||||||
})(
|
})(
|
||||||
t,
|
t,
|
||||||
e.x,
|
e.x,
|
||||||
@@ -420,18 +420,18 @@ function confettiStars() {
|
|||||||
})(d, t)
|
})(d, t)
|
||||||
})).length
|
})).length
|
||||||
? (c = b.frame(e))
|
? (c = b.frame(e))
|
||||||
: l()
|
: l())
|
||||||
})),
|
})),
|
||||||
(s = l)
|
(s = l))
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
addFettis: function (t) {
|
addFettis: function (t) {
|
||||||
return (u = u.concat(t)), f
|
return ((u = u.concat(t)), f)
|
||||||
},
|
},
|
||||||
canvas: t,
|
canvas: t,
|
||||||
promise: f,
|
promise: f,
|
||||||
reset: function () {
|
reset: function () {
|
||||||
c && b.cancel(c), s && s()
|
;(c && b.cancel(c), s && s())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,7 +466,7 @@ function confettiStars() {
|
|||||||
k = p(e, 'scalar'),
|
k = p(e, 'scalar'),
|
||||||
I = (function (t) {
|
I = (function (t) {
|
||||||
var e = p(t, 'origin', Object)
|
var e = p(t, 'origin', Object)
|
||||||
return (e.x = p(e, 'x', Number)), (e.y = p(e, 'y', Number)), e
|
return ((e.x = p(e, 'x', Number)), (e.y = p(e, 'y', Number)), e)
|
||||||
})(e),
|
})(e),
|
||||||
E = d,
|
E = d,
|
||||||
S = [],
|
S = [],
|
||||||
@@ -531,7 +531,7 @@ function confettiStars() {
|
|||||||
return l(function (t) {
|
return l(function (t) {
|
||||||
t()
|
t()
|
||||||
})
|
})
|
||||||
i && a
|
;(i && a
|
||||||
? (t = a.canvas)
|
? (t = a.canvas)
|
||||||
: i &&
|
: i &&
|
||||||
!t &&
|
!t &&
|
||||||
@@ -547,7 +547,7 @@ function confettiStars() {
|
|||||||
)
|
)
|
||||||
})(g)),
|
})(g)),
|
||||||
document.body.appendChild(t)),
|
document.body.appendChild(t)),
|
||||||
r && !d && u(t)
|
r && !d && u(t))
|
||||||
var m = {width: t.width, height: t.height}
|
var m = {width: t.width, height: t.height}
|
||||||
function b() {
|
function b() {
|
||||||
if (s) {
|
if (s) {
|
||||||
@@ -564,9 +564,9 @@ function confettiStars() {
|
|||||||
m.width = m.height = null
|
m.width = m.height = null
|
||||||
}
|
}
|
||||||
function v() {
|
function v() {
|
||||||
;(a = null),
|
;((a = null),
|
||||||
r && e.removeEventListener('resize', b),
|
r && e.removeEventListener('resize', b),
|
||||||
i && t && (document.body.removeChild(t), (t = null), (d = !1))
|
i && t && (document.body.removeChild(t), (t = null), (d = !1)))
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
s && !d && s.init(t),
|
s && !d && s.init(t),
|
||||||
@@ -578,12 +578,13 @@ function confettiStars() {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
(g.reset = function () {
|
(g.reset = function () {
|
||||||
s && s.reset(), a && a.reset()
|
;(s && s.reset(), a && a.reset())
|
||||||
}),
|
}),
|
||||||
g
|
g
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
;(n.exports = E(null, {useWorker: !0, resize: !0})), (n.exports.create = E)
|
;((n.exports = E(null, {useWorker: !0, resize: !0})),
|
||||||
|
(n.exports.create = E))
|
||||||
})(
|
})(
|
||||||
(function () {
|
(function () {
|
||||||
return void 0 !== t ? t : 'undefined' != typeof self ? self : this || {}
|
return void 0 !== t ? t : 'undefined' != typeof self ? self : this || {}
|
||||||
@@ -591,5 +592,5 @@ function confettiStars() {
|
|||||||
e,
|
e,
|
||||||
!1
|
!1
|
||||||
),
|
),
|
||||||
(t.confetti = e.exports)
|
(t.confetti = e.exports))
|
||||||
})(window, {})
|
})(window, {})
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ window.WalletPageLogic = {
|
|||||||
payment_hash: null
|
payment_hash: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
invoiceQrCode: '',
|
|
||||||
disclaimerDialog: {
|
disclaimerDialog: {
|
||||||
show: false,
|
show: false,
|
||||||
location: window.location
|
location: window.location
|
||||||
@@ -313,13 +312,6 @@ window.WalletPageLogic = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hack as rendering in dialog causes reactivity issues. Does speed up, as only rendering lnbits-qrcode once.
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.invoiceQrCode = document.getElementById(
|
|
||||||
'hiddenQrCodeContainer'
|
|
||||||
).innerHTML
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
LNbits.utils.notifyApiError(err)
|
LNbits.utils.notifyApiError(err)
|
||||||
@@ -583,6 +575,7 @@ window.WalletPageLogic = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.catch(LNbits.utils.notifyApiError)
|
||||||
},
|
},
|
||||||
authLnurl() {
|
authLnurl() {
|
||||||
const dismissAuthMsg = Quasar.Notify.create({
|
const dismissAuthMsg = Quasar.Notify.create({
|
||||||
|
|||||||
@@ -617,21 +617,65 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id="lnbits-qrcode">
|
<template id="lnbits-qrcode">
|
||||||
<div class="qrcode__wrapper">
|
<div
|
||||||
<qrcode-vue
|
class="qrcode__outer"
|
||||||
:value="value"
|
:style="`margin: 13px auto; max-width: ${maxWidth}px`"
|
||||||
level="Q"
|
>
|
||||||
render-as="svg"
|
<div ref="qrWrapper" class="qrcode__wrapper">
|
||||||
:margin="custom.margin"
|
<a
|
||||||
:size="custom.width"
|
:href="href"
|
||||||
class="rounded-borders"
|
:title="href === '' ? value : href"
|
||||||
></qrcode-vue>
|
@click="clickQrCode"
|
||||||
<img
|
class="no-link full-width"
|
||||||
v-if="custom.logo"
|
>
|
||||||
class="qrcode__image"
|
<qrcode-vue
|
||||||
:src="custom.logo"
|
ref="qrCode"
|
||||||
alt="qrcode icon"
|
:value="value"
|
||||||
/>
|
:margin="margin"
|
||||||
|
:size="size"
|
||||||
|
level="Q"
|
||||||
|
render-as="svg"
|
||||||
|
class="rounded-borders q-mb-sm"
|
||||||
|
>
|
||||||
|
<q-tooltip :model-value="href === '' ? value : href"></q-tooltip>
|
||||||
|
</qrcode-vue>
|
||||||
|
</a>
|
||||||
|
<img
|
||||||
|
:src="logo"
|
||||||
|
class="qrcode__image"
|
||||||
|
alt="qrcode icon"
|
||||||
|
style="pointer-events: none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="showButtons"
|
||||||
|
class="qrcode__buttons row q-gutter-x-sm"
|
||||||
|
style="justify-content: flex-end"
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
v-if="nfc && nfcSupported"
|
||||||
|
:disabled="nfcTagWriting"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
class="text-grey"
|
||||||
|
icon="nfc"
|
||||||
|
@click="writeNfcTag"
|
||||||
|
>
|
||||||
|
<q-tooltip>Write NFC Tag</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn flat dense class="text-grey" icon="download" @click="downloadSVG">
|
||||||
|
<q-tooltip>Download SVG</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
class="text-grey"
|
||||||
|
@click="copyText(value)"
|
||||||
|
icon="content_copy"
|
||||||
|
>
|
||||||
|
<q-tooltip>Copy</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -640,17 +684,16 @@
|
|||||||
<q-tabs
|
<q-tabs
|
||||||
v-model="tab"
|
v-model="tab"
|
||||||
dense
|
dense
|
||||||
class="text-grey q-mb-md"
|
class="text-grey"
|
||||||
active-color="primary"
|
active-color="primary"
|
||||||
indicator-color="primary"
|
indicator-color="primary"
|
||||||
align="justify"
|
align="justify"
|
||||||
narrow-indicator
|
|
||||||
inline-label
|
inline-label
|
||||||
>
|
>
|
||||||
<q-tab name="bech32" icon="qr_code" label="bech32"></q-tab>
|
<q-tab name="bech32" icon="qr_code" label="bech32"></q-tab>
|
||||||
<q-tab name="lud17" icon="link" label="url (lud17)"></q-tab>
|
<q-tab name="lud17" icon="link" label="url (lud17)"></q-tab>
|
||||||
</q-tabs>
|
</q-tabs>
|
||||||
<lnbits-qrcode :value="lnurl" class="rounded-borders"></lnbits-qrcode>
|
<lnbits-qrcode :value="lnurl" nfc="true"></lnbits-qrcode>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -1044,42 +1087,22 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="props.row.isIn && props.row.isPending && props.row.bolt11"
|
v-if="props.row.isIn && props.row.isPending && props.row.bolt11"
|
||||||
class="text-center q-my-lg"
|
|
||||||
>
|
>
|
||||||
<div v-if="props.row.extra.fiat_payment_request">
|
<div v-if="props.row.extra.fiat_payment_request">
|
||||||
<a
|
<lnbits-qrcode
|
||||||
|
:value="props.row.extra.fiat_payment_request"
|
||||||
:href="props.row.extra.fiat_payment_request"
|
:href="props.row.extra.fiat_payment_request"
|
||||||
target="_blank"
|
:show-buttons="false"
|
||||||
>
|
></lnbits-qrcode>
|
||||||
<lnbits-qrcode
|
|
||||||
:value="props.row.extra.fiat_payment_request"
|
|
||||||
></lnbits-qrcode>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<a :href="'lightning:' + props.row.bolt11">
|
<lnbits-qrcode
|
||||||
<lnbits-qrcode
|
:value="'lightning:' + props.row.bolt11.toUpperCase()"
|
||||||
:value="'lightning:' + props.row.bolt11.toUpperCase()"
|
:href="'lightning:' + props.row.bolt11"
|
||||||
></lnbits-qrcode>
|
></lnbits-qrcode>
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
<div class="row q-mt-md">
|
||||||
<q-card-section>
|
|
||||||
<div class="row q-gutter-x-sm">
|
|
||||||
<q-btn
|
|
||||||
v-if="
|
|
||||||
props.row.isIn && props.row.isPending && props.row.bolt11
|
|
||||||
"
|
|
||||||
outline
|
|
||||||
color="grey"
|
|
||||||
@click="
|
|
||||||
copyText(
|
|
||||||
props.row.extra.fiat_payment_request || props.row.bolt11
|
|
||||||
)
|
|
||||||
"
|
|
||||||
:label="$t('copy_invoice')"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
<q-btn
|
||||||
outline
|
outline
|
||||||
color="grey"
|
color="grey"
|
||||||
@@ -1196,9 +1219,13 @@
|
|||||||
:endpoint="endpoint"
|
:endpoint="endpoint"
|
||||||
>
|
>
|
||||||
<template v-slot:actions>
|
<template v-slot:actions>
|
||||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
<q-btn
|
||||||
>Close</q-btn
|
v-close-popup
|
||||||
>
|
flat
|
||||||
|
color="grey"
|
||||||
|
class="q-ml-auto"
|
||||||
|
:label="$t('close')"
|
||||||
|
></q-btn>
|
||||||
</template>
|
</template>
|
||||||
</lnbits-extension-settings-form>
|
</lnbits-extension-settings-form>
|
||||||
</q-card>
|
</q-card>
|
||||||
@@ -1282,10 +1309,10 @@
|
|||||||
<q-dialog v-model="showQRDialog">
|
<q-dialog v-model="showQRDialog">
|
||||||
<q-card class="q-pa-md">
|
<q-card class="q-pa-md">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<lnbits-qrcode :value="qrValue" :size="200"></lnbits-qrcode>
|
<lnbits-qrcode :value="qrValue"></lnbits-qrcode>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
<q-btn flat label="Close" v-close-popup />
|
<q-btn flat :label="$t('close')" v-close-popup />
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|||||||
@@ -226,9 +226,7 @@ class PhoenixdWallet(Wallet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||||
r = await self.client.get(
|
r = await self.client.get(f"/payments/incoming/{checking_id}")
|
||||||
f"/payments/incoming/{checking_id}?all=true&limit=1000"
|
|
||||||
)
|
|
||||||
if r.is_error:
|
if r.is_error:
|
||||||
if r.status_code == 404:
|
if r.status_code == 404:
|
||||||
# invoice does not exist in phoenixd, so it was never paid
|
# invoice does not exist in phoenixd, so it was never paid
|
||||||
@@ -260,9 +258,7 @@ class PhoenixdWallet(Wallet):
|
|||||||
return PaymentPendingStatus()
|
return PaymentPendingStatus()
|
||||||
|
|
||||||
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||||
r = await self.client.get(
|
r = await self.client.get(f"/payments/outgoingbyhash/{checking_id}")
|
||||||
f"/payments/outgoing/{checking_id}?all=true&limit=1000"
|
|
||||||
)
|
|
||||||
if r.is_error:
|
if r.is_error:
|
||||||
if r.status_code == 404:
|
if r.status_code == 404:
|
||||||
# payment does not exist in phoenixd, so it was never paid
|
# payment does not exist in phoenixd, so it was never paid
|
||||||
|
|||||||
Generated
+4
-4
@@ -23,7 +23,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concat": "^1.0.3",
|
"concat": "^1.0.3",
|
||||||
"minify": "^9.2.0",
|
"minify": "^9.2.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.6.2",
|
||||||
"pyright": "1.1.289",
|
"pyright": "1.1.289",
|
||||||
"sass": "^1.78.0"
|
"sass": "^1.78.0"
|
||||||
}
|
}
|
||||||
@@ -1222,9 +1222,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "3.3.3",
|
"version": "3.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||||
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
|
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concat": "^1.0.3",
|
"concat": "^1.0.3",
|
||||||
"minify": "^9.2.0",
|
"minify": "^9.2.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.6.2",
|
||||||
"pyright": "1.1.289",
|
"pyright": "1.1.289",
|
||||||
"sass": "^1.78.0"
|
"sass": "^1.78.0"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+4
-4
@@ -2134,14 +2134,14 @@ valkey = ["valkey (>=6)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lnurl"
|
name = "lnurl"
|
||||||
version = "0.8.0"
|
version = "0.8.3"
|
||||||
description = "LNURL implementation for Python."
|
description = "LNURL implementation for Python."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10"
|
python-versions = ">=3.10"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "lnurl-0.8.0-py3-none-any.whl", hash = "sha256:260be16dbcc45fd7dd937f781bd79c9fdaf38b4bb04d6e13a1820f24eac9389c"},
|
{file = "lnurl-0.8.3-py3-none-any.whl", hash = "sha256:670cdeaef2c55de986dad89126ab58275d5199ba6554a93d9965d1e162080c2a"},
|
||||||
{file = "lnurl-0.8.0.tar.gz", hash = "sha256:fa970f7d343caae7435848c592dd223d7ac8120e337d2d5e2d3ea23a0f8f84ef"},
|
{file = "lnurl-0.8.3.tar.gz", hash = "sha256:8ca73af84fb9ee36a184d731d165f289ba7bc6260d4dadb2b6cf24f381c3afba"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -4592,4 +4592,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 = "2cd12b894b669ffac32a2584e899fcc6116c625b5b59d16586fe5ec1b1edc5f0"
|
content-hash = "6b3b2f3c3163bc7a7bc2029ff6dd67f67c37057d9efbdf866b9d744f9e4ee9a5"
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "lnbits"
|
name = "lnbits"
|
||||||
version = "1.3.0-rc5"
|
version = "1.3.0-rc7"
|
||||||
requires-python = ">=3.10,<3.13"
|
requires-python = ">=3.10,<3.13"
|
||||||
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
||||||
authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }]
|
authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }]
|
||||||
@@ -14,7 +14,7 @@ dependencies = [
|
|||||||
"starlette==0.47.1",
|
"starlette==0.47.1",
|
||||||
"httpx==0.27.0",
|
"httpx==0.27.0",
|
||||||
"jinja2==3.1.6",
|
"jinja2==3.1.6",
|
||||||
"lnurl==0.8.0",
|
"lnurl==0.8.3",
|
||||||
"pydantic==1.10.22",
|
"pydantic==1.10.22",
|
||||||
"pyqrcode==1.2.1",
|
"pyqrcode==1.2.1",
|
||||||
"shortuuid==1.0.13",
|
"shortuuid==1.0.13",
|
||||||
|
|||||||
+9
-20
@@ -1,5 +1,4 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
from http import HTTPStatus
|
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
from unittest.mock import AsyncMock, Mock
|
from unittest.mock import AsyncMock, Mock
|
||||||
|
|
||||||
@@ -591,31 +590,26 @@ async def test_fiat_tracking(client, adminkey_headers_from, settings: Settings):
|
|||||||
"minWithdrawable": 1000,
|
"minWithdrawable": 1000,
|
||||||
"maxWithdrawable": 1_500_000,
|
"maxWithdrawable": 1_500_000,
|
||||||
},
|
},
|
||||||
{
|
{"status": "OK"},
|
||||||
"status": "OK",
|
{"success": True, "message": "Payment sent with NFC."},
|
||||||
},
|
|
||||||
{
|
|
||||||
"status": "OK",
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
# Error loading LNURL request
|
# Error loading LNURL request
|
||||||
(
|
(
|
||||||
"error_loading_lnurl",
|
"error_loading_lnurl",
|
||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"detail": "Error loading callback request",
|
||||||
"reason": "Error loading callback request",
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# LNURL response with error status
|
# LNURL response with error status
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"status": "ERROR",
|
||||||
|
"reason": "Invalid LNURL-withdraw response.",
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"detail": "Invalid LNURL-withdraw response.",
|
||||||
"reason": "Invalid LNURL-withdraw response.",
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# Invalid LNURL-withdraw pay request
|
# Invalid LNURL-withdraw pay request
|
||||||
@@ -629,8 +623,7 @@ async def test_fiat_tracking(client, adminkey_headers_from, settings: Settings):
|
|||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"detail": "Invalid LNURL-withdraw response.",
|
||||||
"reason": "Invalid LNURL-withdraw response.",
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# Error loading callback request
|
# Error loading callback request
|
||||||
@@ -644,8 +637,7 @@ async def test_fiat_tracking(client, adminkey_headers_from, settings: Settings):
|
|||||||
},
|
},
|
||||||
"error_loading_callback",
|
"error_loading_callback",
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"detail": "Error loading callback request",
|
||||||
"reason": "Error loading callback request",
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# Callback response with error status
|
# Callback response with error status
|
||||||
@@ -662,8 +654,7 @@ async def test_fiat_tracking(client, adminkey_headers_from, settings: Settings):
|
|||||||
"reason": "Callback failed",
|
"reason": "Callback failed",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"detail": "Callback failed",
|
||||||
"reason": "Callback failed",
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# Unexpected exception during LNURL response JSON parsing
|
# Unexpected exception during LNURL response JSON parsing
|
||||||
@@ -671,8 +662,7 @@ async def test_fiat_tracking(client, adminkey_headers_from, settings: Settings):
|
|||||||
"exception_in_lnurl_response_json",
|
"exception_in_lnurl_response_json",
|
||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"detail": "Invalid JSON response from https://example.com/lnurl",
|
||||||
"reason": "Invalid JSON response from https://example.com/lnurl",
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -768,7 +758,6 @@ async def test_api_payment_pay_with_nfc(
|
|||||||
json={"lnurl_w": lnurl},
|
json={"lnurl_w": lnurl},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == HTTPStatus.OK
|
|
||||||
assert response.json() == expected_response
|
assert response.json() == expected_response
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2474,7 +2474,7 @@
|
|||||||
},
|
},
|
||||||
"phoenixd": {
|
"phoenixd": {
|
||||||
"get_payment_status_endpoint": {
|
"get_payment_status_endpoint": {
|
||||||
"uri": "/payments/outgoing/e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
"uri": "/payments/outgoingbyhash/e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Authorization": "Basic OmYxNzFiYTAyMmE3NjRlNjc5ZWVmOTUwYjIxZmIxYzA0ZjE3MWJhMDIyYTc2NGU2NzllZWY5NTBiMjFmYjFjMDQ=",
|
"Authorization": "Basic OmYxNzFiYTAyMmE3NjRlNjc5ZWVmOTUwYjIxZmIxYzA0ZjE3MWJhMDIyYTc2NGU2NzllZWY5NTBiMjFmYjFjMDQ=",
|
||||||
"User-Agent": "LNbits/Tests"
|
"User-Agent": "LNbits/Tests"
|
||||||
|
|||||||
@@ -1260,7 +1260,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lnbits"
|
name = "lnbits"
|
||||||
version = "1.3.0rc5"
|
version = "1.3.0rc7"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiosqlite" },
|
{ name = "aiosqlite" },
|
||||||
@@ -1363,7 +1363,7 @@ requires-dist = [
|
|||||||
{ name = "itsdangerous", specifier = "==2.2.0" },
|
{ name = "itsdangerous", specifier = "==2.2.0" },
|
||||||
{ name = "jinja2", specifier = "==3.1.6" },
|
{ name = "jinja2", specifier = "==3.1.6" },
|
||||||
{ name = "jsonpath-ng", specifier = "==1.7.0" },
|
{ name = "jsonpath-ng", specifier = "==1.7.0" },
|
||||||
{ name = "lnurl", specifier = "==0.8.0" },
|
{ name = "lnurl", specifier = "==0.8.3" },
|
||||||
{ name = "loguru", specifier = "==0.7.3" },
|
{ name = "loguru", specifier = "==0.7.3" },
|
||||||
{ name = "nostr-sdk", specifier = "==0.42.1" },
|
{ name = "nostr-sdk", specifier = "==0.42.1" },
|
||||||
{ name = "packaging", specifier = "==25.0" },
|
{ name = "packaging", specifier = "==25.0" },
|
||||||
@@ -1418,7 +1418,7 @@ dev = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lnurl"
|
name = "lnurl"
|
||||||
version = "0.8.0"
|
version = "0.8.3"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "bech32" },
|
{ name = "bech32" },
|
||||||
@@ -1429,9 +1429,9 @@ dependencies = [
|
|||||||
{ name = "pycryptodomex" },
|
{ name = "pycryptodomex" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/8f/b2/9e4f4ce76c50825b5ae4935affa877941f41ce8e44ef0f9c5a6757c1243f/lnurl-0.8.0.tar.gz", hash = "sha256:fa970f7d343caae7435848c592dd223d7ac8120e337d2d5e2d3ea23a0f8f84ef", size = 17008, upload-time = "2025-08-21T12:30:11.315Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/9b/dc/29cce6bb8688622c7f5f6db4355c54b5c87bb05be58e49374fd2c6f4b0f2/lnurl-0.8.3.tar.gz", hash = "sha256:8ca73af84fb9ee36a184d731d165f289ba7bc6260d4dadb2b6cf24f381c3afba", size = 17171, upload-time = "2025-09-08T13:30:56.636Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f3/16/fdfb77478b95b7863d7822683a1fed34c0ef66bc857d8ebc52687b5f3833/lnurl-0.8.0-py3-none-any.whl", hash = "sha256:260be16dbcc45fd7dd937f781bd79c9fdaf38b4bb04d6e13a1820f24eac9389c", size = 17280, upload-time = "2025-08-21T12:30:10.32Z" },
|
{ url = "https://files.pythonhosted.org/packages/fd/99/e400734afb7469a0cfa661259de2bb66417d6c3f14361444a010e9d186ee/lnurl-0.8.3-py3-none-any.whl", hash = "sha256:670cdeaef2c55de986dad89126ab58275d5199ba6554a93d9965d1e162080c2a", size = 17459, upload-time = "2025-09-08T13:30:55.691Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user