From 24545865b141e88646019c3026188adf2a158600 Mon Sep 17 00:00:00 2001 From: Gene Takavic Date: Tue, 20 Jun 2023 10:12:32 +0200 Subject: [PATCH 01/12] unspent hit won't deplete daily limit --- crud.py | 8 ++++---- views_lnurl.py | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/crud.py b/crud.py index 12e1ce2..72e816b 100644 --- a/crud.py +++ b/crud.py @@ -160,11 +160,11 @@ async def get_hits(cards_ids: list[str]) -> list[Hit]: ) -async def get_hits_today(card_id: str) -> list[Hit]: + +async def get_hits_today(card_id: str, spent: bool) -> List[Hit]: rows = await db.fetchall( - "SELECT * FROM boltcards.hits WHERE card_id = :id", - {"id": card_id}, - Hit, + "SELECT * FROM boltcards.hits WHERE card_id = ? AND spent = ?", + (card_id, spent,), ) updatedrow = [] for hit in rows: diff --git a/views_lnurl.py b/views_lnurl.py index 01da89e..5cd11dd 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -67,7 +67,7 @@ async def api_scan(p, c, request: Request, external_id: str): ip = request.headers["x-forwarded-for"] agent = request.headers["user-agent"] if "user-agent" in request.headers else "" - todays_hits = await get_hits_today(card.id) + todays_hits = await get_hits_today(card.id, True) hits_amount = 0 for hit in todays_hits: @@ -144,6 +144,22 @@ async def lnurl_callback( except Exception as exc: return {"status": "ERROR", "reason": f"Payment failed - {exc}"} + if invoice.amount_msat < card.tx_limit * 1000: + hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000)) + assert hit + try: + await pay_invoice( + wallet_id=card.wallet, + payment_request=pr, + max_sat=card.tx_limit, + extra={"tag": "boltcard", "hit": hit.id}, + ) + return {"status": "OK"} + except Exception as exc: + return {"status": "ERROR", "reason": f"Payment failed - {exc}"} + else: + return {"status": "ERROR", "reason": "Amount too high"} + # /boltcards/api/v1/auth?a=00000000000000000000000000000000 @boltcards_lnurl_router.get("/api/v1/auth") From 0a75a04899cb07b70879b0c2ae6166ccb7b5ad9f Mon Sep 17 00:00:00 2001 From: Gene Takavic Date: Tue, 20 Jun 2023 10:39:08 +0200 Subject: [PATCH 02/12] rollback unnecessary changes --- crud.py | 6 +++--- views_lnurl.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crud.py b/crud.py index 72e816b..3f45c44 100644 --- a/crud.py +++ b/crud.py @@ -161,10 +161,10 @@ async def get_hits(cards_ids: list[str]) -> list[Hit]: -async def get_hits_today(card_id: str, spent: bool) -> List[Hit]: +async def get_hits_today(card_id: str) -> List[Hit]: rows = await db.fetchall( - "SELECT * FROM boltcards.hits WHERE card_id = ? AND spent = ?", - (card_id, spent,), + "SELECT * FROM boltcards.hits WHERE card_id = ?", + (card_id,), ) updatedrow = [] for hit in rows: diff --git a/views_lnurl.py b/views_lnurl.py index 5cd11dd..701453a 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -67,7 +67,7 @@ async def api_scan(p, c, request: Request, external_id: str): ip = request.headers["x-forwarded-for"] agent = request.headers["user-agent"] if "user-agent" in request.headers else "" - todays_hits = await get_hits_today(card.id, True) + todays_hits = await get_hits_today(card.id) hits_amount = 0 for hit in todays_hits: From fff9fffe7fcdbb6b623d7dab556f71432e766772 Mon Sep 17 00:00:00 2001 From: Gene Takavic Date: Tue, 20 Jun 2023 14:40:50 +0200 Subject: [PATCH 03/12] error message --- views_lnurl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views_lnurl.py b/views_lnurl.py index 701453a..0715119 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -158,7 +158,7 @@ async def lnurl_callback( except Exception as exc: return {"status": "ERROR", "reason": f"Payment failed - {exc}"} else: - return {"status": "ERROR", "reason": "Amount too high"} + return {"status": "ERROR", "reason": "Amount exceeds card limit"} # /boltcards/api/v1/auth?a=00000000000000000000000000000000 From 74f56d8360d6eede329f6f9d8814a08c29d9a12b Mon Sep 17 00:00:00 2001 From: Gene Takavic Date: Fri, 21 Jul 2023 11:47:11 +0200 Subject: [PATCH 04/12] unspend on pay exception --- crud.py | 14 +++++++++++--- static/js/index.js | 1 + views_lnurl.py | 8 +++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/crud.py b/crud.py index 3f45c44..d19aaab 100644 --- a/crud.py +++ b/crud.py @@ -161,10 +161,10 @@ async def get_hits(cards_ids: list[str]) -> list[Hit]: -async def get_hits_today(card_id: str) -> List[Hit]: +async def get_spent_hits_today(card_id: str) -> List[Hit]: rows = await db.fetchall( - "SELECT * FROM boltcards.hits WHERE card_id = ?", - (card_id,), + "SELECT * FROM boltcards.hits WHERE card_id = ? AND spent = ?", + (card_id, True), ) updatedrow = [] for hit in rows: @@ -182,6 +182,14 @@ async def spend_hit(card_id: str, amount: int): return await get_hit(card_id) +async def unspend_hit(id: str): + await db.execute( + "UPDATE boltcards.hits SET spent = ? WHERE id = ?", + (False, id), + ) + return await get_hit(id) + + async def create_hit(card_id, ip, useragent, old_ctr, new_ctr) -> Hit: hit_id = urlsafe_short_hash() await db.execute( diff --git a/static/js/index.js b/static/js/index.js index b553347..7fa8710 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -216,6 +216,7 @@ window.app = Vue.createApp({ .then(response => { this.hits = response.data.map(obj => { obj.card_name = this.cards.find(d => d.id == obj.card_id).card_name + obj.amount = obj.spent ? obj.amount : "(" + obj.amount + ")" return mapCards(obj) }) }) diff --git a/views_lnurl.py b/views_lnurl.py index 0715119..395f523 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -18,8 +18,9 @@ get_card_by_otp, get_card_by_uid, get_hit, - get_hits_today, + get_spent_hits_today, spend_hit, + unspend_hit, update_card_counter, update_card_otp, ) @@ -67,7 +68,7 @@ async def api_scan(p, c, request: Request, external_id: str): ip = request.headers["x-forwarded-for"] agent = request.headers["user-agent"] if "user-agent" in request.headers else "" - todays_hits = await get_hits_today(card.id) + todays_hits = await get_spent_hits_today(card.id) hits_amount = 0 for hit in todays_hits: @@ -144,7 +145,7 @@ async def lnurl_callback( except Exception as exc: return {"status": "ERROR", "reason": f"Payment failed - {exc}"} - if invoice.amount_msat < card.tx_limit * 1000: + if True:#invoice.amount_msat <= card.tx_limit * 1000: hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000)) assert hit try: @@ -156,6 +157,7 @@ async def lnurl_callback( ) return {"status": "OK"} except Exception as exc: + await unspend_hit(id=hit.id) # consider it unspent return {"status": "ERROR", "reason": f"Payment failed - {exc}"} else: return {"status": "ERROR", "reason": "Amount exceeds card limit"} From 1f19c5d19a713b4a9f30dac6d40e4d63a3530a3c Mon Sep 17 00:00:00 2001 From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com> Date: Tue, 15 Aug 2023 12:13:31 +0200 Subject: [PATCH 05/12] Update lnurl.py --- views_lnurl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views_lnurl.py b/views_lnurl.py index 395f523..6ad07eb 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -145,7 +145,7 @@ async def lnurl_callback( except Exception as exc: return {"status": "ERROR", "reason": f"Payment failed - {exc}"} - if True:#invoice.amount_msat <= card.tx_limit * 1000: + if invoice.amount_msat <= card.tx_limit * 1000: hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000)) assert hit try: From eb89d311919de8c4b2451a43854dab280036763e Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 10 Jul 2025 15:13:17 +0100 Subject: [PATCH 06/12] package mode --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 9b55529..5f86644 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,7 @@ name = "lnbits-boltcards" version = "0.0.0" description = "LNbits, free and open-source Lightning wallet and accounts system." authors = ["Alan Bits "] +package-mode = false [tool.poetry.dependencies] python = "^3.10 | ^3.9" From 7d4818b11f18d84ce47cf963662b2b85b19987ce Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 10 Jul 2025 15:14:27 +0100 Subject: [PATCH 07/12] chore: lint --- crud.py | 1 - static/js/index.js | 2 +- views_lnurl.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crud.py b/crud.py index d19aaab..0da114b 100644 --- a/crud.py +++ b/crud.py @@ -160,7 +160,6 @@ async def get_hits(cards_ids: list[str]) -> list[Hit]: ) - async def get_spent_hits_today(card_id: str) -> List[Hit]: rows = await db.fetchall( "SELECT * FROM boltcards.hits WHERE card_id = ? AND spent = ?", diff --git a/static/js/index.js b/static/js/index.js index 7fa8710..6358ad2 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -216,7 +216,7 @@ window.app = Vue.createApp({ .then(response => { this.hits = response.data.map(obj => { obj.card_name = this.cards.find(d => d.id == obj.card_id).card_name - obj.amount = obj.spent ? obj.amount : "(" + obj.amount + ")" + obj.amount = obj.spent ? obj.amount : '(' + obj.amount + ')' return mapCards(obj) }) }) diff --git a/views_lnurl.py b/views_lnurl.py index 6ad07eb..de0767e 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -157,7 +157,7 @@ async def lnurl_callback( ) return {"status": "OK"} except Exception as exc: - await unspend_hit(id=hit.id) # consider it unspent + await unspend_hit(id=hit.id) # consider it unspent return {"status": "ERROR", "reason": f"Payment failed - {exc}"} else: return {"status": "ERROR", "reason": "Amount exceeds card limit"} From 68e51cfc5bae32b4ffe2547af31cfda7ee5bf13b Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 10 Jul 2025 15:35:58 +0100 Subject: [PATCH 08/12] fix db compatibility --- crud.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crud.py b/crud.py index 0da114b..ddc723d 100644 --- a/crud.py +++ b/crud.py @@ -160,10 +160,11 @@ async def get_hits(cards_ids: list[str]) -> list[Hit]: ) -async def get_spent_hits_today(card_id: str) -> List[Hit]: +async def get_spent_hits_today(card_id: str) -> list[Hit]: rows = await db.fetchall( "SELECT * FROM boltcards.hits WHERE card_id = ? AND spent = ?", - (card_id, True), + {"card_id": card_id, "spent": True}, + Hit, ) updatedrow = [] for hit in rows: @@ -181,12 +182,12 @@ async def spend_hit(card_id: str, amount: int): return await get_hit(card_id) -async def unspend_hit(id: str): +async def unspend_hit(hit_id: str): await db.execute( "UPDATE boltcards.hits SET spent = ? WHERE id = ?", - (False, id), + {"spent": False, "id": hit_id}, ) - return await get_hit(id) + return await get_hit(hit_id) async def create_hit(card_id, ip, useragent, old_ctr, new_ctr) -> Hit: From 0ded5e3ada3fee01ff17ea33af96cb4c64e8ed85 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 10 Jul 2025 15:36:59 +0100 Subject: [PATCH 09/12] fix: remove toast when no NFC available Closes #55 --- static/js/index.js | 5 +---- templates/boltcards/index.html | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index 6358ad2..b635f43 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -434,10 +434,7 @@ window.app = Vue.createApp({ message: 'NFC is supported on this device. You can now read NFC tags.' }) } catch (error) { - Quasar.Notify.create({ - type: 'negative', - message: error ? error.toString() : 'An unexpected error has occurred.' - }) + console.error(error ? error.toString() : 'An unexpected error has occurred.') } } }) diff --git a/templates/boltcards/index.html b/templates/boltcards/index.html index cd63021..aa79cf0 100644 --- a/templates/boltcards/index.html +++ b/templates/boltcards/index.html @@ -287,6 +287,7 @@
v-model="cardDialog.data.uid" type="text" label="Card UID " + :hint="disableNfcButton ? 'NFC reader is not available on this device. Input UID manually.' : ''" > From b3a02ad1245556e7013db10e5252cfee7f37b2df Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 10 Jul 2025 15:42:07 +0100 Subject: [PATCH 10/12] chore: lint --- static/js/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/static/js/index.js b/static/js/index.js index b635f43..9e53da8 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -434,7 +434,9 @@ window.app = Vue.createApp({ message: 'NFC is supported on this device. You can now read NFC tags.' }) } catch (error) { - console.error(error ? error.toString() : 'An unexpected error has occurred.') + console.error( + error ? error.toString() : 'An unexpected error has occurred.' + ) } } }) From cc246c5cc77c6901608101de276bd52e72c532ae Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Fri, 11 Jul 2025 09:11:41 +0100 Subject: [PATCH 11/12] don't show NFC button if disabled --- templates/boltcards/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/boltcards/index.html b/templates/boltcards/index.html index aa79cf0..8faced2 100644 --- a/templates/boltcards/index.html +++ b/templates/boltcards/index.html @@ -279,7 +279,7 @@
>
-
+
>
-
+
Date: Mon, 8 Sep 2025 17:45:28 +0100 Subject: [PATCH 12/12] Auto stash before checking out "origin/main" --- crud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crud.py b/crud.py index ddc723d..7e8ba73 100644 --- a/crud.py +++ b/crud.py @@ -162,7 +162,7 @@ async def get_hits(cards_ids: list[str]) -> list[Hit]: async def get_spent_hits_today(card_id: str) -> list[Hit]: rows = await db.fetchall( - "SELECT * FROM boltcards.hits WHERE card_id = ? AND spent = ?", + "SELECT * FROM boltcards.hits WHERE card_id = :card_id AND spent = :spent", {"card_id": card_id, "spent": True}, Hit, )