From b9986b562a5f114483419097b07a5906a14fdf5b Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 23 Nov 2022 17:04:08 +0200 Subject: [PATCH 01/21] fix: oldObj values lost --- lnbits/extensions/satspay/static/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/satspay/static/js/utils.js b/lnbits/extensions/satspay/static/js/utils.js index 9b4abbfca..5ce21a5d1 100644 --- a/lnbits/extensions/satspay/static/js/utils.js +++ b/lnbits/extensions/satspay/static/js/utils.js @@ -14,7 +14,7 @@ const retryWithDelay = async function (fn, retryCount = 0) { } const mapCharge = (obj, oldObj = {}) => { - const charge = _.clone(obj) + const charge = {...obj, ...oldObj} charge.progress = obj.time_left < 0 ? 1 : 1 - obj.time_left / obj.time charge.time = minutesToTime(obj.time) From 69c0b753f5096bd2b1b55125eab85f38ae8a4422 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 23 Nov 2022 17:32:34 +0200 Subject: [PATCH 02/21] fix: remove de-selected wallet --- lnbits/extensions/satspay/templates/satspay/index.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/satspay/templates/satspay/index.html b/lnbits/extensions/satspay/templates/satspay/index.html index 396200cf1..af0dacc01 100644 --- a/lnbits/extensions/satspay/templates/satspay/index.html +++ b/lnbits/extensions/satspay/templates/satspay/index.html @@ -409,7 +409,7 @@ balance: null, walletLinks: [], chargeLinks: [], - onchainwallet: '', + onchainwallet: null, rescanning: false, mempool: { endpoint: '' @@ -505,6 +505,7 @@ methods: { cancelCharge: function (data) { this.formDialogCharge.data.description = '' + this.formDialogCharge.data.onchain = false this.formDialogCharge.data.onchainwallet = '' this.formDialogCharge.data.lnbitswallet = '' this.formDialogCharge.data.time = null @@ -577,7 +578,8 @@ const data = this.formDialogCharge.data data.amount = parseInt(data.amount) data.time = parseInt(data.time) - data.onchainwallet = this.onchainwallet?.id + data.lnbitswallet = data.lnbits ? this.lnbitswallet : null + data.onchainwallet = data.onchain ? this.onchainwallet?.id : null this.createCharge(wallet, data) }, refreshActiveChargesBalance: async function () { From a8d4835b74aec1485f7f2c5d93838c6e5be9b64c Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 24 Nov 2022 10:42:28 +0200 Subject: [PATCH 03/21] fix: use `lnbitswallet` form data --- lnbits/extensions/satspay/templates/satspay/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/satspay/templates/satspay/index.html b/lnbits/extensions/satspay/templates/satspay/index.html index af0dacc01..a1ca2de7b 100644 --- a/lnbits/extensions/satspay/templates/satspay/index.html +++ b/lnbits/extensions/satspay/templates/satspay/index.html @@ -578,7 +578,7 @@ const data = this.formDialogCharge.data data.amount = parseInt(data.amount) data.time = parseInt(data.time) - data.lnbitswallet = data.lnbits ? this.lnbitswallet : null + data.lnbitswallet = data.lnbits ? data.lnbitswallet : null data.onchainwallet = data.onchain ? this.onchainwallet?.id : null this.createCharge(wallet, data) }, From 4b4f18fad588d444f61c7ef1a5d130ef566400d3 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 24 Nov 2022 11:39:35 +0200 Subject: [PATCH 04/21] fix: oldObj should not overwrite properties --- lnbits/extensions/satspay/static/js/utils.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/satspay/static/js/utils.js b/lnbits/extensions/satspay/static/js/utils.js index 5ce21a5d1..929279554 100644 --- a/lnbits/extensions/satspay/static/js/utils.js +++ b/lnbits/extensions/satspay/static/js/utils.js @@ -14,15 +14,14 @@ const retryWithDelay = async function (fn, retryCount = 0) { } const mapCharge = (obj, oldObj = {}) => { - const charge = {...obj, ...oldObj} + const charge = {...oldObj, ...obj} charge.progress = obj.time_left < 0 ? 1 : 1 - obj.time_left / obj.time charge.time = minutesToTime(obj.time) charge.timeLeft = minutesToTime(obj.time_left) - charge.expanded = false charge.displayUrl = ['/satspay/', obj.id].join('') - charge.expanded = oldObj.expanded + charge.expanded = oldObj.expanded || false charge.pendingBalance = oldObj.pendingBalance || 0 return charge } From a641ef48362805ca2e9415f7a54638a4e61cd93d Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 24 Nov 2022 16:55:35 +0200 Subject: [PATCH 05/21] refactor: remove duplicate code --- lnbits/extensions/satspay/crud.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py index 23d391b7b..512bcaa3b 100644 --- a/lnbits/extensions/satspay/crud.py +++ b/lnbits/extensions/satspay/crud.py @@ -98,7 +98,7 @@ async def delete_charge(charge_id: str) -> None: await db.execute("DELETE FROM satspay.charges WHERE id = ?", (charge_id,)) -async def check_address_balance(charge_id: str) -> List[Charges]: +async def check_address_balance(charge_id: str) -> Optional[Charges]: charge = await get_charge(charge_id) if not charge.paid: if charge.onchainaddress: @@ -120,8 +120,7 @@ async def check_address_balance(charge_id: str) -> List[Charges]: if invoice_status["paid"]: return await update_charge(charge_id=charge_id, balance=charge.amount) - row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,)) - return Charges.from_row(row) if row else None + return await get_charge(charge_id) async def get_charge_config(charge_id: str): From fdfd9b1d9f1bb47071de99cf4ca6f9d10482e4c9 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 24 Nov 2022 16:57:59 +0200 Subject: [PATCH 06/21] fix: call webhook in the background (from the `task.py`) --- lnbits/extensions/satspay/tasks.py | 24 ++++++++++++++++++++++-- lnbits/extensions/satspay/views_api.py | 11 ----------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/lnbits/extensions/satspay/tasks.py b/lnbits/extensions/satspay/tasks.py index 46c16bbc9..2cac64311 100644 --- a/lnbits/extensions/satspay/tasks.py +++ b/lnbits/extensions/satspay/tasks.py @@ -1,5 +1,6 @@ import asyncio +import httpx from loguru import logger from lnbits.core.models import Payment @@ -7,7 +8,8 @@ from lnbits.extensions.satspay.crud import check_address_balance, get_charge from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener -# from .crud import get_ticket, set_ticket_paid +from .helpers import compact_charge +from .models import Charges async def wait_for_paid_invoices(): @@ -30,4 +32,22 @@ async def on_invoice_paid(payment: Payment) -> None: return await payment.set_pending(False) - await check_address_balance(charge_id=charge.id) + charge = await check_address_balance(charge_id=charge.id) + + if charge.paid and charge.webhook: + await call_webhook(charge) + + +async def call_webhook(charge: Charges): + async with httpx.AsyncClient() as client: + try: + r = await client.post( + charge.webhook, + json=compact_charge(charge), + timeout=40, + ) + except AssertionError: + charge.webhook = None + except Exception as e: + logger.warning(f"Failed to call webhook for charge {charge.id}") + logger.warning(e) diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index e1b87c41f..e516219e4 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -1,6 +1,5 @@ from http import HTTPStatus -import httpx from fastapi.params import Depends from starlette.exceptions import HTTPException @@ -119,16 +118,6 @@ async def api_charge_balance(charge_id): status_code=HTTPStatus.NOT_FOUND, detail="Charge does not exist." ) - if charge.paid and charge.webhook: - async with httpx.AsyncClient() as client: - try: - r = await client.post( - charge.webhook, - json=compact_charge(charge), - timeout=40, - ) - except AssertionError: - charge.webhook = None return { **compact_charge(charge), **{"time_elapsed": charge.time_elapsed}, From 00ba09d2017dda2047a49ede9137ecde9e81bd22 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 24 Nov 2022 17:08:32 +0200 Subject: [PATCH 07/21] fix: remove wallet `inkey` --- .../satspay/templates/satspay/display.html | 14 -------------- lnbits/extensions/satspay/views.py | 1 - 2 files changed, 15 deletions(-) diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html index 12288c809..5d0ebe40c 100644 --- a/lnbits/extensions/satspay/templates/satspay/display.html +++ b/lnbits/extensions/satspay/templates/satspay/display.html @@ -317,16 +317,6 @@ } }, methods: { - startPaymentNotifier() { - this.cancelListener() - if (!this.lnbitswallet) return - this.cancelListener = LNbits.events.onInvoicePaid( - this.wallet, - payment => { - this.checkInvoiceBalance() - } - ) - }, checkBalances: async function () { if (this.charge.hasStaleBalance) return try { @@ -432,10 +422,6 @@ else this.payOnchain() await this.checkBalances() - // empty for onchain - this.wallet.inkey = '{{ wallet_inkey }}' - this.startPaymentNotifier() - if (!this.charge.paid) { this.loopRefresh() } diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index b789bf8fe..d98a93d9d 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -41,7 +41,6 @@ async def display(request: Request, charge_id: str): { "request": request, "charge_data": charge.dict(), - "wallet_inkey": inkey, "mempool_endpoint": mempool_endpoint, }, ) From d3c3622d22191c0d14cec42e77949af8d9fe33f9 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 24 Nov 2022 17:20:12 +0200 Subject: [PATCH 08/21] fix: mempool hostname --- .../satspay/templates/satspay/display.html | 20 ++++++++++++++----- lnbits/extensions/satspay/views.py | 10 +++++----- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html index 5d0ebe40c..b69cc8295 100644 --- a/lnbits/extensions/satspay/templates/satspay/display.html +++ b/lnbits/extensions/satspay/templates/satspay/display.html @@ -218,7 +218,7 @@
{} } }, + computed: { + mempoolHostname: function () { + let hostname = new URL(this.mempoolEndpoint).hostname + if (this.network === 'Testnet') { + hostname += '/testnet' + } + return hostname + } + }, methods: { checkBalances: async function () { if (this.charge.hasStaleBalance) return @@ -335,7 +345,7 @@ const { bitcoin: {addresses: addressesAPI} } = mempoolJS({ - hostname: new URL(this.mempool_endpoint).hostname + hostname: new URL(this.mempoolEndpoint).hostname }) try { @@ -378,10 +388,10 @@ const { bitcoin: {websocket} } = mempoolJS({ - hostname: new URL(this.mempool_endpoint).hostname + hostname: new URL(this.mempoolEndpoint).hostname }) - this.ws = new WebSocket('wss://mempool.space/api/v1/ws') + this.ws = new WebSocket(`wss://${this.mempoolHostname}/api/v1/ws`) this.ws.addEventListener('open', x => { if (this.charge.onchainaddress) { this.trackAddress(this.charge.onchainaddress) diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index d98a93d9d..5029f1682 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -30,17 +30,17 @@ async def display(request: Request, charge_id: str): raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist." ) - wallet = await get_wallet(charge.lnbitswallet) + onchainwallet_config = await get_charge_config(charge_id) - inkey = wallet.inkey if wallet else None - mempool_endpoint = ( - onchainwallet_config.mempool_endpoint if onchainwallet_config else None - ) + if onchainwallet_config: + mempool_endpoint = onchainwallet_config.mempool_endpoint + network = onchainwallet_config.network return satspay_renderer().TemplateResponse( "satspay/display.html", { "request": request, "charge_data": charge.dict(), "mempool_endpoint": mempool_endpoint, + "network": network, }, ) From 3356bab5ca33f8c8c10c2c9ab8307d3c3e890b25 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 24 Nov 2022 17:33:58 +0200 Subject: [PATCH 09/21] fix: refresh when both onchain and ln are present --- lnbits/extensions/satspay/templates/satspay/display.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html index b69cc8295..57b0e7c6f 100644 --- a/lnbits/extensions/satspay/templates/satspay/display.html +++ b/lnbits/extensions/satspay/templates/satspay/display.html @@ -328,7 +328,8 @@ }, methods: { checkBalances: async function () { - if (this.charge.hasStaleBalance) return + if (!this.charge.lnbitswallet && this.charge.hasOnchainStaleBalance) + return try { const {data} = await LNbits.api.request( 'GET', @@ -353,7 +354,7 @@ address: this.charge.onchainaddress }) const newBalance = utxos.reduce((t, u) => t + u.value, 0) - this.charge.hasStaleBalance = this.charge.balance === newBalance + this.charge.hasOnchainStaleBalance = this.charge.balance === newBalance this.pendingFunds = utxos .filter(u => !u.status.confirmed) @@ -430,6 +431,7 @@ created: async function () { if (this.charge.lnbitswallet) this.payInvoice() else this.payOnchain() + await this.checkBalances() if (!this.charge.paid) { From 4e984eae56870b1e9a20814e6787a90ee410907d Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 24 Nov 2022 17:39:40 +0200 Subject: [PATCH 10/21] fix: center back button --- .../satspay/templates/satspay/display.html | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html index 57b0e7c6f..cfc702b14 100644 --- a/lnbits/extensions/satspay/templates/satspay/display.html +++ b/lnbits/extensions/satspay/templates/satspay/display.html @@ -170,13 +170,17 @@ name="check" style="color: green; font-size: 21.4em" > - +
+
+ +
+
@@ -241,13 +245,17 @@ name="check" style="color: green; font-size: 21.4em" > - +
+
+ +
+
@@ -354,7 +362,8 @@ address: this.charge.onchainaddress }) const newBalance = utxos.reduce((t, u) => t + u.value, 0) - this.charge.hasOnchainStaleBalance = this.charge.balance === newBalance + this.charge.hasOnchainStaleBalance = + this.charge.balance === newBalance this.pendingFunds = utxos .filter(u => !u.status.confirmed) From 5440d4ab19251405153bbb6cc996f5ac0bd3a192 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 25 Nov 2022 10:48:57 +0200 Subject: [PATCH 11/21] feat: store onchain charge config with the charge --- lnbits/extensions/satspay/crud.py | 34 +++++++++++-------- lnbits/extensions/satspay/helpers.py | 13 +++++-- lnbits/extensions/satspay/migrations.py | 11 ++++++ lnbits/extensions/satspay/models.py | 7 ++++ lnbits/extensions/satspay/tasks.py | 4 +-- .../satspay/templates/satspay/display.html | 10 +++--- .../satspay/templates/satspay/index.html | 9 +++-- lnbits/extensions/satspay/views.py | 23 ++++++------- lnbits/extensions/satspay/views_api.py | 9 ++--- 9 files changed, 73 insertions(+), 47 deletions(-) diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py index 512bcaa3b..6c0c1cb3a 100644 --- a/lnbits/extensions/satspay/crud.py +++ b/lnbits/extensions/satspay/crud.py @@ -1,6 +1,8 @@ +import json from typing import List, Optional import httpx +from loguru import logger from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment @@ -18,6 +20,10 @@ from .models import Charges, CreateCharge async def create_charge(user: str, data: CreateCharge) -> Charges: charge_id = urlsafe_short_hash() if data.onchainwallet: + config = await get_config(user) + data.extra = json.dumps( + {"mempool_endpoint": config.mempool_endpoint, "network": config.network} + ) onchain = await get_fresh_address(data.onchainwallet) onchainaddress = onchain.address else: @@ -48,9 +54,10 @@ async def create_charge(user: str, data: CreateCharge) -> Charges: completelinktext, time, amount, - balance + balance, + extra ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( charge_id, @@ -67,6 +74,7 @@ async def create_charge(user: str, data: CreateCharge) -> Charges: data.time, data.amount, 0, + data.extra, ), ) return await get_charge(charge_id) @@ -100,31 +108,27 @@ async def delete_charge(charge_id: str) -> None: async def check_address_balance(charge_id: str) -> Optional[Charges]: charge = await get_charge(charge_id) + if not charge.paid: if charge.onchainaddress: - config = await get_charge_config(charge_id) + endpoint = ( + f"{charge.config['mempool_endpoint']}/testnet" + if charge.config["network"] == "Testnet" + else charge.config["mempool_endpoint"] + ) try: async with httpx.AsyncClient() as client: r = await client.get( - config.mempool_endpoint - + "/api/address/" - + charge.onchainaddress + endpoint + "/api/address/" + charge.onchainaddress ) respAmount = r.json()["chain_stats"]["funded_txo_sum"] if respAmount > charge.balance: await update_charge(charge_id=charge_id, balance=respAmount) - except Exception: - pass + except Exception as e: + logger.warning(e) if charge.lnbitswallet: invoice_status = await api_payment(charge.payment_hash) if invoice_status["paid"]: return await update_charge(charge_id=charge_id, balance=charge.amount) return await get_charge(charge_id) - - -async def get_charge_config(charge_id: str): - row = await db.fetchone( - """SELECT "user" FROM satspay.charges WHERE id = ?""", (charge_id,) - ) - return await get_config(row.user) diff --git a/lnbits/extensions/satspay/helpers.py b/lnbits/extensions/satspay/helpers.py index 2d15b5578..1ebaa062c 100644 --- a/lnbits/extensions/satspay/helpers.py +++ b/lnbits/extensions/satspay/helpers.py @@ -1,8 +1,8 @@ from .models import Charges -def compact_charge(charge: Charges): - return { +def public_charge(charge: Charges): + c = { "id": charge.id, "description": charge.description, "onchainaddress": charge.onchainaddress, @@ -13,5 +13,12 @@ def compact_charge(charge: Charges): "balance": charge.balance, "paid": charge.paid, "timestamp": charge.timestamp, - "completelink": charge.completelink, # should be secret? + "time_elapsed": charge.time_elapsed, + "time_left": charge.time_left, + "paid": charge.paid, } + + if charge.paid: + c["completelink"] = charge.completelink + + return c diff --git a/lnbits/extensions/satspay/migrations.py b/lnbits/extensions/satspay/migrations.py index 87446c800..d5f6ba13a 100644 --- a/lnbits/extensions/satspay/migrations.py +++ b/lnbits/extensions/satspay/migrations.py @@ -26,3 +26,14 @@ async def m001_initial(db): ); """ ) + + +async def m002_add_charge_extra_data(db): + """ + Add 'exta' for storing various config about the charge + """ + await db.execute( + """ALTER TABLE satspay.charges + ADD COLUMN extra TEXT DEFAULT '{"mempool_endpoint": "https://mempool.space", "network": "Mainnet"}'; + """ + ) diff --git a/lnbits/extensions/satspay/models.py b/lnbits/extensions/satspay/models.py index daf63f429..8d2602e15 100644 --- a/lnbits/extensions/satspay/models.py +++ b/lnbits/extensions/satspay/models.py @@ -1,3 +1,4 @@ +import json from datetime import datetime, timedelta from sqlite3 import Row from typing import Optional @@ -15,6 +16,7 @@ class CreateCharge(BaseModel): completelinktext: str = Query(None) time: int = Query(..., ge=1) amount: int = Query(..., ge=1) + extra: str = "{}" class Charges(BaseModel): @@ -28,6 +30,7 @@ class Charges(BaseModel): webhook: Optional[str] completelink: Optional[str] completelinktext: Optional[str] = "Back to Merchant" + extra: str = "{}" time: int amount: int balance: int @@ -54,3 +57,7 @@ class Charges(BaseModel): return True else: return False + + @property + def config(self): + return json.loads(self.extra) diff --git a/lnbits/extensions/satspay/tasks.py b/lnbits/extensions/satspay/tasks.py index 2cac64311..3a77501b2 100644 --- a/lnbits/extensions/satspay/tasks.py +++ b/lnbits/extensions/satspay/tasks.py @@ -8,7 +8,7 @@ from lnbits.extensions.satspay.crud import check_address_balance, get_charge from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener -from .helpers import compact_charge +from .helpers import public_charge from .models import Charges @@ -43,7 +43,7 @@ async def call_webhook(charge: Charges): try: r = await client.post( charge.webhook, - json=compact_charge(charge), + json=public_charge(charge), timeout=40, ) except AssertionError: diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html index cfc702b14..a24ed84c7 100644 --- a/lnbits/extensions/satspay/templates/satspay/display.html +++ b/lnbits/extensions/satspay/templates/satspay/display.html @@ -109,7 +109,7 @@ @@ -131,7 +131,7 @@ @@ -222,7 +222,7 @@
({ @@ -538,7 +539,9 @@ '/watchonly/api/v1/config', this.g.user.wallets[0].inkey ) + console.log('### data', data) this.mempool.endpoint = data.mempool_endpoint + this.mempool.network = data.network || 'Mainnet' const url = new URL(this.mempool.endpoint) this.mempool.hostname = url.hostname } catch (error) { @@ -697,8 +700,8 @@ }, created: async function () { await this.getCharges() - await this.getWalletLinks() await this.getWalletConfig() + await this.getWalletLinks() setInterval(() => this.refreshActiveChargesBalance(), 10 * 2000) await this.rescanOnchainAddresses() setInterval(() => this.rescanOnchainAddresses(), 10 * 1000) diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index 5029f1682..5faebe7a9 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -6,12 +6,12 @@ from starlette.exceptions import HTTPException from starlette.requests import Request from starlette.responses import HTMLResponse -from lnbits.core.crud import get_wallet from lnbits.core.models import User from lnbits.decorators import check_user_exists +from lnbits.extensions.satspay.helpers import public_charge from . import satspay_ext, satspay_renderer -from .crud import get_charge, get_charge_config +from .crud import get_charge templates = Jinja2Templates(directory="templates") @@ -31,16 +31,15 @@ async def display(request: Request, charge_id: str): status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist." ) - onchainwallet_config = await get_charge_config(charge_id) - if onchainwallet_config: - mempool_endpoint = onchainwallet_config.mempool_endpoint - network = onchainwallet_config.network + view_data = { + "request": request, + "charge_data": public_charge(charge), + } + if "mempool_endpoint" in charge.config: + view_data["mempool_endpoint"] = charge.config["mempool_endpoint"] + view_data["network"] = charge.config["network"] + return satspay_renderer().TemplateResponse( "satspay/display.html", - { - "request": request, - "charge_data": charge.dict(), - "mempool_endpoint": mempool_endpoint, - "network": network, - }, + view_data, ) diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index e516219e4..73581022f 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -19,7 +19,7 @@ from .crud import ( get_charges, update_charge, ) -from .helpers import compact_charge +from .helpers import public_charge from .models import CreateCharge #############################CHARGES########################## @@ -118,9 +118,4 @@ async def api_charge_balance(charge_id): status_code=HTTPStatus.NOT_FOUND, detail="Charge does not exist." ) - return { - **compact_charge(charge), - **{"time_elapsed": charge.time_elapsed}, - **{"time_left": charge.time_left}, - **{"paid": charge.paid}, - } + return {**public_charge(charge)} From 6f5c9a779885f4442203b8f05cd31e0094737587 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 25 Nov 2022 11:06:45 +0200 Subject: [PATCH 12/21] chore: code clean-up --- lnbits/extensions/satspay/templates/satspay/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/lnbits/extensions/satspay/templates/satspay/index.html b/lnbits/extensions/satspay/templates/satspay/index.html index d0cfc6775..3543dee35 100644 --- a/lnbits/extensions/satspay/templates/satspay/index.html +++ b/lnbits/extensions/satspay/templates/satspay/index.html @@ -539,7 +539,6 @@ '/watchonly/api/v1/config', this.g.user.wallets[0].inkey ) - console.log('### data', data) this.mempool.endpoint = data.mempool_endpoint this.mempool.network = data.network || 'Mainnet' const url = new URL(this.mempool.endpoint) From 1aecedac84ea6139dbab84f2599dcdbb1c2217d6 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 25 Nov 2022 11:25:40 +0200 Subject: [PATCH 13/21] refactor: move business logic logic to `helpers.py` --- lnbits/extensions/satspay/crud.py | 19 ++++-------------- lnbits/extensions/satspay/helpers.py | 29 ++++++++++++++++++++++++++++ lnbits/extensions/satspay/tasks.py | 19 +----------------- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py index 6c0c1cb3a..968c9ab01 100644 --- a/lnbits/extensions/satspay/crud.py +++ b/lnbits/extensions/satspay/crud.py @@ -1,7 +1,6 @@ import json from typing import List, Optional -import httpx from loguru import logger from lnbits.core.services import create_invoice @@ -9,9 +8,8 @@ from lnbits.core.views.api import api_payment from lnbits.helpers import urlsafe_short_hash from ..watchonly.crud import get_config, get_fresh_address - -# from lnbits.db import open_ext_db from . import db +from .helpers import fetch_onchain_balance from .models import Charges, CreateCharge ###############CHARGES########################## @@ -111,19 +109,10 @@ async def check_address_balance(charge_id: str) -> Optional[Charges]: if not charge.paid: if charge.onchainaddress: - endpoint = ( - f"{charge.config['mempool_endpoint']}/testnet" - if charge.config["network"] == "Testnet" - else charge.config["mempool_endpoint"] - ) try: - async with httpx.AsyncClient() as client: - r = await client.get( - endpoint + "/api/address/" + charge.onchainaddress - ) - respAmount = r.json()["chain_stats"]["funded_txo_sum"] - if respAmount > charge.balance: - await update_charge(charge_id=charge_id, balance=respAmount) + respAmount = await fetch_onchain_balance(charge) + if respAmount > charge.balance: + await update_charge(charge_id=charge_id, balance=respAmount) except Exception as e: logger.warning(e) if charge.lnbitswallet: diff --git a/lnbits/extensions/satspay/helpers.py b/lnbits/extensions/satspay/helpers.py index 1ebaa062c..6862c6089 100644 --- a/lnbits/extensions/satspay/helpers.py +++ b/lnbits/extensions/satspay/helpers.py @@ -1,3 +1,6 @@ +import httpx +from loguru import logger + from .models import Charges @@ -22,3 +25,29 @@ def public_charge(charge: Charges): c["completelink"] = charge.completelink return c + + +async def call_webhook(charge: Charges): + async with httpx.AsyncClient() as client: + try: + r = await client.post( + charge.webhook, + json=public_charge(charge), + timeout=40, + ) + except AssertionError: + charge.webhook = None + except Exception as e: + logger.warning(f"Failed to call webhook for charge {charge.id}") + logger.warning(e) + + +async def fetch_onchain_balance(charge: Charges): + endpoint = ( + f"{charge.config['mempool_endpoint']}/testnet" + if charge.config["network"] == "Testnet" + else charge.config["mempool_endpoint"] + ) + async with httpx.AsyncClient() as client: + r = await client.get(endpoint + "/api/address/" + charge.onchainaddress) + return r.json()["chain_stats"]["funded_txo_sum"] diff --git a/lnbits/extensions/satspay/tasks.py b/lnbits/extensions/satspay/tasks.py index 3a77501b2..27eb879a5 100644 --- a/lnbits/extensions/satspay/tasks.py +++ b/lnbits/extensions/satspay/tasks.py @@ -1,6 +1,5 @@ import asyncio -import httpx from loguru import logger from lnbits.core.models import Payment @@ -8,8 +7,7 @@ from lnbits.extensions.satspay.crud import check_address_balance, get_charge from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener -from .helpers import public_charge -from .models import Charges +from .helpers import call_webhook async def wait_for_paid_invoices(): @@ -36,18 +34,3 @@ async def on_invoice_paid(payment: Payment) -> None: if charge.paid and charge.webhook: await call_webhook(charge) - - -async def call_webhook(charge: Charges): - async with httpx.AsyncClient() as client: - try: - r = await client.post( - charge.webhook, - json=public_charge(charge), - timeout=40, - ) - except AssertionError: - charge.webhook = None - except Exception as e: - logger.warning(f"Failed to call webhook for charge {charge.id}") - logger.warning(e) From ead710e591e584c6bf0d16dea50ec522317f05b5 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 25 Nov 2022 11:32:30 +0200 Subject: [PATCH 14/21] fix: important info, should be visible in the logs --- lnbits/tasks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lnbits/tasks.py b/lnbits/tasks.py index 94e43dcfd..de3c69aa7 100644 --- a/lnbits/tasks.py +++ b/lnbits/tasks.py @@ -124,7 +124,7 @@ async def check_pending_payments(): while True: async with db.connect() as conn: - logger.debug( + logger.info( f"Task: checking all pending payments (incoming={incoming}, outgoing={outgoing}) of last 15 days" ) start_time: float = time.time() @@ -140,15 +140,15 @@ async def check_pending_payments(): for payment in pending_payments: await payment.check_status(conn=conn) - logger.debug( + logger.info( f"Task: pending check finished for {len(pending_payments)} payments (took {time.time() - start_time:0.3f} s)" ) # we delete expired invoices once upon the first pending check if incoming: - logger.debug("Task: deleting all expired invoices") + logger.info("Task: deleting all expired invoices") start_time: float = time.time() await delete_expired_invoices(conn=conn) - logger.debug( + logger.info( f"Task: expired invoice deletion finished (took {time.time() - start_time:0.3f} s)" ) From 1a9cd81ee9e89b23e9d503c5f2badac36a4ce35c Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 25 Nov 2022 14:12:13 +0200 Subject: [PATCH 15/21] fix: persist webhook call status --- lnbits/extensions/satspay/helpers.py | 8 ++++---- lnbits/extensions/satspay/models.py | 15 +++++++++++++-- lnbits/extensions/satspay/tasks.py | 8 ++++++-- .../satspay/templates/satspay/index.html | 7 ++++++- lnbits/extensions/satspay/views.py | 5 ++--- lnbits/extensions/satspay/views_api.py | 9 ++++++++- 6 files changed, 39 insertions(+), 13 deletions(-) diff --git a/lnbits/extensions/satspay/helpers.py b/lnbits/extensions/satspay/helpers.py index 6862c6089..04846280d 100644 --- a/lnbits/extensions/satspay/helpers.py +++ b/lnbits/extensions/satspay/helpers.py @@ -35,18 +35,18 @@ async def call_webhook(charge: Charges): json=public_charge(charge), timeout=40, ) - except AssertionError: - charge.webhook = None + return {"webhook_success": r.is_success, "webhook_message": r.reason_phrase} except Exception as e: logger.warning(f"Failed to call webhook for charge {charge.id}") logger.warning(e) + return {"webhook_success": False, "webhook_message": str(e)} async def fetch_onchain_balance(charge: Charges): endpoint = ( f"{charge.config['mempool_endpoint']}/testnet" - if charge.config["network"] == "Testnet" - else charge.config["mempool_endpoint"] + if charge.config.network == "Testnet" + else charge.config.mempool_endpoint ) async with httpx.AsyncClient() as client: r = await client.get(endpoint + "/api/address/" + charge.onchainaddress) diff --git a/lnbits/extensions/satspay/models.py b/lnbits/extensions/satspay/models.py index 8d2602e15..1e7c95c99 100644 --- a/lnbits/extensions/satspay/models.py +++ b/lnbits/extensions/satspay/models.py @@ -19,6 +19,13 @@ class CreateCharge(BaseModel): extra: str = "{}" +class ChargeConfig(BaseModel): + mempool_endpoint: Optional[str] + network: Optional[str] + webhook_success: Optional[bool] = False + webhook_message: Optional[str] + + class Charges(BaseModel): id: str description: Optional[str] @@ -59,5 +66,9 @@ class Charges(BaseModel): return False @property - def config(self): - return json.loads(self.extra) + def config(self) -> ChargeConfig: + charge_config = json.loads(self.extra) + return ChargeConfig(**charge_config) + + def must_call_webhook(self): + return self.webhook and self.paid and self.config.webhook_success == False diff --git a/lnbits/extensions/satspay/tasks.py b/lnbits/extensions/satspay/tasks.py index 27eb879a5..ce54b44a2 100644 --- a/lnbits/extensions/satspay/tasks.py +++ b/lnbits/extensions/satspay/tasks.py @@ -1,4 +1,5 @@ import asyncio +import json from loguru import logger @@ -7,6 +8,7 @@ from lnbits.extensions.satspay.crud import check_address_balance, get_charge from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener +from .crud import update_charge from .helpers import call_webhook @@ -32,5 +34,7 @@ async def on_invoice_paid(payment: Payment) -> None: await payment.set_pending(False) charge = await check_address_balance(charge_id=charge.id) - if charge.paid and charge.webhook: - await call_webhook(charge) + if charge.must_call_webhook(): + resp = await call_webhook(charge) + extra = {**charge.config.dict(), **resp} + await update_charge(charge_id=charge.id, extra=json.dumps(extra)) diff --git a/lnbits/extensions/satspay/templates/satspay/index.html b/lnbits/extensions/satspay/templates/satspay/index.html index 3543dee35..60c4d5199 100644 --- a/lnbits/extensions/satspay/templates/satspay/index.html +++ b/lnbits/extensions/satspay/templates/satspay/index.html @@ -203,9 +203,14 @@ :href="props.row.webhook" target="_blank" style="color: unset; text-decoration: none" - >{{props.row.webhook || props.row.webhook}}{{props.row.webhook}}
+
+ + {{props.row.webhook_message }} + +
ID:
diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index 5faebe7a9..fdf0d86d0 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -34,10 +34,9 @@ async def display(request: Request, charge_id: str): view_data = { "request": request, "charge_data": public_charge(charge), + "mempool_endpoint": charge.config.mempool_endpoint, + "network": charge.config.network } - if "mempool_endpoint" in charge.config: - view_data["mempool_endpoint"] = charge.config["mempool_endpoint"] - view_data["network"] = charge.config["network"] return satspay_renderer().TemplateResponse( "satspay/display.html", diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index 73581022f..bfff55a21 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -1,3 +1,4 @@ +import json from http import HTTPStatus from fastapi.params import Depends @@ -19,7 +20,7 @@ from .crud import ( get_charges, update_charge, ) -from .helpers import public_charge +from .helpers import call_webhook, public_charge from .models import CreateCharge #############################CHARGES########################## @@ -57,6 +58,7 @@ async def api_charges_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)): **{"time_elapsed": charge.time_elapsed}, **{"time_left": charge.time_left}, **{"paid": charge.paid}, + **{"webhook_message": charge.config.webhook_message}, } for charge in await get_charges(wallet.wallet.user) ] @@ -118,4 +120,9 @@ async def api_charge_balance(charge_id): status_code=HTTPStatus.NOT_FOUND, detail="Charge does not exist." ) + if charge.must_call_webhook(): + resp = await call_webhook(charge) + extra = {**charge.config.dict(), **resp} + await update_charge(charge_id=charge.id, extra=json.dumps(extra)) + return {**public_charge(charge)} From 15c257fd65c33d52161948b90ca96c6c981b938d Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 25 Nov 2022 14:20:56 +0200 Subject: [PATCH 16/21] fix: object is not subscriable --- lnbits/extensions/satspay/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/satspay/helpers.py b/lnbits/extensions/satspay/helpers.py index 04846280d..2aa83e1f5 100644 --- a/lnbits/extensions/satspay/helpers.py +++ b/lnbits/extensions/satspay/helpers.py @@ -44,7 +44,7 @@ async def call_webhook(charge: Charges): async def fetch_onchain_balance(charge: Charges): endpoint = ( - f"{charge.config['mempool_endpoint']}/testnet" + f"{charge.config.mempool_endpoint}/testnet" if charge.config.network == "Testnet" else charge.config.mempool_endpoint ) From 3178b79fe46bca5018f98f47357ed8a5d0e37a38 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 25 Nov 2022 14:31:00 +0200 Subject: [PATCH 17/21] chore: format code --- lnbits/extensions/satspay/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index fdf0d86d0..26ecf540a 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -35,7 +35,7 @@ async def display(request: Request, charge_id: str): "request": request, "charge_data": public_charge(charge), "mempool_endpoint": charge.config.mempool_endpoint, - "network": charge.config.network + "network": charge.config.network, } return satspay_renderer().TemplateResponse( From cd53f9930b23fa9a19485c3ca58a0ec4b65ce06f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 25 Nov 2022 14:37:02 +0200 Subject: [PATCH 18/21] chore: update comment --- lnbits/extensions/satspay/migrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/satspay/migrations.py b/lnbits/extensions/satspay/migrations.py index d5f6ba13a..2579961f5 100644 --- a/lnbits/extensions/satspay/migrations.py +++ b/lnbits/extensions/satspay/migrations.py @@ -30,7 +30,7 @@ async def m001_initial(db): async def m002_add_charge_extra_data(db): """ - Add 'exta' for storing various config about the charge + Add 'extra' column for storing various config about the charge (JSON format) """ await db.execute( """ALTER TABLE satspay.charges From 44754df991c6e5b18799f327546bd20bec50a1eb Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 25 Nov 2022 14:37:26 +0200 Subject: [PATCH 19/21] refactor: remove single use var --- lnbits/extensions/satspay/views.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index 26ecf540a..7b769a20e 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -31,14 +31,12 @@ async def display(request: Request, charge_id: str): status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist." ) - view_data = { - "request": request, - "charge_data": public_charge(charge), - "mempool_endpoint": charge.config.mempool_endpoint, - "network": charge.config.network, - } - return satspay_renderer().TemplateResponse( "satspay/display.html", - view_data, + { + "request": request, + "charge_data": public_charge(charge), + "mempool_endpoint": charge.config.mempool_endpoint, + "network": charge.config.network, + }, ) From 988c7eb7b49c3d66b754ac748b3e4fea7e7701d1 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 26 Nov 2022 21:36:11 +0000 Subject: [PATCH 20/21] Added websocket test --- .../templates/lnurldevice/index.html | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index b0b223fff..75516ff2f 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -485,8 +485,9 @@ outline color="grey" @click="copyText(lnurlValue, 'LNURL copied to clipboard!')" - >Copy LNURL + >Copy LNURL +
+ {{ websocketMessage }}
Date: Sat, 26 Nov 2022 22:46:46 +0000 Subject: [PATCH 21/21] Added websocket checker for switch --- .../templates/lnurldevice/index.html | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index 75516ff2f..25dcf8c99 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -485,9 +485,19 @@ outline color="grey" @click="copyText(lnurlValue, 'LNURL copied to clipboard!')" - >Copy LNURL -
- {{ websocketMessage }} + >Copy LNURL + {% raw %}{{ wsMessage }}{% endraw %} + {% raw %}{{ wsMessage }}{% endraw %}