From 68ff753cfdf5983110989b5a2fd91c3691b1fa58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 15 Dec 2025 07:41:36 +0100 Subject: [PATCH 01/13] fix: format function for table column (#67) --- static/js/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index 9a8a9c1..bcfa832 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -51,9 +51,7 @@ window.app = Vue.createApp({ align: 'right', label: 'Max (sat)', field: 'max_withdrawable', - format: v => { - return new Intl.NumberFormat(LOCALE).format(v) - } + format: LNbits.utils.formatSat } ], pagination: { From 8a20df70fe0c3b4abf176421f332eb0730595899 Mon Sep 17 00:00:00 2001 From: PatMulligan <43773168+PatMulligan@users.noreply.github.com> Date: Sat, 27 Dec 2025 09:45:57 +0100 Subject: [PATCH 02/13] FIX: generate LNURL server-side for unique voucher links (#68) Co-authored-by: Claude Opus 4.5 --- models.py | 5 +++++ static/js/index.js | 2 +- templates/withdraw/display.html | 2 +- views.py | 10 +++++++++- views_api.py | 3 +++ 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/models.py b/models.py index ff8c657..0b4b910 100644 --- a/models.py +++ b/models.py @@ -47,6 +47,11 @@ class WithdrawLink(BaseModel): "Example: lnurlw://${window.location.hostname}/lnurlw/${id}" ), ) + lnurl_url: str | None = Field( + default=None, + no_database=True, + description="The raw LNURL callback URL (use for QR code generation)", + ) @property def is_spent(self) -> bool: diff --git a/static/js/index.js b/static/js/index.js index bcfa832..a5f4dbf 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -138,7 +138,7 @@ window.app = Vue.createApp({ const link = _.findWhere(this.withdrawLinks, {id: linkId}) this.qrCodeDialog.data = _.clone(link) this.qrCodeDialog.show = true - this.activeUrl = `${window.location.origin}/withdraw/api/v1/lnurl/${link.unique_hash}` + this.activeUrl = link.lnurl_url }, openUpdateDialog(linkId) { let link = _.findWhere(this.withdrawLinks, {id: linkId}) diff --git a/templates/withdraw/display.html b/templates/withdraw/display.html index 4d06e11..f9fbd53 100644 --- a/templates/withdraw/display.html +++ b/templates/withdraw/display.html @@ -55,7 +55,7 @@ data() { return { spent: {{ 'true' if spent else 'false' }}, - url: `${window.location.origin}/withdraw/api/v1/lnurl/{{ unique_hash }}`, + url: '{{ lnurl_url }}', lnurl: '', nfcTagWriting: false } diff --git a/views.py b/views.py index ba48a5d..4fc06e3 100644 --- a/views.py +++ b/views.py @@ -33,12 +33,20 @@ async def display(request: Request, link_id): status_code=HTTPStatus.NOT_FOUND, detail="Withdraw link does not exist." ) + try: + lnurl = create_lnurl(link, request) + except ValueError as exc: + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail=str(exc), + ) from exc + return withdraw_renderer().TemplateResponse( "withdraw/display.html", { "request": request, "spent": link.is_spent, - "unique_hash": link.unique_hash, + "lnurl_url": str(lnurl.url), }, ) diff --git a/views_api.py b/views_api.py index c80e744..514372d 100644 --- a/views_api.py +++ b/views_api.py @@ -45,6 +45,7 @@ async def api_links( detail=str(exc), ) from exc linkk.lnurl = str(lnurl.bech32) + linkk.lnurl_url = str(lnurl.url) return links @@ -75,6 +76,7 @@ async def api_link_retrieve( detail=str(exc), ) from exc link.lnurl = str(lnurl.bech32) + link.lnurl_url = str(lnurl.url) return link @@ -166,6 +168,7 @@ async def api_link_create_or_update( ) from exc link.lnurl = str(lnurl.bech32) + link.lnurl_url = str(lnurl.url) return link From ab96594f70c12f2430e3308b887a8f48c7472bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Sat, 27 Dec 2025 09:48:17 +0100 Subject: [PATCH 03/13] chore: update to 1.2.2 --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index 292d2d9..de2cba5 100644 --- a/config.json +++ b/config.json @@ -2,7 +2,7 @@ "name": "Withdraw Links", "short_description": "Make LNURL withdraw links", "tile": "/withdraw/static/image/lnurl-withdraw.png", - "version": "1.2.1", + "version": "1.2.2", "min_lnbits_version": "1.3.0", "contributors": [ { From 74852e34941f3d44f47934dcd8cde847ecef7b65 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 17 Mar 2026 21:41:17 +0000 Subject: [PATCH 04/13] feat: add disable option for LNURLw (#70) --- migrations.py | 6 +++++ models.py | 2 ++ static/js/index.js | 12 ++++++---- templates/withdraw/display.html | 9 ++++++- templates/withdraw/index.html | 42 +++++++++++++++++++++++++++++++++ views.py | 2 +- views_lnurl.py | 10 +++++++- 7 files changed, 76 insertions(+), 7 deletions(-) diff --git a/migrations.py b/migrations.py index 754a57f..e27af8a 100644 --- a/migrations.py +++ b/migrations.py @@ -139,3 +139,9 @@ async def m007_add_created_at_timestamp(db): "ALTER TABLE withdraw.withdraw_link " f"ADD COLUMN created_at TIMESTAMP DEFAULT {db.timestamp_column_default}" ) + + +async def m008_add_enabled_column(db): + await db.execute( + "ALTER TABLE withdraw.withdraw_link ADD COLUMN enabled BOOLEAN DEFAULT true;" + ) diff --git a/models.py b/models.py index 0b4b910..e888cdf 100644 --- a/models.py +++ b/models.py @@ -15,6 +15,7 @@ class CreateWithdrawData(BaseModel): webhook_headers: str = Query(None) webhook_body: str = Query(None) custom_url: str = Query(None) + enabled: bool = Query(True) class WithdrawLink(BaseModel): @@ -37,6 +38,7 @@ class WithdrawLink(BaseModel): webhook_body: str = Query(None) custom_url: str = Query(None) created_at: datetime + enabled: bool = Query(True) lnurl: str | None = Field( default=None, no_database=True, diff --git a/static/js/index.js b/static/js/index.js index a5f4dbf..0b42b40 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -68,7 +68,8 @@ window.app = Vue.createApp({ data: { is_unique: false, use_custom: false, - has_webhook: false + has_webhook: false, + enabled: true } }, simpleformDialog: { @@ -78,7 +79,8 @@ window.app = Vue.createApp({ use_custom: false, title: 'Vouchers', min_withdrawable: 0, - wait_time: 1 + wait_time: 1, + enabled: true } }, qrCodeDialog: { @@ -125,13 +127,15 @@ window.app = Vue.createApp({ this.formDialog.data = { is_unique: false, use_custom: false, - has_webhook: false + has_webhook: false, + enabled: true } }, simplecloseFormDialog() { this.simpleformDialog.data = { is_unique: false, - use_custom: false + use_custom: false, + enabled: true } }, openQrCodeDialog(linkId) { diff --git a/templates/withdraw/display.html b/templates/withdraw/display.html index f9fbd53..812c95f 100644 --- a/templates/withdraw/display.html +++ b/templates/withdraw/display.html @@ -7,6 +7,12 @@ Withdraw is spent. + Withdraw is spent. + Withdraw is disabled.