From 9c79a22e3f531e0824f62e52db0f9c187c7671b3 Mon Sep 17 00:00:00 2001 From: axelhamburch Date: Thu, 11 Jun 2026 20:31:14 +0200 Subject: [PATCH] fix: failed payments no longer count towards the daily limit The hit amount was written via spend_hit before pay_invoice ran, so a rejected payment (e.g. invoice above tx_limit) still consumed the daily limit and subsequent taps were blocked with "Max daily limit spent." - validate invoice amount against tx_limit before marking the hit spent - check the daily limit in lnurl_callback (scan-time check happens before the amount is known, so a single payment could exceed it) - zero the hit amount when pay_invoice raises; the hit stays spent so the same k1 cannot be claimed again Co-Authored-By: Claude Fable 5 --- views_lnurl.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/views_lnurl.py b/views_lnurl.py index d62a87f..48583fc 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -139,7 +139,21 @@ async def lnurl_callback( card = await get_card(hit.card_id) if not card: return LnurlErrorResponse(reason="Card not found.") - hit = await spend_hit(card_id=hit.id, amount=int(invoice.amount_msat / 1000)) + + amount_sat = int(invoice.amount_msat / 1000) + # Validate limits before the hit is marked as spent, so a rejected + # attempt does not count towards the daily limit. + if amount_sat > int(card.tx_limit): + return LnurlErrorResponse( + reason=f"Payment failed - Invoice amount {amount_sat} sats is " + f"too high. Max allowed: {int(card.tx_limit)} sats." + ) + todays_hits = await get_hits_today(card.id) + spent_today = sum(h.amount for h in todays_hits) + if spent_today + amount_sat > int(card.daily_limit): + return LnurlErrorResponse(reason="Max daily limit spent.") + + hit = await spend_hit(card_id=hit.id, amount=amount_sat) if not hit: return LnurlErrorResponse(reason="Failed to update hit as spent.") try: @@ -151,6 +165,10 @@ async def lnurl_callback( ) return LnurlSuccessResponse() except Exception as exc: + # No payment happened — zero the hit amount so the failed attempt + # does not count towards the daily limit. The hit stays spent so + # the same k1 cannot be claimed again. + await spend_hit(card_id=hit.id, amount=0) return LnurlErrorResponse(reason=f"Payment failed - {exc}")