diff --git a/README.md b/README.md index ed9fe35..dc70c3b 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ See [lnbits.org](https://lnbits.org) for more detailed documentation. Checkout the LNbits [YouTube](https://www.youtube.com/playlist?list=PLPj3KCksGbSYG0ciIQUWJru1dWstPHshe) video series. -LNbits is inspired by all the great work of [opennode.com](https://www.opennode.com/), and in particular [lnpay.co](https://lnpay.co/). Both work as excellent funding sources for LNbits! +LNbits is inspired by all the great work of [opennode.com](https://www.opennode.com/), and in particular [lnpay.co](https://lnpay.co/). Both work as excellent funding sources for LNbits. ## LNbits as an account system diff --git a/lnbits/__init__.py b/lnbits/__init__.py index b243376..479338e 100644 --- a/lnbits/__init__.py +++ b/lnbits/__init__.py @@ -85,3 +85,4 @@ def migrate_databases(): if __name__ == "__main__": app.run() + diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index 1b73786..17b46a9 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -140,9 +140,9 @@ def get_wallet_payment(wallet_id: str, checking_id: str) -> Optional[Payment]: with open_db() as db: row = db.fetchone( """ - SELECT payhash as checking_id, amount, fee, pending, memo, time - FROM apipayments - WHERE wallet = ? AND payhash = ? + SELECT id as checking_id, amount, fee, pending, memo, time + FROM apipayment + WHERE wallet = ? AND id = ? """, (wallet_id, checking_id), ) @@ -179,7 +179,7 @@ def get_wallet_payments( with open_db() as db: rows = db.fetchall( f""" - SELECT payhash as checking_id, amount, fee, pending, memo, time + SELECT id as checking_id, amount, fee, pending, memo, time FROM apipayments WHERE wallet = ? {clause} ORDER BY time DESC @@ -195,7 +195,7 @@ def delete_wallet_payments_expired(wallet_id: str, *, seconds: int = 86400) -> N db.execute( """ DELETE - FROM apipayments WHERE wallet = ? AND pending = 1 AND time < strftime('%s', 'now') - ? + FROM apipayment WHERE wallet = ? AND pending = 1 AND time < strftime('%s', 'now') - ? """, (wallet_id, seconds), ) @@ -206,15 +206,15 @@ def delete_wallet_payments_expired(wallet_id: str, *, seconds: int = 86400) -> N def create_payment( - *, wallet_id: str, checking_id: str, amount: int, memo: str, fee: int = 0, pending: bool = True + *, wallet_id: str, checking_id: str, payment_hash: str, amount: int, memo: str, fee: int = 0, pending: bool = True ) -> Payment: with open_db() as db: db.execute( """ - INSERT INTO apipayments (wallet, payhash, amount, pending, memo, fee) - VALUES (?, ?, ?, ?, ?, ?) + INSERT INTO apipayment (wallet, id, payment_hash, amount, pending, memo, fee) + VALUES (?, ?, ?, ?, ?, ?, ?) """, - (wallet_id, checking_id, amount, int(pending), memo, fee), + (wallet_id, checking_id, payment_hash, amount, int(pending), memo, fee), ) new_payment = get_wallet_payment(wallet_id, checking_id) @@ -225,9 +225,18 @@ def create_payment( def update_payment_status(checking_id: str, pending: bool) -> None: with open_db() as db: - db.execute("UPDATE apipayments SET pending = ? WHERE payhash = ?", (int(pending), checking_id,)) + db.execute("UPDATE apipayment SET pending = ? WHERE id = ?", (int(pending), checking_id,)) def delete_payment(checking_id: str) -> None: with open_db() as db: - db.execute("DELETE FROM apipayments WHERE payhash = ?", (checking_id,)) + db.execute("DELETE FROM apipayment WHERE id = ?", (checking_id,)) + + +def check_internal(payment_hash: str) -> None: + with open_db() as db: + row = db.fetchone("SELECT * FROM apipayment WHERE payment_hash = ?", (payment_hash,)) + if not row: + return False + else: + return row['id'] diff --git a/lnbits/core/migrations.py b/lnbits/core/migrations.py index a844300..29b0371 100644 --- a/lnbits/core/migrations.py +++ b/lnbits/core/migrations.py @@ -51,6 +51,7 @@ def m001_initial(db): ); """ ) + db.execute( """ CREATE VIEW IF NOT EXISTS balances AS @@ -68,8 +69,74 @@ def m001_initial(db): GROUP BY wallet; """ ) + db.execute("DROP VIEW balances") + db.execute( + """ + CREATE VIEW IF NOT EXISTS balances AS + SELECT wallet, COALESCE(SUM(s), 0) AS balance FROM ( + SELECT wallet, SUM(amount) AS s -- incoming + FROM apipayment + WHERE amount > 0 AND pending = 0 -- don't sum pending + GROUP BY wallet + UNION ALL + SELECT wallet, SUM(amount + fee) AS s -- outgoing, sum fees + FROM apipayment + WHERE amount < 0 -- do sum pending + GROUP BY wallet + ) + GROUP BY wallet; + """ + ) +def m002_changed(db): + + db.execute( + """ + CREATE TABLE IF NOT EXISTS apipayment ( + id TEXT NOT NULL, + payment_hash TEXT NOT NULL, + amount INTEGER NOT NULL, + fee INTEGER NOT NULL DEFAULT 0, + wallet TEXT NOT NULL, + pending BOOLEAN NOT NULL, + memo TEXT, + time TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now')), + + UNIQUE (wallet, id) + ); + """ + ) + + + for row in [list(row) for row in db.fetchall("SELECT * FROM apipayments")]: + db.execute( + """ + INSERT INTO apipayment ( + id, + payment_hash, + amount, + fee, + wallet, + pending, + memo, + time + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + row[0], + "oldinvoice", + row[1], + row[2], + row[3], + row[4], + row[5], + row[6], + ), + ) + db.execute("DROP TABLE apipayments") def migrate(): with open_db() as db: m001_initial(db) + m002_changed(db) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 717cfc1..3ff3b32 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -4,7 +4,7 @@ from lnbits.bolt11 import decode as bolt11_decode # type: ignore from lnbits.helpers import urlsafe_short_hash from lnbits.settings import WALLET -from .crud import get_wallet, create_payment, delete_payment +from .crud import get_wallet, create_payment, delete_payment, check_internal, update_payment_status def create_invoice(*, wallet_id: str, amount: int, memo: str, description_hash: bytes = None) -> Tuple[str, str]: @@ -18,9 +18,10 @@ def create_invoice(*, wallet_id: str, amount: int, memo: str, description_hash: if not ok: raise Exception(error_message or "Unexpected backend error.") + invoice = bolt11_decode(payment_request) amount_msat = amount * 1000 - create_payment(wallet_id=wallet_id, checking_id=checking_id, amount=amount_msat, memo=memo) + create_payment(wallet_id=wallet_id, checking_id=checking_id, payment_hash=invoice.payment_hash, amount=amount_msat, memo=memo) return checking_id, payment_request @@ -29,6 +30,7 @@ def pay_invoice(*, wallet_id: str, bolt11: str, max_sat: Optional[int] = None) - temp_id = f"temp_{urlsafe_short_hash()}" try: invoice = bolt11_decode(bolt11) + internal = check_internal(invoice.payment_hash) if invoice.amount_msat == 0: raise ValueError("Amountless invoices not supported.") @@ -37,21 +39,41 @@ def pay_invoice(*, wallet_id: str, bolt11: str, max_sat: Optional[int] = None) - raise ValueError("Amount in invoice is too high.") fee_reserve = max(1000, int(invoice.amount_msat * 0.01)) - create_payment( - wallet_id=wallet_id, checking_id=temp_id, amount=-invoice.amount_msat, fee=-fee_reserve, memo=temp_id, - ) + + if not internal: + create_payment( + wallet_id=wallet_id, + checking_id=temp_id, + payment_hash=invoice.payment_hash, + amount=-invoice.amount_msat, + fee=-fee_reserve, + memo=temp_id, + ) wallet = get_wallet(wallet_id) assert wallet, "invalid wallet id" if wallet.balance_msat < 0: raise PermissionError("Insufficient balance.") - ok, checking_id, fee_msat, error_message = WALLET.pay_invoice(bolt11) + if internal: + create_payment( + wallet_id=wallet_id, + checking_id=temp_id, + payment_hash=invoice.payment_hash, + amount=-invoice.amount_msat, + fee=0, + pending=False, + memo=invoice.description, + ) + update_payment_status(checking_id=internal, pending=False) + return temp_id + ok, checking_id, fee_msat, error_message = WALLET.pay_invoice(bolt11) if ok: create_payment( wallet_id=wallet_id, checking_id=checking_id, + payment_hash=invoice.payment_hash, amount=-invoice.amount_msat, fee=fee_msat, memo=invoice.description,