Skip to content

Commit

Permalink
feat: lnurl endpoint
Browse files Browse the repository at this point in the history
todo: qrcode
  • Loading branch information
dni committed Jan 7, 2025
1 parent dc52a1f commit 3df5b1e
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 1 deletion.
4 changes: 3 additions & 1 deletion __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
from .tasks import wait_for_paid_invoices
from .views import pay2print_ext_generic
from .views_api import pay2print_ext_api
from .views_lnurl import pay2print_lnurl_router

scheduled_tasks: list[asyncio.Task] = []

pay2print_ext: APIRouter = APIRouter(prefix="/pay2print", tags=["pay2print"])
pay2print_ext.include_router(pay2print_ext_generic)
pay2print_ext.include_router(pay2print_ext_api)
pay2print_ext.include_router(pay2print_lnurl_router)

pay2print_static_files = [
{
Expand Down Expand Up @@ -42,8 +44,8 @@ def pay2print_start():
__all__ = [
"db",
"pay2print_ext",
"pay2print_static_files",
"pay2print_start",
"pay2print_static_files",
"pay2print_stop",
"print_file",
]
10 changes: 10 additions & 0 deletions static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ window.app = Vue.createApp({
printerDialog: {
show: false,
data: {}
},
qrDialog: {
show: false,
data: {}
}
}
},
Expand Down Expand Up @@ -96,6 +100,12 @@ window.app = Vue.createApp({
this.printerDialog.data = {...printer}
this.printerDialog.show = true
},
openLnurlQrCode(printer_id) {
// const printer = this.printers.find(printer => printer.id === printer_id)
const lnurl = `${window.location.origin}/pay2print/api/v1/lnurl/${printer_id}`
this.qrDialog.data = { lnurl: lnurl }
this.qrDialog.show = true
},
openFile(payment_hash) {
return `/pay2print/api/v1/file/${payment_hash}`
},
Expand Down
27 changes: 27 additions & 0 deletions templates/pay2print/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@
%} {% block scripts %} {{ window_vars(user) }}
<script src="{{ static_url_for('pay2print/static', path='js/index.js') }}"></script>
{% endblock %} {% block page %}
<q-dialog v-model="qrDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl text-center lnbits__dialog-card">
<lnbits-qrcode :value="qrDialog.data.lnurl"></lnbits-qrcode>
<q-btn
unelevated
color="primary"
@click="copyText(qrDialog.data.lnurl)"
class="q-mt-md q-mr-sm"
>Copy LNURL</q-btn>
<q-btn
unelevated
color="primary"
@click="qrDialog.show = false"
class="q-mt-md"
>Close</q-btn>
</q-card>
</q-dialog>
<q-dialog v-model="printerDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="submitPrinterForm" class="q-gutter-md">
Expand Down Expand Up @@ -175,6 +192,16 @@ <h5 class="text-subtitle1 q-my-none">Printers</h5>
>
<q-tooltip>Public Text Page</q-tooltip>
</q-btn>
<q-btn
flat
dense
size="xs"
icon="qr_code"
color="secondary"
@click="openLnurlQrCode(props.row.id)"
>
<q-tooltip>LNURL QRCode</q-tooltip>
</q-btn>
</q-td>
<q-td
v-for="col in props.cols"
Expand Down
85 changes: 85 additions & 0 deletions views_lnurl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import json
from http import HTTPStatus
from typing import Union

from fastapi import APIRouter, HTTPException, Query, Request
from lnbits.core.services import create_invoice
from lnbits.lnurl import LnurlErrorResponseHandler
from lnurl import LnurlPayActionResponse, LnurlPayResponse
from lnurl.models import MessageAction
from lnurl.types import (
ClearnetUrl,
DebugUrl,
LightningInvoice,
LnurlPayMetadata,
Max144Str,
MilliSatoshi,
OnionUrl,
)
from pydantic import parse_obj_as

from .crud import (
create_print,
get_printer,
)

pay2print_lnurl_router = APIRouter()
pay2print_lnurl_router.route_class = LnurlErrorResponseHandler


@pay2print_lnurl_router.get(
"/api/v1/lnurl/{printer_id}",
status_code=HTTPStatus.OK,
name="pay2print.api_lnurl_response",
)
async def api_lnurl_response(request: Request, printer_id: str) -> LnurlPayResponse:
printer = await get_printer(printer_id)
if not printer:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Printer does not exist."
)
url = request.url_for("pay2print.api_lnurl_callback", printer_id=printer_id)
callback_url: Union[DebugUrl, OnionUrl, ClearnetUrl] = parse_obj_as(
Union[DebugUrl, OnionUrl, ClearnetUrl], # type: ignore
str(url),
)
return LnurlPayResponse(
callback=callback_url,
minSendable=MilliSatoshi(printer.amount * 1000),
maxSendable=MilliSatoshi(printer.amount * 1000),
metadata=LnurlPayMetadata(
json.dumps([["text/plain", f"Pay to print {printer.name}"]])
),
)


@pay2print_lnurl_router.get(
"/api/v1/lnurl/cb/{printer_id}",
status_code=HTTPStatus.OK,
name="pay2print.api_lnurl_callback",
)
async def api_lnurl_callback(
printer_id: str,
amount: int = Query(...),
) -> LnurlPayActionResponse:
printer = await get_printer(printer_id)
if not printer:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Printer does not exist."
)
if amount != printer.amount:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Amount must be {printer.amount}.",
)
payment = await create_invoice(
wallet_id=printer.wallet,
amount=printer.amount,
memo=f"Pay to print {printer.name}",
extra={"tag": "pay2print"},
)
await create_print(printer_id, payment.payment_hash, "lnurl.txt")
message = parse_obj_as(Max144Str, "Printing! Thank you")
action = MessageAction(message=message)
invoice = parse_obj_as(LightningInvoice, LightningInvoice(payment.bolt11))
return LnurlPayActionResponse(pr=invoice, successAction=action, routes=[])

0 comments on commit 3df5b1e

Please sign in to comment.