From 947dc50d2e6598e52e36d86741d6c12a9cd6b67d Mon Sep 17 00:00:00 2001
From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com>
Date: Mon, 12 Sep 2022 17:14:24 +0200
Subject: [PATCH 01/40] readme update
---
lnbits/extensions/boltcards/README.md | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md
index f9c59409..a7302906 100644
--- a/lnbits/extensions/boltcards/README.md
+++ b/lnbits/extensions/boltcards/README.md
@@ -10,18 +10,17 @@ This extension allows you to link your Bolt Card (or other compatible NXP NTAG d
## About the keys
-Up to five 16-byte keys can be stored on the card, numbered from 00 to 04. In the empty state they all should be set to zeros (00000000000000000000000000000000). For this extension only two keys need to be set:
+Up to five 16-byte keys can be stored on the card, numbered from 00 to 04. In the empty state they all should be set to zeros (00000000000000000000000000000000). For this extension only two keys need to be set, but for the security reasons all five keys should be changed from default (empty) state. The keys directly needed by this extension are:
-One for encrypting the card UID and the counter (p parameter), let's called it meta key, key #01 or K1.
+- One for encrypting the card UID and the counter (p parameter), let's called it meta key, key #01 or K1.
-One for calculating CMAC (c parameter), let's called it file key, key #02 or K2.
+- One for calculating CMAC (c parameter), let's called it file key, key #02 or K2.
-The key #00, K0 (also know as auth key) is skipped to be use as authentification key. Is not needed by this extension, but can be filled in order to write the keys in cooperation with bolt-nfc-android-app.
+The key #00, K0 (also know as auth key) is skipped to be used as authentification key. It is not needed by this extension, but should be filled in order to write the keys in cooperation with bolt-nfc-android-app. In this case also K3 is set to same value as K1 and K4 as K2, so all keys are changed from default values. Keep that in your mind in case you need to reset the keys manually.
***Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!***
## Setting the card - bolt-nfc-android-app (easy way)
-So far, regarding the keys, the app can only write a new key set on an empty card (with zero keys). **When you write non zero (and 'non debug') keys, they can't be rewrite with this app.** You have to do it on your computer.
- Read the card with the app. Note UID so you can fill it in the extension later.
- Write the link on the card. It shoud be like `YOUR_LNBITS_DOMAIN/boltcards/api/v1/scan/{external_id}`
@@ -35,10 +34,10 @@ So far, regarding the keys, the app can only write a new key set on an empty car
- If on an Android device with a newish version of Chrome, you can click the icon next to the input and tap your card to autofill this field.
- Advanced Options
- Card Keys (k0, k1, k2) will be automatically generated if not explicitly set.
- - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in debug mode.
- - GENERATE KEY button fill the keys randomly. If there is "debug" in the card name, a debug set of keys is filled instead.
+ - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default state.
+ - GENERATE KEY button fill the keys randomly. If there is "debug" in the card name, a debug set of keys is filled instead.
- Click CREATE CARD button
-- Click the QR code button next to a card to view its details. You can scan the QR code with the Android app to import the keys.
+- Click the QR code button next to a card to view its details. Backup the keys! You can scan the QR code with the Android app to import the keys.
- Click the "KEYS / AUTH LINK" button to copy the auth URL to the clipboard. You can then paste this into the Android app to import the keys.
- Tap the NFC card to write the keys to the card.
@@ -48,7 +47,7 @@ Follow the guide.
The URI should be `lnurlw://YOUR-DOMAIN.COM/boltcards/api/v1/scan/{YOUR_card_external_id}?p=00000000000000000000000000000000&c=0000000000000000`
-Then fill up the card parameters in the extension. Card Auth key (K0) can be omitted. Initical counter can be 0.
+Then fill up the card parameters in the extension. Card Auth key (K0) can be filled just for the record. Initical counter can be 0.
## Setting the card - android NXP app (hard way)
- If you don't know the card ID, use NXP TagInfo app to find it out.
From e1eb0895890c068a29ee32f4fe7910698522da0e Mon Sep 17 00:00:00 2001
From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com>
Date: Mon, 12 Sep 2022 22:44:22 +0200
Subject: [PATCH 02/40] readme minor changes
---
lnbits/extensions/boltcards/README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md
index a7302906..8e093f3e 100644
--- a/lnbits/extensions/boltcards/README.md
+++ b/lnbits/extensions/boltcards/README.md
@@ -34,7 +34,7 @@ The key #00, K0 (also know as auth key) is skipped to be used as authentificatio
- If on an Android device with a newish version of Chrome, you can click the icon next to the input and tap your card to autofill this field.
- Advanced Options
- Card Keys (k0, k1, k2) will be automatically generated if not explicitly set.
- - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default state.
+ - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default (empty) state.
- GENERATE KEY button fill the keys randomly. If there is "debug" in the card name, a debug set of keys is filled instead.
- Click CREATE CARD button
- Click the QR code button next to a card to view its details. Backup the keys! You can scan the QR code with the Android app to import the keys.
@@ -47,7 +47,7 @@ Follow the guide.
The URI should be `lnurlw://YOUR-DOMAIN.COM/boltcards/api/v1/scan/{YOUR_card_external_id}?p=00000000000000000000000000000000&c=0000000000000000`
-Then fill up the card parameters in the extension. Card Auth key (K0) can be filled just for the record. Initical counter can be 0.
+Then fill up the card parameters in the extension. Card Auth key (K0) can be filled in the extension just for the record. Initical counter can be 0.
## Setting the card - android NXP app (hard way)
- If you don't know the card ID, use NXP TagInfo app to find it out.
From 3660d96295ea75be08cd1e55b7899da60cd66eb9 Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Tue, 20 Sep 2022 15:53:57 +0200
Subject: [PATCH 03/40] rename setting app + delete msg
---
lnbits/extensions/boltcards/README.md | 6 +++---
lnbits/extensions/boltcards/static/js/index.js | 2 +-
lnbits/extensions/boltcards/templates/boltcards/index.html | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md
index 8e093f3e..c6d12311 100644
--- a/lnbits/extensions/boltcards/README.md
+++ b/lnbits/extensions/boltcards/README.md
@@ -6,7 +6,7 @@ This extension allows you to link your Bolt Card (or other compatible NXP NTAG d
**Disclaimer:** ***Use this only if you either know what you are doing or are a reckless lightning pioneer. Only you are responsible for all your sats, cards and other devices. Always backup all your card keys!***
-***In order to use this extension you need to be able to setup your own card.*** That means writing a URL template pointing to your LNBits instance, configuring some SUN (SDM) settings and optionally changing the card's keys. There's a [guide](https://www.whitewolftech.com/articles/payment-card/) to set it up with a card reader connected to your computer. It can be done (without setting the keys) with [TagWriter app by NXP](https://play.google.com/store/apps/details?id=com.nxp.nfc.tagwriter) Android app. Last but not least, an OSS android app by name [bolt-nfc-android-app](https://github.com/boltcard/bolt-nfc-android-app) is being developed for these purposes. It's available from Google Play [here](https://play.google.com/store/apps/details?id=com.lightningnfcapp).
+***In order to use this extension you need to be able to setup your own card.*** That means writing a URL template pointing to your LNBits instance, configuring some SUN (SDM) settings and optionally changing the card's keys. There's a [guide](https://www.whitewolftech.com/articles/payment-card/) to set it up with a card reader connected to your computer. It can be done (without setting the keys) with [TagWriter app by NXP](https://play.google.com/store/apps/details?id=com.nxp.nfc.tagwriter) Android app. Last but not least, an OSS android app by name [Boltcard NFC Card Creator](https://github.com/boltcard/bolt-nfc-android-app) is being developed for these purposes. It's available from Google Play [here](https://play.google.com/store/apps/details?id=com.lightningnfcapp).
## About the keys
@@ -16,11 +16,11 @@ Up to five 16-byte keys can be stored on the card, numbered from 00 to 04. In th
- One for calculating CMAC (c parameter), let's called it file key, key #02 or K2.
-The key #00, K0 (also know as auth key) is skipped to be used as authentification key. It is not needed by this extension, but should be filled in order to write the keys in cooperation with bolt-nfc-android-app. In this case also K3 is set to same value as K1 and K4 as K2, so all keys are changed from default values. Keep that in your mind in case you need to reset the keys manually.
+The key #00, K0 (also know as auth key) is skipped to be used as authentification key. It is not needed by this extension, but should be filled in order to write the keys in cooperation with Boltcard NFC Card Creator. In this case also K3 is set to same value as K1 and K4 as K2, so all keys are changed from default values. Keep that in your mind in case you need to reset the keys manually.
***Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!***
-## Setting the card - bolt-nfc-android-app (easy way)
+## Setting the card - Boltcard NFC Card Creator (easy way)
- Read the card with the app. Note UID so you can fill it in the extension later.
- Write the link on the card. It shoud be like `YOUR_LNBITS_DOMAIN/boltcards/api/v1/scan/{external_id}`
diff --git a/lnbits/extensions/boltcards/static/js/index.js b/lnbits/extensions/boltcards/static/js/index.js
index 11df222a..2ecde39d 100644
--- a/lnbits/extensions/boltcards/static/js/index.js
+++ b/lnbits/extensions/boltcards/static/js/index.js
@@ -398,7 +398,7 @@ new Vue({
let cards = _.findWhere(this.cards, {id: cardId})
LNbits.utils
- .confirmDialog('Are you sure you want to delete this card')
+ .confirmDialog('Are you sure you want to delete this card? Without access to the card keys you won\'t be able to reset them in the future!')
.onOk(function () {
LNbits.api
.request(
diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html
index 55cc1e5e..3e07024c 100644
--- a/lnbits/extensions/boltcards/templates/boltcards/index.html
+++ b/lnbits/extensions/boltcards/templates/boltcards/index.html
@@ -370,7 +370,7 @@
bolt-nfc-android-appBoltcard NFC Card Creator)
From 632d35682d84c981b926ef47f8e6fc9d274b948e Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Fri, 23 Sep 2022 15:17:21 +0200
Subject: [PATCH 04/40] payment notification webhook
---
lnbits/extensions/boltcards/crud.py | 6 +++--
lnbits/extensions/boltcards/lnurl.py | 23 ++++++++++++++++++-
lnbits/extensions/boltcards/migrations.py | 8 +++++++
lnbits/extensions/boltcards/models.py | 2 ++
.../extensions/boltcards/static/js/index.js | 8 +++++--
.../boltcards/templates/boltcards/index.html | 12 +++++++++-
6 files changed, 53 insertions(+), 6 deletions(-)
diff --git a/lnbits/extensions/boltcards/crud.py b/lnbits/extensions/boltcards/crud.py
index c541346e..39ee3f40 100644
--- a/lnbits/extensions/boltcards/crud.py
+++ b/lnbits/extensions/boltcards/crud.py
@@ -27,9 +27,10 @@ async def create_card(data: CreateCardData, wallet_id: str) -> Card:
k0,
k1,
k2,
- otp
+ otp,
+ webhook_url
)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
card_id,
@@ -45,6 +46,7 @@ async def create_card(data: CreateCardData, wallet_id: str) -> Card:
data.k1,
data.k2,
secrets.token_hex(16),
+ data.webhook_url,
),
)
card = await get_card(card_id)
diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py
index 6fb9ad8d..be3e09d8 100644
--- a/lnbits/extensions/boltcards/lnurl.py
+++ b/lnbits/extensions/boltcards/lnurl.py
@@ -8,6 +8,7 @@ from io import BytesIO
from typing import Optional
from urllib.parse import urlparse
+import httpx
from embit import bech32, compact
from fastapi import Request
from fastapi.param_functions import Query
@@ -119,12 +120,32 @@ async def lnurl_callback(
invoice = bolt11.decode(pr)
hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000))
try:
- await pay_invoice(
+ payment_hash = await pay_invoice(
wallet_id=card.wallet,
payment_request=pr,
max_sat=card.tx_limit,
extra={"tag": "boltcard", "tag": hit.id},
)
+
+ if card.webhook_url:
+ async with httpx.AsyncClient() as client:
+ try:
+ r = await client.post(
+ card.webhook_url,
+ json={
+ "notification": "card_payment",
+ "payment_hash": payment_hash,
+ "payment_request": pr,
+ "card_external_id": card.external_id,
+ "card_name": card.card_name,
+ "amount": int(invoice.amount_msat / 1000),
+ },
+ timeout=40,
+ )
+ except Exception as exc:
+ # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid
+ logger.error("Caught exception when dispatching webhook url:", exc)
+
return {"status": "OK"}
except:
return {"status": "ERROR", "reason": f"Payment failed"}
diff --git a/lnbits/extensions/boltcards/migrations.py b/lnbits/extensions/boltcards/migrations.py
index 08126013..25a59fdb 100644
--- a/lnbits/extensions/boltcards/migrations.py
+++ b/lnbits/extensions/boltcards/migrations.py
@@ -58,3 +58,11 @@ async def m001_initial(db):
);
"""
)
+
+
+async def m002_add_webhook(db):
+ await db.execute(
+ """
+ ALTER TABLE boltcards.cards ADD COLUMN webhook_url TEXT NOT NULL DEFAULT '';
+ """
+ )
diff --git a/lnbits/extensions/boltcards/models.py b/lnbits/extensions/boltcards/models.py
index 47ca1df0..8e6f77c9 100644
--- a/lnbits/extensions/boltcards/models.py
+++ b/lnbits/extensions/boltcards/models.py
@@ -30,6 +30,7 @@ class Card(BaseModel):
prev_k1: str
prev_k2: str
otp: str
+ webhook_url: str
time: int
def from_row(cls, row: Row) -> "Card":
@@ -56,6 +57,7 @@ class CreateCardData(BaseModel):
prev_k0: str = Query(ZERO_KEY)
prev_k1: str = Query(ZERO_KEY)
prev_k2: str = Query(ZERO_KEY)
+ webhook_url: str = Query(...)
class Hit(BaseModel):
diff --git a/lnbits/extensions/boltcards/static/js/index.js b/lnbits/extensions/boltcards/static/js/index.js
index 2ecde39d..e13c14fb 100644
--- a/lnbits/extensions/boltcards/static/js/index.js
+++ b/lnbits/extensions/boltcards/static/js/index.js
@@ -23,6 +23,7 @@ new Vue({
cardDialog: {
show: false,
data: {
+ webhook_url: '',
counter: 1,
k0: '',
k1: '',
@@ -270,7 +271,8 @@ new Vue({
k1: card.k1,
k2: card.k2,
k3: card.k1,
- k4: card.k2
+ k4: card.k2,
+ webhook_url: card.webhook_url
}
this.qrCodeDialog.show = true
},
@@ -398,7 +400,9 @@ new Vue({
let cards = _.findWhere(this.cards, {id: cardId})
LNbits.utils
- .confirmDialog('Are you sure you want to delete this card? Without access to the card keys you won\'t be able to reset them in the future!')
+ .confirmDialog(
+ "Are you sure you want to delete this card? Without access to the card keys you won't be able to reset them in the future!"
+ )
.onOk(function () {
LNbits.api
.request(
diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html
index 3e07024c..f795e454 100644
--- a/lnbits/extensions/boltcards/templates/boltcards/index.html
+++ b/lnbits/extensions/boltcards/templates/boltcards/index.html
@@ -283,7 +283,7 @@
v-model="toggleAdvanced"
label="Show advanced options"
>
-
+
Zero if you don't know.
+
+
Lock key: {{ qrCodeDialog.data.k0 }}
Meta key: {{ qrCodeDialog.data.k1 }}
File key: {{ qrCodeDialog.data.k2 }}
+ Notification webhook: {{ qrCodeDialog.data.webhook_url
+ }}
Date: Fri, 23 Sep 2022 16:19:58 +0200
Subject: [PATCH 05/40] Update README.md
Updated for Boltcard NFC Card Creator v0.1.1
---
lnbits/extensions/boltcards/README.md | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md
index c6d12311..5140ecc2 100644
--- a/lnbits/extensions/boltcards/README.md
+++ b/lnbits/extensions/boltcards/README.md
@@ -21,10 +21,7 @@ The key #00, K0 (also know as auth key) is skipped to be used as authentificatio
***Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!***
## Setting the card - Boltcard NFC Card Creator (easy way)
-
-- Read the card with the app. Note UID so you can fill it in the extension later.
-- Write the link on the card. It shoud be like `YOUR_LNBITS_DOMAIN/boltcards/api/v1/scan/{external_id}`
- - `{external_id}` should be replaced with the External ID found in the LNBits dialog.
+Updated for v0.1.1
- Add new card in the extension.
- Set a max sats per transaction. Any transaction greater than this amount will be rejected.
@@ -32,14 +29,16 @@ The key #00, K0 (also know as auth key) is skipped to be used as authentificatio
- Set a card name. This is just for your reference inside LNBits.
- Set the card UID. This is the unique identifier on your NFC card and is 7 bytes.
- If on an Android device with a newish version of Chrome, you can click the icon next to the input and tap your card to autofill this field.
+ - Otherwise read it with the Android app (Advanced -> Read NFC) and paste it to the field.
- Advanced Options
- Card Keys (k0, k1, k2) will be automatically generated if not explicitly set.
- - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default (empty) state.
- - GENERATE KEY button fill the keys randomly. If there is "debug" in the card name, a debug set of keys is filled instead.
+ - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default (empty) state (this is unsecure).
+ - GENERATE KEY button fill the keys randomly.
- Click CREATE CARD button
-- Click the QR code button next to a card to view its details. Backup the keys! You can scan the QR code with the Android app to import the keys.
-- Click the "KEYS / AUTH LINK" button to copy the auth URL to the clipboard. You can then paste this into the Android app to import the keys.
-- Tap the NFC card to write the keys to the card.
+- Click the QR code button next to a card to view its details. Backup the keys now! They'll be comfortable in your password manager.
+ - Now you can scan the QR code with the Android app (Create Bolt Card -> SCAN QR CODE).
+ - Or you can Click the "KEYS / AUTH LINK" button to copy the auth URL to the clipboard. Then paste it into the Android app (Create Bolt Card -> PASTE AUTH URL).
+- Click WRITE CARD NOW and tap the NFC card to set it up. DO NOT REMOVE THE CARD PREMATURELY!
## Setting the card - computer (hard way)
@@ -69,4 +68,4 @@ Then fill up the card parameters in the extension. Card Auth key (K0) can be fil
- Save & Write
- Scan with compatible Wallet
-This app afaik cannot change the keys. If you cannot change them any other way, leave them empty in the extension dialog and remember you're not secure. Card Auth key (K0) can be omitted anyway. Initical counter can be 0.
+This app afaik cannot change the keys. If you cannot change them any other way, leave them empty in the extension dialog and remember you're not secured. Card Auth key (K0) can be omitted anyway. Initical counter can be 0.
From ea2d66fbd628681618dbd411f544f63ee8692a5f Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Fri, 23 Sep 2022 17:11:19 +0200
Subject: [PATCH 06/40] refund notification
---
lnbits/extensions/boltcards/tasks.py | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/boltcards/tasks.py b/lnbits/extensions/boltcards/tasks.py
index 1b51c98b..27929efc 100644
--- a/lnbits/extensions/boltcards/tasks.py
+++ b/lnbits/extensions/boltcards/tasks.py
@@ -2,12 +2,13 @@ import asyncio
import json
import httpx
+from loguru import logger
from lnbits.core import db as core_db
from lnbits.core.models import Payment
from lnbits.tasks import register_invoice_listener
-from .crud import create_refund, get_hit
+from .crud import create_refund, get_card, get_hit
async def wait_for_paid_invoices():
@@ -34,6 +35,25 @@ async def on_invoice_paid(payment: Payment) -> None:
)
await mark_webhook_sent(payment, 1)
+ card = await get_card(hit.card_id)
+ if card.webhook_url:
+ async with httpx.AsyncClient() as client:
+ try:
+ r = await client.post(
+ card.webhook_url,
+ json={
+ "notification": "card_refund",
+ "payment_hash": payment.payment_hash,
+ "payment_request": payment.bolt11,
+ "card_external_id": card.external_id,
+ "card_name": card.card_name,
+ "amount": int(payment.amount / 1000),
+ },
+ timeout=40,
+ )
+ except Exception as exc:
+ logger.error("Caught exception when dispatching webhook url:", exc)
+
async def mark_webhook_sent(payment: Payment, status: int) -> None:
payment.extra["wh_status"] = status
From 8b3af03519c06437325e32e78a71febf9f91d3a1 Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Sun, 25 Sep 2022 17:01:22 +0200
Subject: [PATCH 07/40] webhook to pay_invoice
---
lnbits/core/services.py | 31 ++++++++++++++++-
lnbits/extensions/boltcards/lnurl.py | 42 ++++++++----------------
lnbits/extensions/boltcards/views_api.py | 5 ---
lnbits/extensions/withdraw/lnurl.py | 30 +++++++----------
4 files changed, 56 insertions(+), 52 deletions(-)
diff --git a/lnbits/core/services.py b/lnbits/core/services.py
index 10693f4b..aeb4f938 100644
--- a/lnbits/core/services.py
+++ b/lnbits/core/services.py
@@ -2,7 +2,7 @@ import asyncio
import json
from binascii import unhexlify
from io import BytesIO
-from typing import Dict, Optional, Tuple
+from typing import Dict, Optional, Tuple, Union
from urllib.parse import parse_qs, urlparse
import httpx
@@ -102,6 +102,7 @@ async def pay_invoice(
extra: Optional[Dict] = None,
description: str = "",
conn: Optional[Connection] = None,
+ webhook: Optional[Union[str, tuple]] = None,
) -> str:
"""
Pay a Lightning invoice.
@@ -231,6 +232,34 @@ async def pay_invoice(
f"didn't receive checking_id from backend, payment may be stuck in database: {temp_id}"
)
+ if type(webhook) is str:
+ webhook_url = webhook
+ elif type(webhook) is tuple:
+ webhook_url = webhook[0]
+ additionals = webhook[1]
+ else:
+ webhook_url = None
+
+ if webhook_url:
+ async with httpx.AsyncClient() as client:
+ try:
+ json = {
+ "payment_hash": invoice.payment_hash,
+ "payment_request": payment_request,
+ "amount": int(invoice.amount_msat / 1000),
+ }
+ if type(additionals) is dict:
+ json.update(additionals)
+
+ r = await client.post(
+ webhook_url,
+ json=json,
+ timeout=40,
+ )
+ except Exception as exc:
+ # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid
+ logger.error("Caught exception when dispatching webhook url:", exc)
+
return invoice.payment_hash
diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py
index be3e09d8..320b1666 100644
--- a/lnbits/extensions/boltcards/lnurl.py
+++ b/lnbits/extensions/boltcards/lnurl.py
@@ -1,19 +1,12 @@
-import base64
-import hashlib
-import hmac
import json
import secrets
from http import HTTPStatus
-from io import BytesIO
-from typing import Optional
from urllib.parse import urlparse
-import httpx
from embit import bech32, compact
from fastapi import Request
from fastapi.param_functions import Query
from fastapi.params import Depends, Query
-from lnurl import Lnurl, LnurlWithdrawResponse
from lnurl import encode as lnurl_encode # type: ignore
from lnurl.types import LnurlPayMetadata # type: ignore
from loguru import logger
@@ -34,7 +27,6 @@ from .crud import (
get_hit,
get_hits_today,
spend_hit,
- update_card,
update_card_counter,
update_card_otp,
)
@@ -120,32 +112,26 @@ async def lnurl_callback(
invoice = bolt11.decode(pr)
hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000))
try:
- payment_hash = await pay_invoice(
+ webhook = (
+ (
+ card.webhook_url,
+ {
+ "notification": "card_payment",
+ "card_external_id": card.external_id,
+ "card_name": card.card_name,
+ },
+ )
+ if card.webhook_url
+ else None
+ )
+ await pay_invoice(
wallet_id=card.wallet,
payment_request=pr,
max_sat=card.tx_limit,
extra={"tag": "boltcard", "tag": hit.id},
+ webhook=webhook,
)
- if card.webhook_url:
- async with httpx.AsyncClient() as client:
- try:
- r = await client.post(
- card.webhook_url,
- json={
- "notification": "card_payment",
- "payment_hash": payment_hash,
- "payment_request": pr,
- "card_external_id": card.external_id,
- "card_name": card.card_name,
- "amount": int(invoice.amount_msat / 1000),
- },
- timeout=40,
- )
- except Exception as exc:
- # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid
- logger.error("Caught exception when dispatching webhook url:", exc)
-
return {"status": "OK"}
except:
return {"status": "ERROR", "reason": f"Payment failed"}
diff --git a/lnbits/extensions/boltcards/views_api.py b/lnbits/extensions/boltcards/views_api.py
index 7b8357cf..2fc11dbc 100644
--- a/lnbits/extensions/boltcards/views_api.py
+++ b/lnbits/extensions/boltcards/views_api.py
@@ -12,21 +12,16 @@ from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from . import boltcards_ext
from .crud import (
create_card,
- create_hit,
delete_card,
enable_disable_card,
get_card,
- get_card_by_otp,
get_card_by_uid,
get_cards,
get_hits,
get_refunds,
update_card,
- update_card_counter,
- update_card_otp,
)
from .models import CreateCardData
-from .nxp424 import decryptSUN, getSunMAC
@boltcards_ext.get("/api/v1/cards")
diff --git a/lnbits/extensions/withdraw/lnurl.py b/lnbits/extensions/withdraw/lnurl.py
index 18a99599..3379fd17 100644
--- a/lnbits/extensions/withdraw/lnurl.py
+++ b/lnbits/extensions/withdraw/lnurl.py
@@ -3,7 +3,6 @@ import traceback
from datetime import datetime
from http import HTTPStatus
-import httpx
import shortuuid # type: ignore
from fastapi import HTTPException
from fastapi.param_functions import Query
@@ -115,29 +114,24 @@ async def api_lnurl_callback(
payment_request = pr
- payment_hash = await pay_invoice(
+ webhook = (
+ (
+ link.webhook_url,
+ {
+ "lnurlw": link.id,
+ },
+ )
+ if link.webhook_url
+ else None
+ )
+ await pay_invoice(
wallet_id=link.wallet,
payment_request=payment_request,
max_sat=link.max_withdrawable,
extra={"tag": "withdraw"},
+ webhook=webhook,
)
- if link.webhook_url:
- async with httpx.AsyncClient() as client:
- try:
- r = await client.post(
- link.webhook_url,
- json={
- "payment_hash": payment_hash,
- "payment_request": payment_request,
- "lnurlw": link.id,
- },
- timeout=40,
- )
- except Exception as exc:
- # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid
- logger.error("Caught exception when dispatching webhook url:", exc)
-
return {"status": "OK"}
except Exception as e:
From 0bede387f5ed5620781c50b689fc7016ad5d15af Mon Sep 17 00:00:00 2001
From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com>
Date: Sun, 25 Sep 2022 18:11:25 +0200
Subject: [PATCH 08/40] webhook to pay_invoice/fix
---
lnbits/core/services.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/lnbits/core/services.py b/lnbits/core/services.py
index aeb4f938..4f937ec3 100644
--- a/lnbits/core/services.py
+++ b/lnbits/core/services.py
@@ -232,6 +232,7 @@ async def pay_invoice(
f"didn't receive checking_id from backend, payment may be stuck in database: {temp_id}"
)
+ additionals = None
if type(webhook) is str:
webhook_url = webhook
elif type(webhook) is tuple:
From 5c6cd70d3bf08720a094c4aac263f53c583664e9 Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Mon, 26 Sep 2022 11:47:00 +0200
Subject: [PATCH 09/40] disabling card with just otp which is also part of
notify
---
lnbits/extensions/boltcards/crud.py | 10 ++++++++--
lnbits/extensions/boltcards/lnurl.py | 3 +++
lnbits/extensions/boltcards/views_api.py | 18 ++++++++++++++++++
3 files changed, 29 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/boltcards/crud.py b/lnbits/extensions/boltcards/crud.py
index 39ee3f40..0724ea04 100644
--- a/lnbits/extensions/boltcards/crud.py
+++ b/lnbits/extensions/boltcards/crud.py
@@ -114,8 +114,14 @@ async def get_card_by_external_id(external_id: str) -> Optional[Card]:
return Card.parse_obj(card)
-async def get_card_by_otp(otp: str) -> Optional[Card]:
- row = await db.fetchone("SELECT * FROM boltcards.cards WHERE otp = ?", (otp,))
+async def get_card_by_otp(otp: str, half: bool = False) -> Optional[Card]:
+ if half and len(otp) == 16:
+ otp = "%" + otp
+ row = await db.fetchone(
+ "SELECT * FROM boltcards.cards WHERE otp LIKE ?", (otp,)
+ )
+ else:
+ row = await db.fetchone("SELECT * FROM boltcards.cards WHERE otp = ?", (otp,))
if not row:
return None
diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py
index 320b1666..064bde2c 100644
--- a/lnbits/extensions/boltcards/lnurl.py
+++ b/lnbits/extensions/boltcards/lnurl.py
@@ -119,6 +119,9 @@ async def lnurl_callback(
"notification": "card_payment",
"card_external_id": card.external_id,
"card_name": card.card_name,
+ "card_otp": card.otp[
+ -16:
+ ], # actually only half of the OTP is sent (full otp reveals the keys)
},
)
if card.webhook_url
diff --git a/lnbits/extensions/boltcards/views_api.py b/lnbits/extensions/boltcards/views_api.py
index 2fc11dbc..d7f5cf7c 100644
--- a/lnbits/extensions/boltcards/views_api.py
+++ b/lnbits/extensions/boltcards/views_api.py
@@ -15,11 +15,13 @@ from .crud import (
delete_card,
enable_disable_card,
get_card,
+ get_card_by_otp,
get_card_by_uid,
get_cards,
get_hits,
get_refunds,
update_card,
+ update_card_otp,
)
from .models import CreateCardData
@@ -111,6 +113,22 @@ async def enable_card(
return card.dict()
+@boltcards_ext.post("/api/v1/disablecard")
+async def disble_card_with_otp(a):
+ if len(a) < 16:
+ raise HTTPException(detail="Invalid OTP.", status_code=HTTPStatus.BAD_REQUEST)
+ card = await get_card_by_otp(a, half=True)
+ if not card:
+ raise HTTPException(detail="No card found.", status_code=HTTPStatus.NOT_FOUND)
+
+ new_otp = secrets.token_hex(16)
+ await update_card_otp(new_otp, card.id)
+
+ card = await enable_disable_card(enable=False, id=card.id)
+
+ return {"status": "OK"}
+
+
@boltcards_ext.delete("/api/v1/cards/{card_id}")
async def api_card_delete(card_id, wallet: WalletTypeInfo = Depends(require_admin_key)):
card = await get_card(card_id)
From 49c58cc8d0cb48fd874ba40d28c4331a065424df Mon Sep 17 00:00:00 2001
From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com>
Date: Mon, 24 Oct 2022 11:48:34 +0200
Subject: [PATCH 10/40] return a reason of failed payment to the pos; disable a
change of wallet in the form
---
lnbits/extensions/boltcards/lnurl.py | 4 ++--
lnbits/extensions/boltcards/templates/boltcards/index.html | 1 +
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py
index 064bde2c..cd4c6ba4 100644
--- a/lnbits/extensions/boltcards/lnurl.py
+++ b/lnbits/extensions/boltcards/lnurl.py
@@ -136,8 +136,8 @@ async def lnurl_callback(
)
return {"status": "OK"}
- except:
- return {"status": "ERROR", "reason": f"Payment failed"}
+ except Exception as exc:
+ return {"status": "ERROR", "reason": f"Payment failed - {exc}"}
# /boltcards/api/v1/auth?a=00000000000000000000000000000000
diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html
index f795e454..7b9713e2 100644
--- a/lnbits/extensions/boltcards/templates/boltcards/index.html
+++ b/lnbits/extensions/boltcards/templates/boltcards/index.html
@@ -215,6 +215,7 @@
emit-value
v-model="cardDialog.data.wallet"
:options="g.user.walletOptions"
+ :disable="cardDialog.data.id != null"
label="Wallet *"
>
From 678c269a9124a967300a34d75542ed0c0c35150a Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Fri, 18 Nov 2022 16:57:39 +0100
Subject: [PATCH 11/40] wipe card
---
.../extensions/boltcards/static/js/index.js | 16 ++-
.../boltcards/templates/boltcards/index.html | 102 ++++++++++++++----
2 files changed, 97 insertions(+), 21 deletions(-)
diff --git a/lnbits/extensions/boltcards/static/js/index.js b/lnbits/extensions/boltcards/static/js/index.js
index e13c14fb..1949050b 100644
--- a/lnbits/extensions/boltcards/static/js/index.js
+++ b/lnbits/extensions/boltcards/static/js/index.js
@@ -150,6 +150,7 @@ new Vue({
},
qrCodeDialog: {
show: false,
+ wipe: false,
data: null
}
}
@@ -260,9 +261,10 @@ new Vue({
})
})
},
- openQrCodeDialog(cardId) {
+ openQrCodeDialog(cardId, wipe) {
var card = _.findWhere(this.cards, {id: cardId})
this.qrCodeDialog.data = {
+ id: card.id,
link: window.location.origin + '/boltcards/api/v1/auth?a=' + card.otp,
name: card.card_name,
uid: card.uid,
@@ -274,6 +276,18 @@ new Vue({
k4: card.k2,
webhook_url: card.webhook_url
}
+ this.qrCodeDialog.data_wipe = JSON.stringify({
+ action: 'wipe',
+ id: 1,
+ k0: card.k0,
+ k1: card.k1,
+ k2: card.k2,
+ k3: card.k1,
+ k4: card.k2,
+ uid: card.uid,
+ version: 1
+ })
+ this.qrCodeDialog.wipe = wipe
this.qrCodeDialog.show = true
},
addCardOpen: function () {
diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html
index 7b9713e2..b80e9685 100644
--- a/lnbits/extensions/boltcards/templates/boltcards/index.html
+++ b/lnbits/extensions/boltcards/templates/boltcards/index.html
@@ -48,6 +48,7 @@
+
@@ -58,7 +59,7 @@
dense
icon="qr_code"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
- @click="openQrCodeDialog(props.row.id)"
+ @click="openQrCodeDialog(props.row.id, false)"
>
Card key credentials
@@ -99,7 +100,7 @@
flat
dense
size="xs"
- @click="deleteCard(props.row.id)"
+ @click="openQrCodeDialog(props.row.id, true)"
icon="cancel"
color="pink"
>
@@ -367,21 +368,61 @@
{% raw %}
-
-
-
-
- (Keys for
- Boltcard NFC Card Creator)
-
+
+
+
+
+
+ (QR for create the card in
+ Boltcard NFC Card Creator)
+
+
+
+
+
+ (QR for for wipe the card in
+ Boltcard NFC Card Creator)
+
+
+
+
+
+
Name: {{ qrCodeDialog.data.name }}
UID: {{ qrCodeDialog.data.uid }}
@@ -398,11 +439,32 @@
outline
color="grey"
@click="copyText(qrCodeDialog.data.link)"
- label="Keys/Auth link"
+ label="Create link"
+ v-show="!qrCodeDialog.wipe"
>
+ Click to copy, then paste to NFC Card Creator
+
+
+ Click to copy, then paste to NFC Card Creator
+
+
+ Backup the keys, or wipe the card first!
-
Click to copy, then add to NFC card
-
{% endraw %}
Close
From 8d05cddc877f2e00a7c542c034a91a6c13db5493 Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Sat, 19 Nov 2022 13:50:53 +0100
Subject: [PATCH 12/40] wipe card readme
---
lnbits/extensions/boltcards/README.md | 17 +++++++++++++++--
lnbits/extensions/boltcards/static/js/index.js | 1 -
2 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md
index 5140ecc2..f612ae2f 100644
--- a/lnbits/extensions/boltcards/README.md
+++ b/lnbits/extensions/boltcards/README.md
@@ -21,7 +21,7 @@ The key #00, K0 (also know as auth key) is skipped to be used as authentificatio
***Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!***
## Setting the card - Boltcard NFC Card Creator (easy way)
-Updated for v0.1.1
+Updated for v0.1.2
- Add new card in the extension.
- Set a max sats per transaction. Any transaction greater than this amount will be rejected.
@@ -38,7 +38,20 @@ Updated for v0.1.1
- Click the QR code button next to a card to view its details. Backup the keys now! They'll be comfortable in your password manager.
- Now you can scan the QR code with the Android app (Create Bolt Card -> SCAN QR CODE).
- Or you can Click the "KEYS / AUTH LINK" button to copy the auth URL to the clipboard. Then paste it into the Android app (Create Bolt Card -> PASTE AUTH URL).
-- Click WRITE CARD NOW and tap the NFC card to set it up. DO NOT REMOVE THE CARD PREMATURELY!
+- Click WRITE CARD NOW and approach the NFC card to set it up. DO NOT REMOVE THE CARD PREMATURELY!
+
+## Erasing the card - Boltcard NFC Card Creator
+Updated for v0.1.2
+
+Since v0.1.2 of Boltcard NFC Card Creator it is possible not only reset the keys but also disable the SUN function and do the complete erase so the card can be use again as a static tag (or set as a new Bolt Card, ofc).
+
+- Click the QR code button next to a card to view its details and select WIPE
+- OR click the red cross icon on the right side to reach the same
+- In the android app (Advanced -> Reset Keys)
+ - Click SCAN QR CODE to scan the QR
+ - Or click WIPE DATA in LNbits to copy and paste in to the app (PASTE KEY JSON)
+- Click RESET CARD NOW and approach the NFC card to erase it. DO NOT REMOVE THE CARD PREMATURELY!
+- Now if there is all success the card can be safely delete from LNbits (but keep the keys backuped anyway; batter safe than brick).
## Setting the card - computer (hard way)
diff --git a/lnbits/extensions/boltcards/static/js/index.js b/lnbits/extensions/boltcards/static/js/index.js
index 1949050b..95a95afc 100644
--- a/lnbits/extensions/boltcards/static/js/index.js
+++ b/lnbits/extensions/boltcards/static/js/index.js
@@ -278,7 +278,6 @@ new Vue({
}
this.qrCodeDialog.data_wipe = JSON.stringify({
action: 'wipe',
- id: 1,
k0: card.k0,
k1: card.k1,
k2: card.k2,
From 95ad55775df5ad625997b568662f086d42deb13b Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Sun, 20 Nov 2022 09:51:54 +0100
Subject: [PATCH 13/40] fixes
---
.../boltcards/templates/boltcards/index.html | 37 ++++++++++---------
1 file changed, 19 insertions(+), 18 deletions(-)
diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html
index b80e9685..15af9db5 100644
--- a/lnbits/extensions/boltcards/templates/boltcards/index.html
+++ b/lnbits/extensions/boltcards/templates/boltcards/index.html
@@ -379,16 +379,16 @@
:options="{width: 800}"
class="rounded-borders"
>
-
-
- (QR for create the card in
- Boltcard NFC Card Creator)
-
+
+ (QR for create the card in
+ Boltcard NFC Card Creator)
+
-
- (QR for for wipe the card in
- Boltcard NFC Card Creator)
-
+
+ (QR for for wipe the card in
+ Boltcard NFC Card Creator)
+
-
+
Date: Thu, 24 Nov 2022 04:52:26 +0100
Subject: [PATCH 14/40] Update index.html
typo
---
lnbits/extensions/boltcards/templates/boltcards/index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html
index 7097a43f..aefb22c6 100644
--- a/lnbits/extensions/boltcards/templates/boltcards/index.html
+++ b/lnbits/extensions/boltcards/templates/boltcards/index.html
@@ -401,7 +401,7 @@
>
- (QR for for wipe the card in
+ (QR for wipe the card in
Date: Tue, 29 Nov 2022 09:34:54 +0100
Subject: [PATCH 15/40] force download wipe json when deleting card
---
lnbits/extensions/boltcards/static/js/index.js | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/lnbits/extensions/boltcards/static/js/index.js b/lnbits/extensions/boltcards/static/js/index.js
index 95a95afc..1f32ad44 100644
--- a/lnbits/extensions/boltcards/static/js/index.js
+++ b/lnbits/extensions/boltcards/static/js/index.js
@@ -412,6 +412,12 @@ new Vue({
let self = this
let cards = _.findWhere(this.cards, {id: cardId})
+ Quasar.utils.exportFile(
+ cards.card_name + '.json',
+ this.qrCodeDialog.data_wipe,
+ 'application/json'
+ )
+
LNbits.utils
.confirmDialog(
"Are you sure you want to delete this card? Without access to the card keys you won't be able to reset them in the future!"
From 4e6466cd954117638713b4b23de81d981509b9f0 Mon Sep 17 00:00:00 2001
From: ben
Date: Fri, 9 Dec 2022 14:10:10 +0000
Subject: [PATCH 16/40] Started adding ability to forward by tag
---
.../splitpayments/static/js/index.js | 2 +-
.../templates/splitpayments/index.html | 20 +++++++++++++++++--
2 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/splitpayments/static/js/index.js b/lnbits/extensions/splitpayments/static/js/index.js
index 5d326231..0cd24920 100644
--- a/lnbits/extensions/splitpayments/static/js/index.js
+++ b/lnbits/extensions/splitpayments/static/js/index.js
@@ -56,7 +56,7 @@ new Vue({
this.selectedWallet = wallet
this.getTargets()
},
- targetChanged(isPercent, index) {
+ percentageChanged(isPercent, index) {
// fix percent min and max range
if (isPercent) {
if (this.targets[index].percent > 100) this.targets[index].percent = 100
diff --git a/lnbits/extensions/splitpayments/templates/splitpayments/index.html b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
index 1cceb7ba..acf6109a 100644
--- a/lnbits/extensions/splitpayments/templates/splitpayments/index.html
+++ b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
@@ -31,6 +31,13 @@
style="flex-wrap: nowrap"
v-for="(target, t) in targets"
>
+
+
+
From 52d490d6879e69d25868e929d0d7b410597f354b Mon Sep 17 00:00:00 2001
From: ben
Date: Mon, 12 Dec 2022 12:44:58 +0000
Subject: [PATCH 17/40] struggling with ui
---
lnbits/extensions/splitpayments/static/js/index.js | 4 +++-
.../splitpayments/templates/splitpayments/index.html | 3 ++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/splitpayments/static/js/index.js b/lnbits/extensions/splitpayments/static/js/index.js
index 0cd24920..32fe20c7 100644
--- a/lnbits/extensions/splitpayments/static/js/index.js
+++ b/lnbits/extensions/splitpayments/static/js/index.js
@@ -20,7 +20,9 @@ new Vue({
return {
selectedWallet: null,
currentHash: '', // a string that must match if the edit data is unchanged
- targets: []
+ targets: [{
+ method: "split"
+ }]
}
},
computed: {
diff --git a/lnbits/extensions/splitpayments/templates/splitpayments/index.html b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
index acf6109a..e86842e9 100644
--- a/lnbits/extensions/splitpayments/templates/splitpayments/index.html
+++ b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
@@ -33,10 +33,11 @@
>
Date: Tue, 13 Dec 2022 13:42:09 +0000
Subject: [PATCH 18/40] Added tag
---
.../splitpayments/static/js/index.js | 10 ++-
.../templates/splitpayments/index.html | 75 +++++++++++--------
2 files changed, 51 insertions(+), 34 deletions(-)
diff --git a/lnbits/extensions/splitpayments/static/js/index.js b/lnbits/extensions/splitpayments/static/js/index.js
index 32fe20c7..94ece07b 100644
--- a/lnbits/extensions/splitpayments/static/js/index.js
+++ b/lnbits/extensions/splitpayments/static/js/index.js
@@ -63,8 +63,14 @@ new Vue({
if (isPercent) {
if (this.targets[index].percent > 100) this.targets[index].percent = 100
if (this.targets[index].percent < 0) this.targets[index].percent = 0
+ this.targets[index].tag = ''
}
+ // not percentage
+ if (!isPercent) {
+ this.targets[index].percent = 0
+ }
+
// remove empty lines (except last)
if (this.targets.length >= 2) {
for (let i = this.targets.length - 2; i >= 0; i--) {
@@ -72,6 +78,7 @@ new Vue({
if (
(!target.wallet || target.wallet.trim() === '') &&
(!target.alias || target.alias.trim() === '') &&
+ (!target.tag || target.tag.trim() === '') &&
!target.percent
) {
this.targets.splice(i, 1)
@@ -81,7 +88,7 @@ new Vue({
// add a line at the end if the last one is filled
let last = this.targets[this.targets.length - 1]
- if (last.wallet && last.wallet.trim() !== '' && last.percent > 0) {
+ if (last.wallet && last.wallet.trim() !== '') {
this.targets.push({})
}
@@ -113,6 +120,7 @@ new Vue({
// overwrite so changes appear
this.targets = this.targets
+ console.log(this.targets)
},
saveTargets() {
LNbits.api
diff --git a/lnbits/extensions/splitpayments/templates/splitpayments/index.html b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
index e86842e9..7ea76339 100644
--- a/lnbits/extensions/splitpayments/templates/splitpayments/index.html
+++ b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
@@ -31,54 +31,63 @@
style="flex-wrap: nowrap"
v-for="(target, t) in targets"
>
-
-
-
+
+
+
+
+
+
+
-
+
From d69c2e9170591464744ad897ca2fa214d50e34bf Mon Sep 17 00:00:00 2001
From: ben
Date: Tue, 13 Dec 2022 14:25:18 +0000
Subject: [PATCH 19/40] moved to tag changed
---
.../splitpayments/static/js/index.js | 36 +++++++++++++++----
.../templates/splitpayments/index.html | 7 ++--
2 files changed, 32 insertions(+), 11 deletions(-)
diff --git a/lnbits/extensions/splitpayments/static/js/index.js b/lnbits/extensions/splitpayments/static/js/index.js
index 94ece07b..567b8672 100644
--- a/lnbits/extensions/splitpayments/static/js/index.js
+++ b/lnbits/extensions/splitpayments/static/js/index.js
@@ -58,17 +58,40 @@ new Vue({
this.selectedWallet = wallet
this.getTargets()
},
+ tagChanged(isTag, index) {
+ // fix percent min and max range
+ if (isTag) {
+ this.targets[index].percent = null
+ this.targets[index].tag.trim()
+ }
+
+ // remove empty lines (except last)
+ if (this.targets.length >= 2) {
+ for (let i = this.targets.length - 2; i >= 0; i--) {
+ let target = this.targets[i]
+ if (
+ (!target.wallet || target.wallet.trim() === '') &&
+ (!target.alias || target.alias.trim() === '') &&
+ !target.percent
+ ) {
+ this.targets.splice(i, 1)
+ }
+ }
+ }
+ },
+ clearChanged(index) {
+ if(this.targets[index].method == 'split'){
+ this.targets[index].tag = null
+ }
+ else{
+ this.targets[index].percent = null
+ }
+ },
percentageChanged(isPercent, index) {
// fix percent min and max range
if (isPercent) {
if (this.targets[index].percent > 100) this.targets[index].percent = 100
if (this.targets[index].percent < 0) this.targets[index].percent = 0
- this.targets[index].tag = ''
- }
-
- // not percentage
- if (!isPercent) {
- this.targets[index].percent = 0
}
// remove empty lines (except last)
@@ -78,7 +101,6 @@ new Vue({
if (
(!target.wallet || target.wallet.trim() === '') &&
(!target.alias || target.alias.trim() === '') &&
- (!target.tag || target.tag.trim() === '') &&
!target.percent
) {
this.targets.splice(i, 1)
diff --git a/lnbits/extensions/splitpayments/templates/splitpayments/index.html b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
index 7ea76339..5a3f7895 100644
--- a/lnbits/extensions/splitpayments/templates/splitpayments/index.html
+++ b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
@@ -37,7 +37,6 @@
v-model="target.alias"
label="Alias"
:hint="t === targets.length - 1 ? 'A name to identify this target wallet locally.' : undefined"
- @input="targetChanged(false)"
style="width: 150px"
>
@@ -46,7 +45,6 @@
v-model="target.wallet"
label="Wallet"
:hint="t === targets.length - 1 ? 'A wallet ID or invoice key.' : undefined"
- @input="targetChanged(false)"
option-label="name"
style="width: 300px"
new-value-mode="add-unique"
@@ -64,7 +62,8 @@
style="width: 180px"
v-model="target.method"
:label="`${target.method}` === 'tag' ? 'Send funds by tag' : `${target.method}` === 'split' ? 'Split funds by %' : 'Split/tag?'"
- >
+ @input="clearChanged(t)"
+ >
Date: Tue, 13 Dec 2022 15:27:28 +0100
Subject: [PATCH 20/40] fix get_hits, get_refunds
---
lnbits/extensions/boltcards/crud.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/lnbits/extensions/boltcards/crud.py b/lnbits/extensions/boltcards/crud.py
index 0724ea04..346b5522 100644
--- a/lnbits/extensions/boltcards/crud.py
+++ b/lnbits/extensions/boltcards/crud.py
@@ -179,6 +179,9 @@ async def get_hit(hit_id: str) -> Optional[Hit]:
async def get_hits(cards_ids: Union[str, List[str]]) -> List[Hit]:
+ if len(cards_ids) == 0:
+ return []
+
q = ",".join(["?"] * len(cards_ids))
rows = await db.fetchall(
f"SELECT * FROM boltcards.hits WHERE card_id IN ({q})", (*cards_ids,)
@@ -273,6 +276,9 @@ async def get_refund(refund_id: str) -> Optional[Refund]:
async def get_refunds(hits_ids: Union[str, List[str]]) -> List[Refund]:
+ if len(hits_ids) == 0:
+ return []
+
q = ",".join(["?"] * len(hits_ids))
rows = await db.fetchall(
f"SELECT * FROM boltcards.refunds WHERE hit_id IN ({q})", (*hits_ids,)
From 2e69ebe0800535c1b008d7611fbec6f9b1180b66 Mon Sep 17 00:00:00 2001
From: ben
Date: Tue, 13 Dec 2022 20:01:05 +0000
Subject: [PATCH 21/40] Seems to be working
---
lnbits/extensions/splitpayments/crud.py | 8 ++--
lnbits/extensions/splitpayments/migrations.py | 42 ++++++++++++++++
lnbits/extensions/splitpayments/models.py | 4 +-
.../splitpayments/static/js/index.js | 48 +++++++++----------
lnbits/extensions/splitpayments/tasks.py | 9 +++-
.../templates/splitpayments/index.html | 21 +++++---
lnbits/extensions/splitpayments/views_api.py | 16 ++++---
7 files changed, 105 insertions(+), 43 deletions(-)
diff --git a/lnbits/extensions/splitpayments/crud.py b/lnbits/extensions/splitpayments/crud.py
index ef10add4..67ca9dfe 100644
--- a/lnbits/extensions/splitpayments/crud.py
+++ b/lnbits/extensions/splitpayments/crud.py
@@ -2,6 +2,7 @@ from typing import List
from . import db
from .models import Target
+from loguru import logger
async def get_targets(source_wallet: str) -> List[Target]:
@@ -12,6 +13,7 @@ async def get_targets(source_wallet: str) -> List[Target]:
async def set_targets(source_wallet: str, targets: List[Target]):
+ logger.debug(targets)
async with db.connect() as conn:
await conn.execute(
"DELETE FROM splitpayments.targets WHERE source = ?", (source_wallet,)
@@ -20,8 +22,8 @@ async def set_targets(source_wallet: str, targets: List[Target]):
await conn.execute(
"""
INSERT INTO splitpayments.targets
- (source, wallet, percent, alias)
- VALUES (?, ?, ?, ?)
+ (source, wallet, percent, tag, alias)
+ VALUES (?, ?, ?, ?, ?)
""",
- (source_wallet, target.wallet, target.percent, target.alias),
+ (source_wallet, target.wallet, target.percent, target.tag, target.alias),
)
diff --git a/lnbits/extensions/splitpayments/migrations.py b/lnbits/extensions/splitpayments/migrations.py
index b3921c42..d9ead03f 100644
--- a/lnbits/extensions/splitpayments/migrations.py
+++ b/lnbits/extensions/splitpayments/migrations.py
@@ -1,3 +1,5 @@
+from lnbits.helpers import urlsafe_short_hash
+
async def m001_initial(db):
"""
Initial split payment table.
@@ -52,3 +54,43 @@ async def m002_float_percent(db):
)
await db.execute("DROP TABLE splitpayments.splitpayments_old")
+
+
+async def m003_float_percent(db):
+ """
+ Add float percent and migrates the existing data.
+ """
+ await db.execute("ALTER TABLE splitpayments.targets RENAME TO splitpayments_old")
+ await db.execute(
+ """
+ CREATE TABLE splitpayments.targets (
+ wallet TEXT NOT NULL,
+ source TEXT NOT NULL,
+ percent INT,
+ alias TEXT,
+
+ UNIQUE (source, wallet)
+ );
+ """
+ )
+
+ for row in [
+ list(row)
+ for row in await db.fetchall("SELECT * FROM splitpayments.splitpayments_old")
+ ]:
+ await db.execute(
+ """
+ INSERT INTO splitpayments.targets (
+ id
+ wallet,
+ source,
+ percent,
+ tag,
+ alias
+ )
+ VALUES (?, ?, ?, ?)
+ """,
+ (urlsafe_short_hash(), row[0], row[1], row[2], '', row[3]),
+ )
+
+ await db.execute("DROP TABLE splitpayments.splitpayments_old")
\ No newline at end of file
diff --git a/lnbits/extensions/splitpayments/models.py b/lnbits/extensions/splitpayments/models.py
index 6338d97f..88f98888 100644
--- a/lnbits/extensions/splitpayments/models.py
+++ b/lnbits/extensions/splitpayments/models.py
@@ -8,13 +8,15 @@ class Target(BaseModel):
wallet: str
source: str
percent: float
+ tag: str
alias: Optional[str]
class TargetPutList(BaseModel):
wallet: str = Query(...)
alias: str = Query("")
- percent: float = Query(..., ge=0.01, lt=100)
+ percent: float = Query(..., ge=0, lt=100)
+ tag: str = Query("")
class TargetPut(BaseModel):
diff --git a/lnbits/extensions/splitpayments/static/js/index.js b/lnbits/extensions/splitpayments/static/js/index.js
index 567b8672..9426b41a 100644
--- a/lnbits/extensions/splitpayments/static/js/index.js
+++ b/lnbits/extensions/splitpayments/static/js/index.js
@@ -10,7 +10,7 @@ function hashTargets(targets) {
}
function isTargetComplete(target) {
- return target.wallet && target.wallet.trim() !== '' && target.percent > 0
+ return target.wallet && target.wallet.trim() !== '' && (target.percent > 0 || target.tag != '')
}
new Vue({
@@ -52,33 +52,15 @@ new Vue({
.then(response => {
this.currentHash = hashTargets(response.data)
this.targets = response.data.concat({})
+ for (let i = 0; i < this.targets.length; i++) {
+ if(this.targets[i].tag !=
+ }
})
},
changedWallet(wallet) {
this.selectedWallet = wallet
this.getTargets()
},
- tagChanged(isTag, index) {
- // fix percent min and max range
- if (isTag) {
- this.targets[index].percent = null
- this.targets[index].tag.trim()
- }
-
- // remove empty lines (except last)
- if (this.targets.length >= 2) {
- for (let i = this.targets.length - 2; i >= 0; i--) {
- let target = this.targets[i]
- if (
- (!target.wallet || target.wallet.trim() === '') &&
- (!target.alias || target.alias.trim() === '') &&
- !target.percent
- ) {
- this.targets.splice(i, 1)
- }
- }
- }
- },
clearChanged(index) {
if(this.targets[index].method == 'split'){
this.targets[index].tag = null
@@ -87,12 +69,18 @@ new Vue({
this.targets[index].percent = null
}
},
- percentageChanged(isPercent, index) {
+ targetChanged(index) {
// fix percent min and max range
- if (isPercent) {
+ console.log(this.targets)
+ if (this.targets[index].percent) {
if (this.targets[index].percent > 100) this.targets[index].percent = 100
if (this.targets[index].percent < 0) this.targets[index].percent = 0
}
+
+ // not percentage
+ if (!this.targets[index].percent) {
+ this.targets[index].percent = 0
+ }
// remove empty lines (except last)
if (this.targets.length >= 2) {
@@ -101,6 +89,7 @@ new Vue({
if (
(!target.wallet || target.wallet.trim() === '') &&
(!target.alias || target.alias.trim() === '') &&
+ (!target.tag || target.tag.trim() === '') &&
!target.percent
) {
this.targets.splice(i, 1)
@@ -145,6 +134,15 @@ new Vue({
console.log(this.targets)
},
saveTargets() {
+ console.log(this.targets)
+ for (let i = 0; i < this.targets.length; i++) {
+ if (this.targets[i].tag){
+ this.targets[i].percent = 0
+ }
+ else{
+ this.targets[i].tag = ''
+ }
+ }
LNbits.api
.request(
'PUT',
@@ -153,7 +151,7 @@ new Vue({
{
targets: this.targets
.filter(isTargetComplete)
- .map(({wallet, percent, alias}) => ({wallet, percent, alias}))
+ .map(({wallet, percent, tag, alias}) => ({wallet, percent, tag, alias}))
}
)
.then(response => {
diff --git a/lnbits/extensions/splitpayments/tasks.py b/lnbits/extensions/splitpayments/tasks.py
index 53378b20..c5cac4a6 100644
--- a/lnbits/extensions/splitpayments/tasks.py
+++ b/lnbits/extensions/splitpayments/tasks.py
@@ -37,7 +37,14 @@ async def on_invoice_paid(payment: Payment) -> None:
logger.debug(f"performing split payments to {len(targets)} targets")
for target in targets:
- amount = int(payment.amount * target.percent / 100) # msats
+
+ if target.tag and payment.extra.get("tag") == target.tag:
+ amount = int(payment.amount)
+ elif target.percent:
+ amount = int(payment.amount * target.percent / 100) # msats
+ else:
+ return
+
payment_hash, payment_request = await create_invoice(
wallet_id=target.wallet,
amount=int(amount / 1000), # sats
diff --git a/lnbits/extensions/splitpayments/templates/splitpayments/index.html b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
index 5a3f7895..86a711f0 100644
--- a/lnbits/extensions/splitpayments/templates/splitpayments/index.html
+++ b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
@@ -59,6 +59,7 @@
:true-value="'tag'"
color="primary"
label=""
+ value="True"
style="width: 180px"
v-model="target.method"
:label="`${target.method}` === 'tag' ? 'Send funds by tag' : `${target.method}` === 'split' ? 'Split funds by %' : 'Split/tag?'"
@@ -66,14 +67,13 @@
>
+
+ Add more
+
-
@@ -101,7 +110,7 @@
unelevated
color="primary"
type="submit"
- :disabled="!isDirty"
+ :disabled="targets.length < 2"
>
Save Targets
diff --git a/lnbits/extensions/splitpayments/views_api.py b/lnbits/extensions/splitpayments/views_api.py
index 8b5a282a..1599d336 100644
--- a/lnbits/extensions/splitpayments/views_api.py
+++ b/lnbits/extensions/splitpayments/views_api.py
@@ -11,6 +11,8 @@ from . import splitpayments_ext
from .crud import get_targets, set_targets
from .models import Target, TargetPut
+from loguru import logger
+
@splitpayments_ext.get("/api/v1/targets")
async def api_targets_get(wallet: WalletTypeInfo = Depends(require_admin_key)):
@@ -25,6 +27,7 @@ async def api_targets_set(
body = await req.json()
targets = []
data = TargetPut.parse_obj(body["targets"])
+ logger.debug(data)
for entry in data.__root__:
wallet = await get_wallet(entry.wallet)
if not wallet:
@@ -50,16 +53,15 @@ async def api_targets_set(
Target(
wallet=wallet.id,
source=wal.wallet.id,
+ tag=entry.tag,
percent=entry.percent,
alias=entry.alias,
)
)
-
- percent_sum = sum([target.percent for target in targets])
- if percent_sum > 100:
- raise HTTPException(
- status_code=HTTPStatus.BAD_REQUEST, detail="Splitting over 100%."
- )
-
+ percent_sum = sum([target.percent for target in targets])
+ if percent_sum > 100:
+ raise HTTPException(
+ status_code=HTTPStatus.BAD_REQUEST, detail="Splitting over 100%."
+ )
await set_targets(wal.wallet.id, targets)
return ""
From 100cf17e4b86d33573051ec19e84f6908a181bb2 Mon Sep 17 00:00:00 2001
From: ben
Date: Tue, 13 Dec 2022 20:07:16 +0000
Subject: [PATCH 22/40] fixed percent
---
lnbits/extensions/splitpayments/migrations.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/splitpayments/migrations.py b/lnbits/extensions/splitpayments/migrations.py
index d9ead03f..58ca90bc 100644
--- a/lnbits/extensions/splitpayments/migrations.py
+++ b/lnbits/extensions/splitpayments/migrations.py
@@ -56,7 +56,7 @@ async def m002_float_percent(db):
await db.execute("DROP TABLE splitpayments.splitpayments_old")
-async def m003_float_percent(db):
+async def m003_add_id_and_tag(db):
"""
Add float percent and migrates the existing data.
"""
@@ -66,7 +66,7 @@ async def m003_float_percent(db):
CREATE TABLE splitpayments.targets (
wallet TEXT NOT NULL,
source TEXT NOT NULL,
- percent INT,
+ percent REAL NOT NULL CHECK (percent >= 0 AND percent <= 100),
alias TEXT,
UNIQUE (source, wallet)
From e161c5d1168dbf54067ec4222c4dba39b6034c7f Mon Sep 17 00:00:00 2001
From: ben
Date: Tue, 13 Dec 2022 20:11:07 +0000
Subject: [PATCH 23/40] fixed migration
---
lnbits/extensions/splitpayments/migrations.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/splitpayments/migrations.py b/lnbits/extensions/splitpayments/migrations.py
index 58ca90bc..4163062f 100644
--- a/lnbits/extensions/splitpayments/migrations.py
+++ b/lnbits/extensions/splitpayments/migrations.py
@@ -64,9 +64,11 @@ async def m003_add_id_and_tag(db):
await db.execute(
"""
CREATE TABLE splitpayments.targets (
+ id TEXT PRIMARY KEY,
wallet TEXT NOT NULL,
source TEXT NOT NULL,
percent REAL NOT NULL CHECK (percent >= 0 AND percent <= 100),
+ tag TEXT NOT NULL,
alias TEXT,
UNIQUE (source, wallet)
@@ -81,7 +83,7 @@ async def m003_add_id_and_tag(db):
await db.execute(
"""
INSERT INTO splitpayments.targets (
- id
+ id,
wallet,
source,
percent,
From c91b5031bfb69cf0b0c9a499bfb516afb3d4f1b9 Mon Sep 17 00:00:00 2001
From: ben
Date: Tue, 13 Dec 2022 20:17:52 +0000
Subject: [PATCH 24/40] migration working
---
lnbits/extensions/splitpayments/migrations.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/splitpayments/migrations.py b/lnbits/extensions/splitpayments/migrations.py
index 4163062f..cb72b990 100644
--- a/lnbits/extensions/splitpayments/migrations.py
+++ b/lnbits/extensions/splitpayments/migrations.py
@@ -90,7 +90,7 @@ async def m003_add_id_and_tag(db):
tag,
alias
)
- VALUES (?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?)
""",
(urlsafe_short_hash(), row[0], row[1], row[2], '', row[3]),
)
From 5f53eb8e4e77bdb146c82698d60207b853d75f95 Mon Sep 17 00:00:00 2001
From: ben
Date: Tue, 13 Dec 2022 23:41:40 +0000
Subject: [PATCH 25/40] tagged working
---
lnbits/extensions/splitpayments/models.py | 2 +-
.../splitpayments/static/js/index.js | 31 +++++++---
lnbits/extensions/splitpayments/tasks.py | 62 ++++++++++++-------
.../templates/splitpayments/index.html | 3 +-
4 files changed, 67 insertions(+), 31 deletions(-)
diff --git a/lnbits/extensions/splitpayments/models.py b/lnbits/extensions/splitpayments/models.py
index 88f98888..fc3db2c6 100644
--- a/lnbits/extensions/splitpayments/models.py
+++ b/lnbits/extensions/splitpayments/models.py
@@ -16,7 +16,7 @@ class TargetPutList(BaseModel):
wallet: str = Query(...)
alias: str = Query("")
percent: float = Query(..., ge=0, lt=100)
- tag: str = Query("")
+ tag: str
class TargetPut(BaseModel):
diff --git a/lnbits/extensions/splitpayments/static/js/index.js b/lnbits/extensions/splitpayments/static/js/index.js
index 9426b41a..9d7eb409 100644
--- a/lnbits/extensions/splitpayments/static/js/index.js
+++ b/lnbits/extensions/splitpayments/static/js/index.js
@@ -39,6 +39,15 @@ new Vue({
timeout: 500
})
},
+ clearTarget(index) {
+ this.targets.splice(index, 1)
+ console.log(this.targets)
+ this.$q.notify({
+ message:
+ 'Removed item. You must click to save manually.',
+ timeout: 500
+ })
+ },
getTargets() {
LNbits.api
.request(
@@ -52,8 +61,17 @@ new Vue({
.then(response => {
this.currentHash = hashTargets(response.data)
this.targets = response.data.concat({})
- for (let i = 0; i < this.targets.length; i++) {
- if(this.targets[i].tag !=
+ for (let i = 0; i < this.targets.length; i++) {
+ if(this.targets[i].tag.length > 0){
+ this.targets[i].method = "tag"
+ }
+ else if (this.targets[i].percent.length > 0){
+ this.targets[i].method = "split"
+ }
+ else{
+ this.targets[i].method = ""
+ }
+
}
})
},
@@ -64,17 +82,19 @@ new Vue({
clearChanged(index) {
if(this.targets[index].method == 'split'){
this.targets[index].tag = null
+ this.targets[index].method = 'split'
}
else{
this.targets[index].percent = null
+ this.targets[index].method = 'tag'
}
},
targetChanged(index) {
// fix percent min and max range
- console.log(this.targets)
if (this.targets[index].percent) {
if (this.targets[index].percent > 100) this.targets[index].percent = 100
if (this.targets[index].percent < 0) this.targets[index].percent = 0
+ this.targets[index].tag = ""
}
// not percentage
@@ -128,15 +148,12 @@ new Vue({
if (t !== index) target.percent -= +(diff * target.percent).toFixed(2)
})
}
-
// overwrite so changes appear
this.targets = this.targets
- console.log(this.targets)
},
saveTargets() {
- console.log(this.targets)
for (let i = 0; i < this.targets.length; i++) {
- if (this.targets[i].tag){
+ if (this.targets[i].tag != ''){
this.targets[i].percent = 0
}
else{
diff --git a/lnbits/extensions/splitpayments/tasks.py b/lnbits/extensions/splitpayments/tasks.py
index c5cac4a6..2bbefd1d 100644
--- a/lnbits/extensions/splitpayments/tasks.py
+++ b/lnbits/extensions/splitpayments/tasks.py
@@ -25,7 +25,7 @@ async def on_invoice_paid(payment: Payment) -> None:
return
targets = await get_targets(payment.wallet_id)
-
+ logger.debug(targets)
if not targets:
return
@@ -35,28 +35,46 @@ async def on_invoice_paid(payment: Payment) -> None:
logger.error("splitpayment failure: total percent adds up to more than 100%")
return
- logger.debug(f"performing split payments to {len(targets)} targets")
+ logger.debug(f"checking if tagged for {len(targets)} targets")
+ tagged = False
for target in targets:
-
- if target.tag and payment.extra.get("tag") == target.tag:
- amount = int(payment.amount)
- elif target.percent:
+ if payment.extra.get("tag") == target.tag:
+ tagged = True
+ payment_hash, payment_request = await create_invoice(
+ wallet_id=target.wallet,
+ amount=int(payment.amount / 1000), # sats
+ internal=True,
+ memo=f"Pushed tagged payment to {target.alias}",
+ extra={"tag": "splitpayments"},
+ )
+ logger.debug(f"created split invoice: {payment_hash}")
+
+ checking_id = await pay_invoice(
+ payment_request=payment_request,
+ wallet_id=payment.wallet_id,
+ extra={"tag": "splitpayments"},
+ )
+ logger.debug(f"paid split invoice: {checking_id}")
+
+ logger.debug(f"performing split to {len(targets)} targets")
+ logger.debug("pitbull")
+ if tagged == False:
+ for target in targets:
amount = int(payment.amount * target.percent / 100) # msats
- else:
- return
+ payment_hash, payment_request = await create_invoice(
+ wallet_id=target.wallet,
+ amount=int(amount / 1000), # sats
+ internal=True,
+ memo=f"split payment: {target.percent}% for {target.alias or target.wallet}",
+ extra={"tag": "splitpayments"},
+ )
+ logger.debug(f"created split invoice: {payment_hash}")
+
+ checking_id = await pay_invoice(
+ payment_request=payment_request,
+ wallet_id=payment.wallet_id,
+ extra={"tag": "splitpayments"},
+ )
+ logger.debug(f"paid split invoice: {checking_id}")
- payment_hash, payment_request = await create_invoice(
- wallet_id=target.wallet,
- amount=int(amount / 1000), # sats
- internal=True,
- memo=f"split payment: {target.percent}% for {target.alias or target.wallet}",
- extra={"tag": "splitpayments"},
- )
- logger.debug(f"created split invoice: {payment_hash}")
- checking_id = await pay_invoice(
- payment_request=payment_request,
- wallet_id=payment.wallet_id,
- extra={"tag": "splitpayments"},
- )
- logger.debug(f"paid split invoice: {checking_id}")
diff --git a/lnbits/extensions/splitpayments/templates/splitpayments/index.html b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
index 86a711f0..8154f4c7 100644
--- a/lnbits/extensions/splitpayments/templates/splitpayments/index.html
+++ b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
@@ -67,7 +67,7 @@
>
Add more
+
From f506b010f60533b3fd98591d624b54e9674d98b6 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 14 Dec 2022 00:04:03 +0000
Subject: [PATCH 26/40] Working
---
lnbits/extensions/splitpayments/tasks.py | 33 ++++++++++++------------
1 file changed, 17 insertions(+), 16 deletions(-)
diff --git a/lnbits/extensions/splitpayments/tasks.py b/lnbits/extensions/splitpayments/tasks.py
index 2bbefd1d..08931a20 100644
--- a/lnbits/extensions/splitpayments/tasks.py
+++ b/lnbits/extensions/splitpayments/tasks.py
@@ -57,24 +57,25 @@ async def on_invoice_paid(payment: Payment) -> None:
logger.debug(f"paid split invoice: {checking_id}")
logger.debug(f"performing split to {len(targets)} targets")
- logger.debug("pitbull")
+
if tagged == False:
for target in targets:
- amount = int(payment.amount * target.percent / 100) # msats
- payment_hash, payment_request = await create_invoice(
- wallet_id=target.wallet,
- amount=int(amount / 1000), # sats
- internal=True,
- memo=f"split payment: {target.percent}% for {target.alias or target.wallet}",
- extra={"tag": "splitpayments"},
- )
- logger.debug(f"created split invoice: {payment_hash}")
+ if target.percent > 0:
+ amount = int(payment.amount * target.percent / 100) # msats
+ payment_hash, payment_request = await create_invoice(
+ wallet_id=target.wallet,
+ amount=int(amount / 1000), # sats
+ internal=True,
+ memo=f"split payment: {target.percent}% for {target.alias or target.wallet}",
+ extra={"tag": "splitpayments"},
+ )
+ logger.debug(f"created split invoice: {payment_hash}")
- checking_id = await pay_invoice(
- payment_request=payment_request,
- wallet_id=payment.wallet_id,
- extra={"tag": "splitpayments"},
- )
- logger.debug(f"paid split invoice: {checking_id}")
+ checking_id = await pay_invoice(
+ payment_request=payment_request,
+ wallet_id=payment.wallet_id,
+ extra={"tag": "splitpayments"},
+ )
+ logger.debug(f"paid split invoice: {checking_id}")
From 75e6252d23504c5e6c7a8cac885c2acc8116ff48 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 14 Dec 2022 00:06:36 +0000
Subject: [PATCH 27/40] formatted
---
lnbits/extensions/splitpayments/crud.py | 10 +++-
lnbits/extensions/splitpayments/migrations.py | 5 +-
.../splitpayments/static/js/index.js | 55 ++++++++++---------
lnbits/extensions/splitpayments/tasks.py | 6 +-
.../templates/splitpayments/index.html | 34 +++++++-----
lnbits/extensions/splitpayments/views_api.py | 3 -
6 files changed, 62 insertions(+), 51 deletions(-)
diff --git a/lnbits/extensions/splitpayments/crud.py b/lnbits/extensions/splitpayments/crud.py
index 67ca9dfe..65908ad4 100644
--- a/lnbits/extensions/splitpayments/crud.py
+++ b/lnbits/extensions/splitpayments/crud.py
@@ -2,7 +2,6 @@ from typing import List
from . import db
from .models import Target
-from loguru import logger
async def get_targets(source_wallet: str) -> List[Target]:
@@ -13,7 +12,6 @@ async def get_targets(source_wallet: str) -> List[Target]:
async def set_targets(source_wallet: str, targets: List[Target]):
- logger.debug(targets)
async with db.connect() as conn:
await conn.execute(
"DELETE FROM splitpayments.targets WHERE source = ?", (source_wallet,)
@@ -25,5 +23,11 @@ async def set_targets(source_wallet: str, targets: List[Target]):
(source, wallet, percent, tag, alias)
VALUES (?, ?, ?, ?, ?)
""",
- (source_wallet, target.wallet, target.percent, target.tag, target.alias),
+ (
+ source_wallet,
+ target.wallet,
+ target.percent,
+ target.tag,
+ target.alias,
+ ),
)
diff --git a/lnbits/extensions/splitpayments/migrations.py b/lnbits/extensions/splitpayments/migrations.py
index cb72b990..eb72387e 100644
--- a/lnbits/extensions/splitpayments/migrations.py
+++ b/lnbits/extensions/splitpayments/migrations.py
@@ -1,5 +1,6 @@
from lnbits.helpers import urlsafe_short_hash
+
async def m001_initial(db):
"""
Initial split payment table.
@@ -92,7 +93,7 @@ async def m003_add_id_and_tag(db):
)
VALUES (?, ?, ?, ?, ?, ?)
""",
- (urlsafe_short_hash(), row[0], row[1], row[2], '', row[3]),
+ (urlsafe_short_hash(), row[0], row[1], row[2], "", row[3]),
)
- await db.execute("DROP TABLE splitpayments.splitpayments_old")
\ No newline at end of file
+ await db.execute("DROP TABLE splitpayments.splitpayments_old")
diff --git a/lnbits/extensions/splitpayments/static/js/index.js b/lnbits/extensions/splitpayments/static/js/index.js
index 9d7eb409..f5f16276 100644
--- a/lnbits/extensions/splitpayments/static/js/index.js
+++ b/lnbits/extensions/splitpayments/static/js/index.js
@@ -10,7 +10,11 @@ function hashTargets(targets) {
}
function isTargetComplete(target) {
- return target.wallet && target.wallet.trim() !== '' && (target.percent > 0 || target.tag != '')
+ return (
+ target.wallet &&
+ target.wallet.trim() !== '' &&
+ (target.percent > 0 || target.tag != '')
+ )
}
new Vue({
@@ -20,9 +24,11 @@ new Vue({
return {
selectedWallet: null,
currentHash: '', // a string that must match if the edit data is unchanged
- targets: [{
- method: "split"
- }]
+ targets: [
+ {
+ method: 'split'
+ }
+ ]
}
},
computed: {
@@ -43,8 +49,7 @@ new Vue({
this.targets.splice(index, 1)
console.log(this.targets)
this.$q.notify({
- message:
- 'Removed item. You must click to save manually.',
+ message: 'Removed item. You must click to save manually.',
timeout: 500
})
},
@@ -61,17 +66,14 @@ new Vue({
.then(response => {
this.currentHash = hashTargets(response.data)
this.targets = response.data.concat({})
- for (let i = 0; i < this.targets.length; i++) {
- if(this.targets[i].tag.length > 0){
- this.targets[i].method = "tag"
+ for (let i = 0; i < this.targets.length; i++) {
+ if (this.targets[i].tag.length > 0) {
+ this.targets[i].method = 'tag'
+ } else if (this.targets[i].percent.length > 0) {
+ this.targets[i].method = 'split'
+ } else {
+ this.targets[i].method = ''
}
- else if (this.targets[i].percent.length > 0){
- this.targets[i].method = "split"
- }
- else{
- this.targets[i].method = ""
- }
-
}
})
},
@@ -80,11 +82,10 @@ new Vue({
this.getTargets()
},
clearChanged(index) {
- if(this.targets[index].method == 'split'){
+ if (this.targets[index].method == 'split') {
this.targets[index].tag = null
this.targets[index].method = 'split'
- }
- else{
+ } else {
this.targets[index].percent = null
this.targets[index].method = 'tag'
}
@@ -94,14 +95,14 @@ new Vue({
if (this.targets[index].percent) {
if (this.targets[index].percent > 100) this.targets[index].percent = 100
if (this.targets[index].percent < 0) this.targets[index].percent = 0
- this.targets[index].tag = ""
+ this.targets[index].tag = ''
}
// not percentage
if (!this.targets[index].percent) {
this.targets[index].percent = 0
}
-
+
// remove empty lines (except last)
if (this.targets.length >= 2) {
for (let i = this.targets.length - 2; i >= 0; i--) {
@@ -153,10 +154,9 @@ new Vue({
},
saveTargets() {
for (let i = 0; i < this.targets.length; i++) {
- if (this.targets[i].tag != ''){
+ if (this.targets[i].tag != '') {
this.targets[i].percent = 0
- }
- else{
+ } else {
this.targets[i].tag = ''
}
}
@@ -168,7 +168,12 @@ new Vue({
{
targets: this.targets
.filter(isTargetComplete)
- .map(({wallet, percent, tag, alias}) => ({wallet, percent, tag, alias}))
+ .map(({wallet, percent, tag, alias}) => ({
+ wallet,
+ percent,
+ tag,
+ alias
+ }))
}
)
.then(response => {
diff --git a/lnbits/extensions/splitpayments/tasks.py b/lnbits/extensions/splitpayments/tasks.py
index 08931a20..a1832b63 100644
--- a/lnbits/extensions/splitpayments/tasks.py
+++ b/lnbits/extensions/splitpayments/tasks.py
@@ -55,9 +55,9 @@ async def on_invoice_paid(payment: Payment) -> None:
extra={"tag": "splitpayments"},
)
logger.debug(f"paid split invoice: {checking_id}")
-
+
logger.debug(f"performing split to {len(targets)} targets")
-
+
if tagged == False:
for target in targets:
if target.percent > 0:
@@ -77,5 +77,3 @@ async def on_invoice_paid(payment: Payment) -> None:
extra={"tag": "splitpayments"},
)
logger.debug(f"paid split invoice: {checking_id}")
-
-
diff --git a/lnbits/extensions/splitpayments/templates/splitpayments/index.html b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
index 8154f4c7..b105bf2c 100644
--- a/lnbits/extensions/splitpayments/templates/splitpayments/index.html
+++ b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
@@ -31,7 +31,7 @@
style="flex-wrap: nowrap"
v-for="(target, t) in targets"
>
-
-
+
-
-
+
Add more
-
+
diff --git a/lnbits/extensions/splitpayments/views_api.py b/lnbits/extensions/splitpayments/views_api.py
index 1599d336..41a11c94 100644
--- a/lnbits/extensions/splitpayments/views_api.py
+++ b/lnbits/extensions/splitpayments/views_api.py
@@ -11,8 +11,6 @@ from . import splitpayments_ext
from .crud import get_targets, set_targets
from .models import Target, TargetPut
-from loguru import logger
-
@splitpayments_ext.get("/api/v1/targets")
async def api_targets_get(wallet: WalletTypeInfo = Depends(require_admin_key)):
@@ -27,7 +25,6 @@ async def api_targets_set(
body = await req.json()
targets = []
data = TargetPut.parse_obj(body["targets"])
- logger.debug(data)
for entry in data.__root__:
wallet = await get_wallet(entry.wallet)
if not wallet:
From 4c3db72afcffd0b4491b8794d8f849ee5ce2b298 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 14 Dec 2022 00:31:09 +0000
Subject: [PATCH 28/40] Added ID
---
lnbits/extensions/splitpayments/crud.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/splitpayments/crud.py b/lnbits/extensions/splitpayments/crud.py
index 65908ad4..2b556f87 100644
--- a/lnbits/extensions/splitpayments/crud.py
+++ b/lnbits/extensions/splitpayments/crud.py
@@ -3,6 +3,8 @@ from typing import List
from . import db
from .models import Target
+from lnbits.helpers import urlsafe_short_hash
+
async def get_targets(source_wallet: str) -> List[Target]:
rows = await db.fetchall(
@@ -20,10 +22,11 @@ async def set_targets(source_wallet: str, targets: List[Target]):
await conn.execute(
"""
INSERT INTO splitpayments.targets
- (source, wallet, percent, tag, alias)
- VALUES (?, ?, ?, ?, ?)
+ (id, source, wallet, percent, tag, alias)
+ VALUES (?, ?, ?, ?, ?, ?)
""",
(
+ urlsafe_short_hash(),
source_wallet,
target.wallet,
target.percent,
From 59c6469bd1dedbd07b5e515a3101fa2215dcda12 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 14 Dec 2022 14:43:41 +0000
Subject: [PATCH 29/40] generalised tag search
---
lnbits/extensions/splitpayments/tasks.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/splitpayments/tasks.py b/lnbits/extensions/splitpayments/tasks.py
index a1832b63..33768805 100644
--- a/lnbits/extensions/splitpayments/tasks.py
+++ b/lnbits/extensions/splitpayments/tasks.py
@@ -38,7 +38,7 @@ async def on_invoice_paid(payment: Payment) -> None:
logger.debug(f"checking if tagged for {len(targets)} targets")
tagged = False
for target in targets:
- if payment.extra.get("tag") == target.tag:
+ if target.tag in payment.extra:
tagged = True
payment_hash, payment_request = await create_invoice(
wallet_id=target.wallet,
From 60a01cd89a954a952ff9284f36b26a0f783ad765 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 14 Dec 2022 14:53:09 +0000
Subject: [PATCH 30/40] fomrat
---
lnbits/extensions/splitpayments/crud.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/splitpayments/crud.py b/lnbits/extensions/splitpayments/crud.py
index 2b556f87..d7ac329c 100644
--- a/lnbits/extensions/splitpayments/crud.py
+++ b/lnbits/extensions/splitpayments/crud.py
@@ -1,9 +1,11 @@
from typing import List
+from lnbits.helpers import urlsafe_short_hash
+
from . import db
from .models import Target
-from lnbits.helpers import urlsafe_short_hash
+
async def get_targets(source_wallet: str) -> List[Target]:
From 475ff47f5b9b756445a992a18964c5e3e84cc728 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 14 Dec 2022 14:56:04 +0000
Subject: [PATCH 31/40] isort
---
lnbits/extensions/splitpayments/crud.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/lnbits/extensions/splitpayments/crud.py b/lnbits/extensions/splitpayments/crud.py
index d7ac329c..de4e0822 100644
--- a/lnbits/extensions/splitpayments/crud.py
+++ b/lnbits/extensions/splitpayments/crud.py
@@ -6,8 +6,6 @@ from . import db
from .models import Target
-
-
async def get_targets(source_wallet: str) -> List[Target]:
rows = await db.fetchall(
"SELECT * FROM splitpayments.targets WHERE source = ?", (source_wallet,)
From 210c73618106eb046cf96d8914839e837bc177f8 Mon Sep 17 00:00:00 2001
From: Tiago Vasconcelos
Date: Thu, 15 Dec 2022 14:41:24 +0000
Subject: [PATCH 32/40] remove icon add button for better UX
---
.../paywall/templates/paywall/display.html | 22 +++++++++----------
1 file changed, 10 insertions(+), 12 deletions(-)
diff --git a/lnbits/extensions/paywall/templates/paywall/display.html b/lnbits/extensions/paywall/templates/paywall/display.html
index b15db83e..c6c8645d 100644
--- a/lnbits/extensions/paywall/templates/paywall/display.html
+++ b/lnbits/extensions/paywall/templates/paywall/display.html
@@ -18,19 +18,17 @@
label="Choose an amount *"
:hint="'Minimum ' + paywallAmount + ' sat'"
>
-
-
-
+
+ Send
+
From 6a44ac342535384301252b9e1bd62bd4051291c3 Mon Sep 17 00:00:00 2001
From: Tiago Vasconcelos
Date: Thu, 15 Dec 2022 14:56:30 +0000
Subject: [PATCH 33/40] fix button type
---
lnbits/extensions/paywall/templates/paywall/display.html | 1 -
1 file changed, 1 deletion(-)
diff --git a/lnbits/extensions/paywall/templates/paywall/display.html b/lnbits/extensions/paywall/templates/paywall/display.html
index c6c8645d..df2afa24 100644
--- a/lnbits/extensions/paywall/templates/paywall/display.html
+++ b/lnbits/extensions/paywall/templates/paywall/display.html
@@ -24,7 +24,6 @@
unelevated
color="primary"
:disabled="userAmount < paywallAmount || paymentReq"
- type="submit"
@click="createInvoice"
>Send
From 90679f54eece6aad3e230dc22bf50102706f9069 Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Thu, 15 Dec 2022 16:28:17 +0100
Subject: [PATCH 34/40] handling some errors
---
lnbits/extensions/boltcards/crud.py | 6 ++++++
lnbits/extensions/boltcards/lnurl.py | 21 +++++++++++++++------
2 files changed, 21 insertions(+), 6 deletions(-)
diff --git a/lnbits/extensions/boltcards/crud.py b/lnbits/extensions/boltcards/crud.py
index c541346e..4fae31f9 100644
--- a/lnbits/extensions/boltcards/crud.py
+++ b/lnbits/extensions/boltcards/crud.py
@@ -171,6 +171,9 @@ async def get_hit(hit_id: str) -> Optional[Hit]:
async def get_hits(cards_ids: Union[str, List[str]]) -> List[Hit]:
+ if len(cards_ids) == 0:
+ return []
+
q = ",".join(["?"] * len(cards_ids))
rows = await db.fetchall(
f"SELECT * FROM boltcards.hits WHERE card_id IN ({q})", (*cards_ids,)
@@ -265,6 +268,9 @@ async def get_refund(refund_id: str) -> Optional[Refund]:
async def get_refunds(hits_ids: Union[str, List[str]]) -> List[Refund]:
+ if len(hits_ids) == 0:
+ return []
+
q = ",".join(["?"] * len(hits_ids))
rows = await db.fetchall(
f"SELECT * FROM boltcards.refunds WHERE hit_id IN ({q})", (*hits_ids,)
diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py
index 6fb9ad8d..8a05d2ef 100644
--- a/lnbits/extensions/boltcards/lnurl.py
+++ b/lnbits/extensions/boltcards/lnurl.py
@@ -108,15 +108,24 @@ async def lnurl_callback(
pr: str = Query(None),
k1: str = Query(None),
):
+ if not k1:
+ return {"status": "ERROR", "reason": "Missing K1 token"}
+
hit = await get_hit(k1)
- card = await get_card(hit.card_id)
+
if not hit:
- return {"status": "ERROR", "reason": f"LNURL-pay record not found."}
- if hit.id != k1:
- return {"status": "ERROR", "reason": "Bad K1"}
+ return {"status": "ERROR", "reason": "Record not found for this charge (bad k1)"}
if hit.spent:
- return {"status": "ERROR", "reason": f"Payment already claimed"}
- invoice = bolt11.decode(pr)
+ return {"status": "ERROR", "reason": "Payment already claimed"}
+ if not pr:
+ return {"status": "ERROR", "reason": "Missing payment request"}
+
+ try:
+ invoice = bolt11.decode(pr)
+ except:
+ return {"status": "ERROR", "reason": "Failed to decode payment request"}
+
+ card = await get_card(hit.card_id)
hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000))
try:
await pay_invoice(
From b8bd9cc807d485db031070f9083d0af3074c5dce Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Thu, 15 Dec 2022 16:46:49 +0100
Subject: [PATCH 35/40] format
---
lnbits/extensions/boltcards/lnurl.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py
index 8a05d2ef..3974e529 100644
--- a/lnbits/extensions/boltcards/lnurl.py
+++ b/lnbits/extensions/boltcards/lnurl.py
@@ -114,7 +114,10 @@ async def lnurl_callback(
hit = await get_hit(k1)
if not hit:
- return {"status": "ERROR", "reason": "Record not found for this charge (bad k1)"}
+ return {
+ "status": "ERROR",
+ "reason": "Record not found for this charge (bad k1)",
+ }
if hit.spent:
return {"status": "ERROR", "reason": "Payment already claimed"}
if not pr:
From 3aee76be39ec48398ddf7ffa93623f7d7c48f58f Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Fri, 16 Dec 2022 11:09:10 +0100
Subject: [PATCH 36/40] reducing the PR
---
lnbits/core/services.py | 32 +------------------
lnbits/extensions/boltcards/crud.py | 12 ++-----
lnbits/extensions/boltcards/lnurl.py | 16 ----------
lnbits/extensions/boltcards/migrations.py | 8 -----
lnbits/extensions/boltcards/models.py | 2 --
.../extensions/boltcards/static/js/index.js | 4 +--
lnbits/extensions/boltcards/tasks.py | 21 +-----------
.../boltcards/templates/boltcards/index.html | 10 ------
lnbits/extensions/boltcards/views_api.py | 16 ----------
lnbits/extensions/withdraw/lnurl.py | 30 ++++++++++-------
10 files changed, 23 insertions(+), 128 deletions(-)
diff --git a/lnbits/core/services.py b/lnbits/core/services.py
index 4b6cc8e4..5d993b4c 100644
--- a/lnbits/core/services.py
+++ b/lnbits/core/services.py
@@ -2,7 +2,7 @@ import asyncio
import json
from binascii import unhexlify
from io import BytesIO
-from typing import Dict, Optional, Tuple, Union
+from typing import Dict, Optional, Tuple
from urllib.parse import parse_qs, urlparse
import httpx
@@ -102,7 +102,6 @@ async def pay_invoice(
extra: Optional[Dict] = None,
description: str = "",
conn: Optional[Connection] = None,
- webhook: Optional[Union[str, tuple]] = None,
) -> str:
"""
Pay a Lightning invoice.
@@ -232,35 +231,6 @@ async def pay_invoice(
f"didn't receive checking_id from backend, payment may be stuck in database: {temp_id}"
)
- additionals = None
- if type(webhook) is str:
- webhook_url = webhook
- elif type(webhook) is tuple:
- webhook_url = webhook[0]
- additionals = webhook[1]
- else:
- webhook_url = None
-
- if webhook_url:
- async with httpx.AsyncClient() as client:
- try:
- json = {
- "payment_hash": invoice.payment_hash,
- "payment_request": payment_request,
- "amount": int(invoice.amount_msat / 1000),
- }
- if type(additionals) is dict:
- json.update(additionals)
-
- r = await client.post(
- webhook_url,
- json=json,
- timeout=40,
- )
- except Exception as exc:
- # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid
- logger.error("Caught exception when dispatching webhook url:", exc)
-
return invoice.payment_hash
diff --git a/lnbits/extensions/boltcards/crud.py b/lnbits/extensions/boltcards/crud.py
index 346b5522..f15deb69 100644
--- a/lnbits/extensions/boltcards/crud.py
+++ b/lnbits/extensions/boltcards/crud.py
@@ -27,10 +27,9 @@ async def create_card(data: CreateCardData, wallet_id: str) -> Card:
k0,
k1,
k2,
- otp,
- webhook_url
+ otp
)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
card_id,
@@ -46,7 +45,6 @@ async def create_card(data: CreateCardData, wallet_id: str) -> Card:
data.k1,
data.k2,
secrets.token_hex(16),
- data.webhook_url,
),
)
card = await get_card(card_id)
@@ -179,9 +177,6 @@ async def get_hit(hit_id: str) -> Optional[Hit]:
async def get_hits(cards_ids: Union[str, List[str]]) -> List[Hit]:
- if len(cards_ids) == 0:
- return []
-
q = ",".join(["?"] * len(cards_ids))
rows = await db.fetchall(
f"SELECT * FROM boltcards.hits WHERE card_id IN ({q})", (*cards_ids,)
@@ -276,9 +271,6 @@ async def get_refund(refund_id: str) -> Optional[Refund]:
async def get_refunds(hits_ids: Union[str, List[str]]) -> List[Refund]:
- if len(hits_ids) == 0:
- return []
-
q = ",".join(["?"] * len(hits_ids))
rows = await db.fetchall(
f"SELECT * FROM boltcards.refunds WHERE hit_id IN ({q})", (*hits_ids,)
diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py
index cd4c6ba4..7588f56e 100644
--- a/lnbits/extensions/boltcards/lnurl.py
+++ b/lnbits/extensions/boltcards/lnurl.py
@@ -112,27 +112,11 @@ async def lnurl_callback(
invoice = bolt11.decode(pr)
hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000))
try:
- webhook = (
- (
- card.webhook_url,
- {
- "notification": "card_payment",
- "card_external_id": card.external_id,
- "card_name": card.card_name,
- "card_otp": card.otp[
- -16:
- ], # actually only half of the OTP is sent (full otp reveals the keys)
- },
- )
- if card.webhook_url
- else None
- )
await pay_invoice(
wallet_id=card.wallet,
payment_request=pr,
max_sat=card.tx_limit,
extra={"tag": "boltcard", "tag": hit.id},
- webhook=webhook,
)
return {"status": "OK"}
diff --git a/lnbits/extensions/boltcards/migrations.py b/lnbits/extensions/boltcards/migrations.py
index 64bb2dc9..9609e0c3 100644
--- a/lnbits/extensions/boltcards/migrations.py
+++ b/lnbits/extensions/boltcards/migrations.py
@@ -58,11 +58,3 @@ async def m001_initial(db):
);
"""
)
-
-
-async def m002_add_webhook(db):
- await db.execute(
- """
- ALTER TABLE boltcards.cards ADD COLUMN webhook_url TEXT NOT NULL DEFAULT '';
- """
- )
diff --git a/lnbits/extensions/boltcards/models.py b/lnbits/extensions/boltcards/models.py
index 8e6f77c9..47ca1df0 100644
--- a/lnbits/extensions/boltcards/models.py
+++ b/lnbits/extensions/boltcards/models.py
@@ -30,7 +30,6 @@ class Card(BaseModel):
prev_k1: str
prev_k2: str
otp: str
- webhook_url: str
time: int
def from_row(cls, row: Row) -> "Card":
@@ -57,7 +56,6 @@ class CreateCardData(BaseModel):
prev_k0: str = Query(ZERO_KEY)
prev_k1: str = Query(ZERO_KEY)
prev_k2: str = Query(ZERO_KEY)
- webhook_url: str = Query(...)
class Hit(BaseModel):
diff --git a/lnbits/extensions/boltcards/static/js/index.js b/lnbits/extensions/boltcards/static/js/index.js
index 1f32ad44..880a555e 100644
--- a/lnbits/extensions/boltcards/static/js/index.js
+++ b/lnbits/extensions/boltcards/static/js/index.js
@@ -23,7 +23,6 @@ new Vue({
cardDialog: {
show: false,
data: {
- webhook_url: '',
counter: 1,
k0: '',
k1: '',
@@ -273,8 +272,7 @@ new Vue({
k1: card.k1,
k2: card.k2,
k3: card.k1,
- k4: card.k2,
- webhook_url: card.webhook_url
+ k4: card.k2
}
this.qrCodeDialog.data_wipe = JSON.stringify({
action: 'wipe',
diff --git a/lnbits/extensions/boltcards/tasks.py b/lnbits/extensions/boltcards/tasks.py
index c5a9ac9c..7d5d7407 100644
--- a/lnbits/extensions/boltcards/tasks.py
+++ b/lnbits/extensions/boltcards/tasks.py
@@ -9,7 +9,7 @@ from lnbits.core.models import Payment
from lnbits.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener
-from .crud import create_refund, get_card, get_hit
+from .crud import create_refund, get_hit
async def wait_for_paid_invoices():
@@ -36,25 +36,6 @@ async def on_invoice_paid(payment: Payment) -> None:
)
await mark_webhook_sent(payment, 1)
- card = await get_card(hit.card_id)
- if card.webhook_url:
- async with httpx.AsyncClient() as client:
- try:
- r = await client.post(
- card.webhook_url,
- json={
- "notification": "card_refund",
- "payment_hash": payment.payment_hash,
- "payment_request": payment.bolt11,
- "card_external_id": card.external_id,
- "card_name": card.card_name,
- "amount": int(payment.amount / 1000),
- },
- timeout=40,
- )
- except Exception as exc:
- logger.error("Caught exception when dispatching webhook url:", exc)
-
async def mark_webhook_sent(payment: Payment, status: int) -> None:
payment.extra["wh_status"] = status
diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html
index aefb22c6..61aa6fec 100644
--- a/lnbits/extensions/boltcards/templates/boltcards/index.html
+++ b/lnbits/extensions/boltcards/templates/boltcards/index.html
@@ -324,14 +324,6 @@
>Zero if you don't know.
-
-
Lock key: {{ qrCodeDialog.data.k0 }}
Meta key: {{ qrCodeDialog.data.k1 }}
File key: {{ qrCodeDialog.data.k2 }}
- Notification webhook: {{ qrCodeDialog.data.webhook_url
- }}
Always backup all keys that you're trying to write on the card. Without
diff --git a/lnbits/extensions/boltcards/views_api.py b/lnbits/extensions/boltcards/views_api.py
index c2d5bff3..80679556 100644
--- a/lnbits/extensions/boltcards/views_api.py
+++ b/lnbits/extensions/boltcards/views_api.py
@@ -113,22 +113,6 @@ async def enable_card(
return card.dict()
-@boltcards_ext.post("/api/v1/disablecard")
-async def disble_card_with_otp(a):
- if len(a) < 16:
- raise HTTPException(detail="Invalid OTP.", status_code=HTTPStatus.BAD_REQUEST)
- card = await get_card_by_otp(a, half=True)
- if not card:
- raise HTTPException(detail="No card found.", status_code=HTTPStatus.NOT_FOUND)
-
- new_otp = secrets.token_hex(16)
- await update_card_otp(new_otp, card.id)
-
- card = await enable_disable_card(enable=False, id=card.id)
-
- return {"status": "OK"}
-
-
@boltcards_ext.delete("/api/v1/cards/{card_id}")
async def api_card_delete(card_id, wallet: WalletTypeInfo = Depends(require_admin_key)):
card = await get_card(card_id)
diff --git a/lnbits/extensions/withdraw/lnurl.py b/lnbits/extensions/withdraw/lnurl.py
index 2ae47f6b..5737e54f 100644
--- a/lnbits/extensions/withdraw/lnurl.py
+++ b/lnbits/extensions/withdraw/lnurl.py
@@ -3,6 +3,7 @@ import traceback
from datetime import datetime
from http import HTTPStatus
+import httpx
import shortuuid # type: ignore
from fastapi import HTTPException
from fastapi.param_functions import Query
@@ -132,24 +133,29 @@ async def api_lnurl_callback(
payment_request = pr
- webhook = (
- (
- link.webhook_url,
- {
- "lnurlw": link.id,
- },
- )
- if link.webhook_url
- else None
- )
- await pay_invoice(
+ payment_hash = await pay_invoice(
wallet_id=link.wallet,
payment_request=payment_request,
max_sat=link.max_withdrawable,
extra={"tag": "withdraw"},
- webhook=webhook,
)
+ if link.webhook_url:
+ async with httpx.AsyncClient() as client:
+ try:
+ r = await client.post(
+ link.webhook_url,
+ json={
+ "payment_hash": payment_hash,
+ "payment_request": payment_request,
+ "lnurlw": link.id,
+ },
+ timeout=40,
+ )
+ except Exception as exc:
+ # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid
+ logger.error("Caught exception when dispatching webhook url:", exc)
+
return {"status": "OK"}
except Exception as e:
From a916c1ac7332094ba5bf3136363f626c5dfa7ddd Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Fri, 16 Dec 2022 11:17:59 +0100
Subject: [PATCH 37/40] reducing the PR
---
lnbits/extensions/boltcards/crud.py | 10 ++--------
lnbits/extensions/boltcards/lnurl.py | 3 ---
2 files changed, 2 insertions(+), 11 deletions(-)
diff --git a/lnbits/extensions/boltcards/crud.py b/lnbits/extensions/boltcards/crud.py
index f15deb69..c541346e 100644
--- a/lnbits/extensions/boltcards/crud.py
+++ b/lnbits/extensions/boltcards/crud.py
@@ -112,14 +112,8 @@ async def get_card_by_external_id(external_id: str) -> Optional[Card]:
return Card.parse_obj(card)
-async def get_card_by_otp(otp: str, half: bool = False) -> Optional[Card]:
- if half and len(otp) == 16:
- otp = "%" + otp
- row = await db.fetchone(
- "SELECT * FROM boltcards.cards WHERE otp LIKE ?", (otp,)
- )
- else:
- row = await db.fetchone("SELECT * FROM boltcards.cards WHERE otp = ?", (otp,))
+async def get_card_by_otp(otp: str) -> Optional[Card]:
+ row = await db.fetchone("SELECT * FROM boltcards.cards WHERE otp = ?", (otp,))
if not row:
return None
diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py
index 7588f56e..8128cb9e 100644
--- a/lnbits/extensions/boltcards/lnurl.py
+++ b/lnbits/extensions/boltcards/lnurl.py
@@ -3,13 +3,11 @@ import secrets
from http import HTTPStatus
from urllib.parse import urlparse
-from embit import bech32, compact
from fastapi import Request
from fastapi.param_functions import Query
from fastapi.params import Depends, Query
from lnurl import encode as lnurl_encode # type: ignore
from lnurl.types import LnurlPayMetadata # type: ignore
-from loguru import logger
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import HTMLResponse
@@ -118,7 +116,6 @@ async def lnurl_callback(
max_sat=card.tx_limit,
extra={"tag": "boltcard", "tag": hit.id},
)
-
return {"status": "OK"}
except Exception as exc:
return {"status": "ERROR", "reason": f"Payment failed - {exc}"}
From 75561bd93005aee6bf0393700992748920e14444 Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Fri, 16 Dec 2022 11:51:37 +0100
Subject: [PATCH 38/40] some minors
---
lnbits/extensions/boltcards/README.md | 2 +-
lnbits/extensions/boltcards/templates/boltcards/index.html | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md
index 3a00e265..34a1c05c 100644
--- a/lnbits/extensions/boltcards/README.md
+++ b/lnbits/extensions/boltcards/README.md
@@ -17,7 +17,7 @@ Up to five 16-byte keys can be stored on the card, numbered from 00 to 04. In th
- One for calculating CMAC (c parameter), let's called it file key, key #02 or K2.
-The key #00, K0 (also know as auth key) is skipped to be used as authentification key. It is not needed by this extension, but should be filled in order to write the keys in cooperation with Boltcard NFC Card Creator. In this case also K3 is set to same value as K1 and K4 as K2, so all keys are changed from default values. Keep that in your mind in case you need to reset the keys manually.
+The key #00, K0 (also know as auth key) is used as authentification key. It is not directly needed by this extension, but should be filled in order to write the keys in cooperation with Boltcard NFC Card Creator. In this case also K3 is set to same value as K1 and K4 as K2, so all keys are changed from default values. Keep that in your mind in case you ever need to reset the keys manually.
***Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!***
diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html
index 61aa6fec..f401112b 100644
--- a/lnbits/extensions/boltcards/templates/boltcards/index.html
+++ b/lnbits/extensions/boltcards/templates/boltcards/index.html
@@ -420,9 +420,9 @@
Name: {{ qrCodeDialog.data.name }}
UID: {{ qrCodeDialog.data.uid }}
External ID: {{ qrCodeDialog.data.external_id }}
- Lock key: {{ qrCodeDialog.data.k0 }}
- Meta key: {{ qrCodeDialog.data.k1 }}
- File key: {{ qrCodeDialog.data.k2 }}
+ Lock key (K0): {{ qrCodeDialog.data.k0 }}
+ Meta key (K1 & K3): {{ qrCodeDialog.data.k1 }}
+ File key (K2 & K4): {{ qrCodeDialog.data.k2 }}
Always backup all keys that you're trying to write on the card. Without
From 7b7a731021b34be91af512c7fc685ffe40bef25d Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Fri, 16 Dec 2022 11:55:47 +0100
Subject: [PATCH 39/40] reducing the PR
---
lnbits/extensions/boltcards/tasks.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/lnbits/extensions/boltcards/tasks.py b/lnbits/extensions/boltcards/tasks.py
index 7d5d7407..c1e99b76 100644
--- a/lnbits/extensions/boltcards/tasks.py
+++ b/lnbits/extensions/boltcards/tasks.py
@@ -2,7 +2,6 @@ import asyncio
import json
import httpx
-from loguru import logger
from lnbits.core import db as core_db
from lnbits.core.models import Payment
From ffe0a77106287bb874fc95d7d76ef980f1554a85 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Sat, 17 Dec 2022 09:49:00 +0100
Subject: [PATCH 40/40] fix admin ui only have superuser to topup
---
lnbits/core/views/admin_api.py | 64 +++++++++++++++++-----------------
1 file changed, 32 insertions(+), 32 deletions(-)
diff --git a/lnbits/core/views/admin_api.py b/lnbits/core/views/admin_api.py
index 2ceaa4e6..7c057adc 100644
--- a/lnbits/core/views/admin_api.py
+++ b/lnbits/core/views/admin_api.py
@@ -15,16 +15,6 @@ from .. import core_app
from ..crud import delete_admin_settings, get_admin_settings, update_admin_settings
-@core_app.get(
- "/admin/api/v1/restart/",
- status_code=HTTPStatus.OK,
- dependencies=[Depends(check_super_user)],
-)
-async def api_restart_server() -> dict[str, str]:
- server_restart.set()
- return {"status": "Success"}
-
-
@core_app.get("/admin/api/v1/settings/")
async def api_get_settings(
user: User = Depends(check_admin), # type: ignore
@@ -33,26 +23,6 @@ async def api_get_settings(
return admin_settings
-@core_app.put(
- "/admin/api/v1/topup/",
- status_code=HTTPStatus.OK,
- dependencies=[Depends(check_admin)],
-)
-async def api_topup_balance(
- id: str = Body(...), amount: int = Body(...)
-) -> dict[str, str]:
- try:
- await get_wallet(id)
- except:
- raise HTTPException(
- status_code=HTTPStatus.FORBIDDEN, detail="wallet does not exist."
- )
-
- await update_wallet_balance(wallet_id=id, amount=int(amount))
-
- return {"status": "Success"}
-
-
@core_app.put(
"/admin/api/v1/settings/",
status_code=HTTPStatus.OK,
@@ -67,8 +37,38 @@ async def api_update_settings(data: EditableSetings):
@core_app.delete(
"/admin/api/v1/settings/",
status_code=HTTPStatus.OK,
- dependencies=[Depends(check_admin)],
+ dependencies=[Depends(check_super_user)],
)
-async def api_delete_settings() -> dict[str, str]:
+async def api_delete_settings() -> None:
await delete_admin_settings()
+ server_restart.set()
+
+
+@core_app.get(
+ "/admin/api/v1/restart/",
+ status_code=HTTPStatus.OK,
+ dependencies=[Depends(check_super_user)],
+)
+async def api_restart_server() -> dict[str, str]:
+ server_restart.set()
+ return {"status": "Success"}
+
+
+@core_app.put(
+ "/admin/api/v1/topup/",
+ status_code=HTTPStatus.OK,
+ dependencies=[Depends(check_super_user)],
+)
+async def api_topup_balance(
+ id: str = Body(...), amount: int = Body(...)
+) -> dict[str, str]:
+ try:
+ await get_wallet(id)
+ except:
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN, detail="wallet does not exist."
+ )
+
+ await update_wallet_balance(wallet_id=id, amount=int(amount))
+
return {"status": "Success"}