mirror of
https://github.com/lnbits/lnbits.git
synced 2025-09-25 11:14:02 +02:00
fix: lnurlp, withdraw
This commit is contained in:
@@ -27,4 +27,3 @@ print(
|
||||
- service fee: {SERVICE_FEE}
|
||||
"""
|
||||
)
|
||||
|
||||
|
@@ -1,34 +1,48 @@
|
||||
import asyncio
|
||||
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from starlette.routing import Mount
|
||||
|
||||
from lnbits.db import Database
|
||||
from lnbits.helpers import template_renderer
|
||||
from lnbits.tasks import catch_everything_and_restart
|
||||
|
||||
db = Database("ext_lnurlp")
|
||||
|
||||
lnurlp_static_files = [
|
||||
{
|
||||
"path": "/lnurlp/static",
|
||||
"app": StaticFiles(directory="lnbits/extensions/lnurlp/static"),
|
||||
"name": "lnurlp_static",
|
||||
}
|
||||
]
|
||||
|
||||
lnurlp_ext: APIRouter = APIRouter(
|
||||
prefix="/lnurlp",
|
||||
static_folder="static",
|
||||
tags=["lnurlp"]
|
||||
# "lnurlp", __name__, static_folder="static", template_folder="templates"
|
||||
)
|
||||
|
||||
def lnurlp_renderer():
|
||||
return template_renderer(
|
||||
[
|
||||
"lnbits/extensions/lnticket/templates",
|
||||
"lnbits/extensions/lnurlp/templates",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
from .views_api import * # noqa
|
||||
from .views import * # noqa
|
||||
from .tasks import wait_for_paid_invoices
|
||||
|
||||
def lnurlp_start():
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
|
||||
|
||||
|
||||
@lnurlp_ext.on_event("startup")
|
||||
def _do_it():
|
||||
register_listeners()
|
||||
|
||||
# from .lnurl import * # noqa
|
||||
# from .tasks import register_listeners
|
||||
|
||||
# from lnbits.tasks import record_async
|
||||
|
||||
|
@@ -2,25 +2,17 @@ from typing import List, Optional, Union
|
||||
|
||||
from lnbits.db import SQLITE
|
||||
from . import db
|
||||
from .models import PayLink
|
||||
from .models import PayLink, CreatePayLinkData
|
||||
|
||||
|
||||
async def create_pay_link(
|
||||
*,
|
||||
wallet_id: str,
|
||||
description: str,
|
||||
min: int,
|
||||
max: int,
|
||||
comment_chars: int = 0,
|
||||
currency: Optional[str] = None,
|
||||
webhook_url: Optional[str] = None,
|
||||
success_text: Optional[str] = None,
|
||||
success_url: Optional[str] = None,
|
||||
data: CreatePayLinkData,
|
||||
wallet_id: str
|
||||
) -> PayLink:
|
||||
|
||||
returning = "" if db.type == SQLITE else "RETURNING ID"
|
||||
method = db.execute if db.type == SQLITE else db.fetchone
|
||||
|
||||
print("CPL", wallet_id, data)
|
||||
result = await (method)(
|
||||
f"""
|
||||
INSERT INTO lnurlp.pay_links (
|
||||
@@ -41,14 +33,14 @@ async def create_pay_link(
|
||||
""",
|
||||
(
|
||||
wallet_id,
|
||||
description,
|
||||
min,
|
||||
max,
|
||||
webhook_url,
|
||||
success_text,
|
||||
success_url,
|
||||
comment_chars,
|
||||
currency,
|
||||
data.description,
|
||||
data.min,
|
||||
data.max,
|
||||
data.webhook_url,
|
||||
data.success_text,
|
||||
data.success_url,
|
||||
data.comment_chars,
|
||||
data.currency,
|
||||
),
|
||||
)
|
||||
if db.type == SQLITE:
|
||||
|
@@ -11,7 +11,7 @@ from . import lnurlp_ext
|
||||
from .crud import increment_pay_link
|
||||
|
||||
|
||||
@lnurlp_ext.get("/api/v1/lnurl/{link_id}", status_code=HTTPStatus.OK)
|
||||
@lnurlp_ext.get("/api/v1/lnurl/{link_id}", status_code=HTTPStatus.OK, name="lnurlp.api_lnurl_response")
|
||||
async def api_lnurl_response(request: Request, link_id):
|
||||
link = await increment_pay_link(link_id, served_meta=1)
|
||||
if not link:
|
||||
|
@@ -1,12 +1,23 @@
|
||||
import json
|
||||
from urllib.parse import urlparse, urlunparse, parse_qs, urlencode, ParseResult
|
||||
from quart import url_for
|
||||
from starlette.requests import Request
|
||||
from fastapi.param_functions import Query
|
||||
from typing import Optional, Dict
|
||||
from lnbits.lnurl import encode as lnurl_encode # type: ignore
|
||||
from lnurl.types import LnurlPayMetadata # type: ignore
|
||||
from sqlite3 import Row
|
||||
from pydantic import BaseModel
|
||||
|
||||
class CreatePayLinkData(BaseModel):
|
||||
description: str
|
||||
min: int = Query(0.01, ge=0.01)
|
||||
max: int = Query(0.01, ge=0.01)
|
||||
currency: str = Query(None)
|
||||
comment_chars: int = Query(0, ge=0, lt=800)
|
||||
webhook_url: str = Query(None)
|
||||
success_text: str = Query(None)
|
||||
success_url: str = Query(None)
|
||||
|
||||
class PayLink(BaseModel):
|
||||
id: int
|
||||
wallet: str
|
||||
@@ -14,10 +25,10 @@ class PayLink(BaseModel):
|
||||
min: int
|
||||
served_meta: int
|
||||
served_pr: int
|
||||
webhook_url: str
|
||||
success_text: str
|
||||
success_url: str
|
||||
currency: str
|
||||
webhook_url: Optional[str]
|
||||
success_text: Optional[str]
|
||||
success_url: Optional[str]
|
||||
currency: Optional[str]
|
||||
comment_chars: int
|
||||
max: int
|
||||
|
||||
@@ -28,7 +39,8 @@ class PayLink(BaseModel):
|
||||
|
||||
@property
|
||||
def lnurl(self) -> str:
|
||||
url = url_for("lnurlp.api_lnurl_response", link_id=self.id, _external=True)
|
||||
r = Request
|
||||
url = r.url_for("lnurlp.api_lnurl_response", link_id=self.id, _external=True)
|
||||
return lnurl_encode(url)
|
||||
|
||||
@property
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import trio
|
||||
import asyncio
|
||||
import json
|
||||
import httpx
|
||||
|
||||
@@ -9,17 +9,14 @@ from lnbits.tasks import register_invoice_listener
|
||||
from .crud import get_pay_link
|
||||
|
||||
|
||||
async def register_listeners():
|
||||
invoice_paid_chan_send, invoice_paid_chan_recv = trio.open_memory_channel(2)
|
||||
register_invoice_listener(invoice_paid_chan_send)
|
||||
await wait_for_paid_invoices(invoice_paid_chan_recv)
|
||||
async def wait_for_paid_invoices():
|
||||
invoice_queue = asyncio.Queue()
|
||||
register_invoice_listener(invoice_queue)
|
||||
|
||||
|
||||
async def wait_for_paid_invoices(invoice_paid_chan: trio.MemoryReceiveChannel):
|
||||
async for payment in invoice_paid_chan:
|
||||
while True:
|
||||
payment = await invoice_queue.get()
|
||||
await on_invoice_paid(payment)
|
||||
|
||||
|
||||
async def on_invoice_paid(payment: Payment) -> None:
|
||||
if "lnurlp" != payment.extra.get("tag"):
|
||||
# not an lnurlp invoice
|
||||
|
@@ -18,7 +18,7 @@
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||
<code
|
||||
>curl -X GET {{ request.url_root }}api/v1/links -H "X-Api-Key: {{
|
||||
g.user.wallets[0].inkey }}"
|
||||
user.wallets[0].inkey }}"
|
||||
</code>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
@@ -27,7 +27,8 @@
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<code
|
||||
><span class="text-blue">GET</span> /lnurlp/api/v1/links/<pay_id></code
|
||||
><span class="text-blue">GET</span>
|
||||
/lnurlp/api/v1/links/<pay_id></code
|
||||
>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
||||
<code>{"X-Api-Key": <invoice_key>}</code><br />
|
||||
@@ -39,7 +40,7 @@
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||
<code
|
||||
>curl -X GET {{ request.url_root }}api/v1/links/<pay_id> -H
|
||||
"X-Api-Key: {{ g.user.wallets[0].inkey }}"
|
||||
"X-Api-Key: {{ user.wallets[0].inkey }}"
|
||||
</code>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
@@ -56,7 +57,11 @@
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
||||
<code>{"X-Api-Key": <admin_key>}</code><br />
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
||||
<code>{"description": <string> "amount": <integer> "max": <integer> "min": <integer> "comment_chars": <integer>}</code>
|
||||
<code
|
||||
>{"description": <string> "amount": <integer> "max":
|
||||
<integer> "min": <integer> "comment_chars":
|
||||
<integer>}</code
|
||||
>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||
Returns 201 CREATED (application/json)
|
||||
</h5>
|
||||
@@ -64,8 +69,10 @@
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||
<code
|
||||
>curl -X POST {{ request.url_root }}api/v1/links -d '{"description":
|
||||
<string>, "amount": <integer>, "max": <integer>, "min": <integer>, "comment_chars": <integer>}' -H "Content-type:
|
||||
application/json" -H "X-Api-Key: {{ g.user.wallets[0].adminkey }}"
|
||||
<string>, "amount": <integer>, "max": <integer>,
|
||||
"min": <integer>, "comment_chars": <integer>}' -H
|
||||
"Content-type: application/json" -H "X-Api-Key: {{
|
||||
user.wallets[0].adminkey }}"
|
||||
</code>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
@@ -95,7 +102,7 @@
|
||||
>curl -X PUT {{ request.url_root }}api/v1/links/<pay_id> -d
|
||||
'{"description": <string>, "amount": <integer>}' -H
|
||||
"Content-type: application/json" -H "X-Api-Key: {{
|
||||
g.user.wallets[0].adminkey }}"
|
||||
user.wallets[0].adminkey }}"
|
||||
</code>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
@@ -120,7 +127,7 @@
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||
<code
|
||||
>curl -X DELETE {{ request.url_root }}api/v1/links/<pay_id> -H
|
||||
"X-Api-Key: {{ g.user.wallets[0].adminkey }}"
|
||||
"X-Api-Key: {{ user.wallets[0].adminkey }}"
|
||||
</code>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
from http import HTTPStatus
|
||||
|
||||
from lnbits.decorators import check_user_exists, validate_uuids
|
||||
from lnbits.decorators import check_user_exists
|
||||
|
||||
from . import lnurlp_ext, lnurlp_renderer
|
||||
from .crud import get_pay_link
|
||||
@@ -15,7 +15,7 @@ from lnbits.core.models import User
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
@lnurlp_ext.get("/", response_class=HTMLResponse)
|
||||
@validate_uuids(["usr"], required=True)
|
||||
# @validate_uuids(["usr"], required=True)
|
||||
# @check_user_exists()
|
||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||
return lnurlp_renderer().TemplateResponse("lnurlp/index.html", {"request": request, "user": user.dict()})
|
||||
|
@@ -10,8 +10,9 @@ from starlette.requests import Request
|
||||
from starlette.responses import HTMLResponse, JSONResponse # type: ignore
|
||||
|
||||
from lnbits.core.crud import get_user
|
||||
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
|
||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
||||
from lnbits.utils.exchange_rates import currencies, get_fiat_rate_satoshis
|
||||
from .models import CreatePayLinkData
|
||||
|
||||
from . import lnurlp_ext
|
||||
from .crud import (
|
||||
@@ -75,20 +76,11 @@ async def api_link_retrieve(link_id, wallet: WalletTypeInfo = Depends(get_key_ty
|
||||
|
||||
return {**link._asdict(), **{"lnurl": link.lnurl}}
|
||||
|
||||
class CreateData(BaseModel):
|
||||
description: str
|
||||
min: int = Query(0.01, ge=0.01)
|
||||
max: int = Query(0.01, ge=0.01)
|
||||
currency: Optional[str]
|
||||
comment_chars: int = Query(0, ge=0, lt=800)
|
||||
webhook_url: Optional[str]
|
||||
success_text: Optional[str]
|
||||
success_url: Optional[str]
|
||||
|
||||
@lnurlp_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED)
|
||||
@lnurlp_ext.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
|
||||
# @api_check_wallet_key("invoice")
|
||||
async def api_link_create_or_update(data: CreateData, link_id=None, wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||
async def api_link_create_or_update(data: CreatePayLinkData, link_id=None, wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||
if data.min > data.max:
|
||||
raise HTTPException(
|
||||
detail="Min is greater than max.",
|
||||
@@ -128,18 +120,18 @@ async def api_link_create_or_update(data: CreateData, link_id=None, wallet: Wall
|
||||
# HTTPStatus.NOT_FOUND,
|
||||
# )
|
||||
|
||||
if link.wallet != g.wallet.id:
|
||||
if link.wallet != wallet.wallet.id:
|
||||
raise HTTPException(
|
||||
detail="Not your pay link.",
|
||||
status_code=HTTPStatus.FORBIDDEN
|
||||
)
|
||||
# return {"message": "Not your pay link."}, HTTPStatus.FORBIDDEN
|
||||
|
||||
link = await update_pay_link(link_id, **data)
|
||||
link = await update_pay_link(link_id, data)
|
||||
else:
|
||||
link = await create_pay_link(wallet_id=wallet.wallet.id, **data)
|
||||
|
||||
return {**link._asdict(), **{"lnurl": link.lnurl}}
|
||||
link = await create_pay_link(data, wallet_id=wallet.wallet.id)
|
||||
print("LINK", link)
|
||||
return {**link.dict(), "lnurl": link.lnurl}
|
||||
|
||||
|
||||
@lnurlp_ext.delete("/api/v1/links/{link_id}")
|
||||
|
@@ -1,13 +1,23 @@
|
||||
from fastapi import APIRouter
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from lnbits.db import Database
|
||||
from lnbits.helpers import template_renderer
|
||||
|
||||
db = Database("ext_withdraw")
|
||||
|
||||
withdraw_static_files = [
|
||||
{
|
||||
"path": "/withdraw/static",
|
||||
"app": StaticFiles(directory="lnbits/extensions/withdraw/static"),
|
||||
"name": "withdraw_static",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
withdraw_ext: APIRouter = APIRouter(
|
||||
prefix="/withdraw",
|
||||
static_folder="static"
|
||||
tags=["withdraw"],
|
||||
# "withdraw", __name__, static_folder="static", template_folder="templates"
|
||||
)
|
||||
|
||||
@@ -23,6 +33,7 @@ from .views_api import * # noqa
|
||||
from .views import * # noqa
|
||||
from .lnurl import * # noqa
|
||||
|
||||
@withdraw_ext.on_event("startup")
|
||||
def _do_it():
|
||||
register_listeners()
|
||||
|
||||
# @withdraw_ext.on_event("startup")
|
||||
# def _do_it():
|
||||
# register_listeners()
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import shortuuid # type: ignore
|
||||
from http import HTTPStatus
|
||||
from datetime import datetime
|
||||
from quart import jsonify, request
|
||||
|
||||
from lnbits.core.services import pay_invoice
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from quart import url_for
|
||||
from fastapi import Request
|
||||
from lnurl import Lnurl, LnurlWithdrawResponse, encode as lnurl_encode # type: ignore
|
||||
from sqlite3 import Row
|
||||
from pydantic import BaseModel
|
||||
@@ -33,19 +33,19 @@ class WithdrawLink(BaseModel):
|
||||
return self.used >= self.uses
|
||||
|
||||
@property
|
||||
def lnurl(self) -> Lnurl:
|
||||
def lnurl(self, req: Request) -> Lnurl:
|
||||
if self.is_unique:
|
||||
usescssv = self.usescsv.split(",")
|
||||
tohash = self.id + self.unique_hash + usescssv[self.number]
|
||||
multihash = shortuuid.uuid(name=tohash)
|
||||
url = url_for(
|
||||
url = req.url_for(
|
||||
"withdraw.api_lnurl_multi_response",
|
||||
unique_hash=self.unique_hash,
|
||||
id_unique_hash=multihash,
|
||||
_external=True,
|
||||
)
|
||||
else:
|
||||
url = url_for(
|
||||
url = req.url_for(
|
||||
"withdraw.api_lnurl_response",
|
||||
unique_hash=self.unique_hash,
|
||||
_external=True,
|
||||
@@ -55,7 +55,7 @@ class WithdrawLink(BaseModel):
|
||||
|
||||
@property
|
||||
def lnurl_response(self) -> LnurlWithdrawResponse:
|
||||
url = url_for(
|
||||
url = req.url_for(
|
||||
"withdraw.api_lnurl_callback", unique_hash=self.unique_hash, _external=True
|
||||
)
|
||||
return LnurlWithdrawResponse(
|
||||
|
@@ -1,7 +1,7 @@
|
||||
from http import HTTPStatus
|
||||
import pyqrcode
|
||||
from io import BytesIO
|
||||
from lnbits.decorators import check_user_exists, validate_uuids
|
||||
from lnbits.decorators import check_user_exists
|
||||
|
||||
from . import withdraw_ext, withdraw_renderer
|
||||
from .crud import get_withdraw_link, chunks
|
||||
@@ -16,7 +16,7 @@ from lnbits.core.models import User
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
@withdraw_ext.get("/", response_class=HTMLResponse)
|
||||
@validate_uuids(["usr"], required=True)
|
||||
# @validate_uuids(["usr"], required=True)
|
||||
# @check_user_exists()
|
||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||
return withdraw_renderer().TemplateResponse("withdraw/index.html", {"request":request,"user": user.dict()})
|
||||
@@ -36,7 +36,7 @@ async def display(request: Request, link_id):
|
||||
|
||||
|
||||
@withdraw_ext.get("/img/{link_id}", response_class=HTMLResponse)
|
||||
async def img(request: Request, link_id, response: Response):
|
||||
async def img(request: Request, link_id):
|
||||
link = await get_withdraw_link(link_id, 0)
|
||||
if not link:
|
||||
raise HTTPException(
|
||||
|
@@ -9,7 +9,7 @@ from starlette.requests import Request
|
||||
from starlette.responses import HTMLResponse, JSONResponse # type: ignore
|
||||
|
||||
from lnbits.core.crud import get_user
|
||||
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
|
||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
||||
|
||||
# from fastapi import FastAPI, Query, Response
|
||||
|
||||
@@ -83,8 +83,8 @@ class CreateData(BaseModel):
|
||||
|
||||
@withdraw_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED)
|
||||
@withdraw_ext.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
|
||||
@api_check_wallet_key("admin")
|
||||
async def api_link_create_or_update(data: CreateData, link_id: str = None, response: Response):
|
||||
# @api_check_wallet_key("admin")
|
||||
async def api_link_create_or_update(data: CreateData, link_id: str = None):
|
||||
if data.max_withdrawable < data.min_withdrawable:
|
||||
raise HTTPException(
|
||||
detail="`max_withdrawable` needs to be at least `min_withdrawable`.",
|
||||
|
Reference in New Issue
Block a user