diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml
index ce0233de..57abd8ba 100644
--- a/.github/workflows/linting.yml
+++ b/.github/workflows/linting.yml
@@ -1,25 +1,33 @@
-name: Run Linters
+name: Linters
-on: [push, pull_request]
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
jobs:
- mypy:
+ black:
runs-on: ubuntu-latest
steps:
- - name: Checkout
- uses: actions/checkout@v1
- - name: Run MyPy python type checker
- uses: jpetrucciani/mypy-check@master
- with:
- path: 'lnbits'
-
+ - uses: actions/checkout@v2
+ - run: sudo apt-get install python3-venv
+ - run: python3 -m venv venv
+ - run: ./venv/bin/pip install black
+ - run: make checkblack
prettier:
runs-on: ubuntu-latest
steps:
- - name: Checkout
- uses: actions/checkout@v1
- - name: Check JS code formatting convention
- uses: creyD/prettier_action@v2.2
- with:
- dry: True
- prettier_options: --write lnbits/static/js/** lnbits/core/static/js/** lnbits/extensions/*/templates/**
+ - uses: actions/checkout@v2
+ - run: npm install
+ - run: make checkprettier
+ mypy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - run: sudo apt-get install python3-venv
+ - run: sudo apt-get install libev-dev
+ - run: python3 -m venv venv
+ - run: ./venv/bin/pip install -r requirements.txt
+ - run: ./venv/bin/pip install mypy
+ - run: make mypy
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 415fd15c..e2715129 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: [3.6, 3.7, 3.8]
+ python-version: [3.8]
steps:
- uses: actions/checkout@v2
diff --git a/Makefile b/Makefile
index b96d1eda..fd8c70d0 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,20 @@
-all: prettier mypy black
+all: format check
+
+format: prettier black
+
+check: mypy checkprettier checkblack
prettier: $(shell find lnbits -name "*.js" -name ".html")
./node_modules/.bin/prettier --write lnbits/static/js/*.js lnbits/core/static/js/*.js lnbits/extensions/*/templates/*/*.html ./lnbits/core/templates/core/*.html lnbits/templates/*.html lnbits/extensions/*/static/js/*.js
-mypy: $(shell find lnbits -name "*.py")
- mypy lnbits
-
black: $(shell find lnbits -name "*.py")
- black lnbits
+ ./venv/bin/black --line-length 120 lnbits
+
+mypy: $(shell find lnbits -name "*.py")
+ ./venv/bin/mypy lnbits
+
+checkprettier: $(shell find lnbits -name "*.js" -name ".html")
+ ./node_modules/.bin/prettier --check lnbits/static/js/*.js lnbits/core/static/js/*.js lnbits/extensions/*/templates/*/*.html ./lnbits/core/templates/core/*.html lnbits/templates/*.html lnbits/extensions/*/static/js/*.js
+
+checkblack: $(shell find lnbits -name "*.py")
+ ./venv/bin/black --check --line-length 120 lnbits
diff --git a/README.md b/README.md
index ed9fe356..dc70c3b8 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/bolt11.py b/lnbits/bolt11.py
index d9344617..184844f7 100644
--- a/lnbits/bolt11.py
+++ b/lnbits/bolt11.py
@@ -1,12 +1,10 @@
-# type: ignore
-
-import bitstring
+import bitstring # type: ignore
import re
import hashlib
-from typing import List, NamedTuple
+from typing import List, NamedTuple, Optional
from bech32 import bech32_decode, CHARSET
-from ecdsa import SECP256k1, VerifyingKey
-from ecdsa.util import sigdecode_string
+from ecdsa import SECP256k1, VerifyingKey # type: ignore
+from ecdsa.util import sigdecode_string # type: ignore
from binascii import unhexlify
@@ -19,40 +17,40 @@ class Route(NamedTuple):
class Invoice(object):
- payment_hash: str = None
+ payment_hash: str
amount_msat: int = 0
- description: str = None
- payee: str = None
- date: int = None
+ description: Optional[str] = None
+ description_hash: Optional[str] = None
+ payee: str
+ date: int
expiry: int = 3600
- secret: str = None
+ secret: Optional[str] = None
route_hints: List[Route] = []
min_final_cltv_expiry: int = 18
def decode(pr: str) -> Invoice:
- """ Super naïve bolt11 decoder,
- only gets payment_hash, description/description_hash and amount in msatoshi.
+ """bolt11 decoder,
based on https://github.com/rustyrussell/lightning-payencode/blob/master/lnaddr.py
"""
- hrp, data = bech32_decode(pr)
- if not hrp:
- raise ValueError("Bad bech32 checksum")
+ hrp, decoded_data = bech32_decode(pr)
+ if hrp is None or decoded_data is None:
+ raise ValueError("Bad bech32 checksum")
if not hrp.startswith("ln"):
raise ValueError("Does not start with ln")
- data = u5_to_bitarray(data)
+ bitarray = _u5_to_bitarray(decoded_data)
# final signature 65 bytes, split it off.
- if len(data) < 65 * 8:
+ if len(bitarray) < 65 * 8:
raise ValueError("Too short to contain signature")
# extract the signature
- signature = data[-65 * 8 :].tobytes()
+ signature = bitarray[-65 * 8 :].tobytes()
# the tagged fields as a bitstream
- data = bitstring.ConstBitStream(data[: -65 * 8])
+ data = bitstring.ConstBitStream(bitarray[: -65 * 8])
# build the invoice object
invoice = Invoice()
@@ -62,35 +60,35 @@ def decode(pr: str) -> Invoice:
if m:
amountstr = hrp[2 + m.end() :]
if amountstr != "":
- invoice.amount_msat = unshorten_amount(amountstr)
+ invoice.amount_msat = _unshorten_amount(amountstr)
# pull out date
invoice.date = data.read(35).uint
while data.pos != data.len:
- tag, tagdata, data = pull_tagged(data)
+ tag, tagdata, data = _pull_tagged(data)
data_length = len(tagdata) / 5
if tag == "d":
- invoice.description = trim_to_bytes(tagdata).decode("utf-8")
+ invoice.description = _trim_to_bytes(tagdata).decode("utf-8")
elif tag == "h" and data_length == 52:
- invoice.description = trim_to_bytes(tagdata).hex()
+ invoice.description_hash = _trim_to_bytes(tagdata).hex()
elif tag == "p" and data_length == 52:
- invoice.payment_hash = trim_to_bytes(tagdata).hex()
+ invoice.payment_hash = _trim_to_bytes(tagdata).hex()
elif tag == "x":
invoice.expiry = tagdata.uint
elif tag == "n":
- invoice.payee = trim_to_bytes(tagdata).hex()
+ invoice.payee = _trim_to_bytes(tagdata).hex()
# this won't work in most cases, we must extract the payee
# from the signature
elif tag == "s":
- invoice.secret = trim_to_bytes(tagdata).hex()
+ invoice.secret = _trim_to_bytes(tagdata).hex()
elif tag == "r":
s = bitstring.ConstBitStream(tagdata)
while s.pos + 264 + 64 + 32 + 32 + 16 < s.len:
route = Route(
pubkey=s.read(264).tobytes().hex(),
- short_channel_id=readable_scid(s.read(64).intbe),
+ short_channel_id=_readable_scid(s.read(64).intbe),
base_fee_msat=s.read(32).intbe,
ppm_fee=s.read(32).intbe,
cltv=s.read(16).intbe,
@@ -116,7 +114,7 @@ def decode(pr: str) -> Invoice:
return invoice
-def unshorten_amount(amount: str) -> int:
+def _unshorten_amount(amount: str) -> int:
""" Given a shortened amount, return millisatoshis
"""
# BOLT #11:
@@ -141,18 +139,18 @@ def unshorten_amount(amount: str) -> int:
raise ValueError("Invalid amount '{}'".format(amount))
if unit in units:
- return int(amount[:-1]) * 100_000_000_000 / units[unit]
+ return int(int(amount[:-1]) * 100_000_000_000 / units[unit])
else:
return int(amount) * 100_000_000_000
-def pull_tagged(stream):
+def _pull_tagged(stream):
tag = stream.read(5).uint
length = stream.read(5).uint * 32 + stream.read(5).uint
return (CHARSET[tag], stream.read(length * 5), stream)
-def trim_to_bytes(barr):
+def _trim_to_bytes(barr):
# Adds a byte if necessary.
b = barr.tobytes()
if barr.len % 8 != 0:
@@ -160,7 +158,7 @@ def trim_to_bytes(barr):
return b
-def readable_scid(short_channel_id: int) -> str:
+def _readable_scid(short_channel_id: int) -> str:
return "{blockheight}x{transactionindex}x{outputindex}".format(
blockheight=((short_channel_id >> 40) & 0xFFFFFF),
transactionindex=((short_channel_id >> 16) & 0xFFFFFF),
@@ -168,7 +166,7 @@ def readable_scid(short_channel_id: int) -> str:
)
-def u5_to_bitarray(arr):
+def _u5_to_bitarray(arr: List[int]) -> bitstring.BitArray:
ret = bitstring.BitArray()
for a in arr:
ret += bitstring.pack("uint:5", a)
diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py
index 1b73786c..4733a493 100644
--- a/lnbits/core/crud.py
+++ b/lnbits/core/crud.py
@@ -1,7 +1,10 @@
-from typing import List, Optional
+import json
+import datetime
from uuid import uuid4
+from typing import List, Optional, Dict
from lnbits.db import open_db
+from lnbits import bolt11
from lnbits.settings import DEFAULT_WALLET_NAME
from .models import User, Wallet, Payment
@@ -136,18 +139,18 @@ def get_wallet_for_key(key: str, key_type: str = "invoice") -> Optional[Wallet]:
# ---------------
-def get_wallet_payment(wallet_id: str, checking_id: str) -> Optional[Payment]:
+def get_wallet_payment(wallet_id: str, payment_hash: str) -> Optional[Payment]:
with open_db() as db:
row = db.fetchone(
"""
- SELECT payhash as checking_id, amount, fee, pending, memo, time
+ SELECT *
FROM apipayments
- WHERE wallet = ? AND payhash = ?
+ WHERE wallet = ? AND hash = ?
""",
- (wallet_id, checking_id),
+ (wallet_id, payment_hash),
)
- return Payment(**row) if row else None
+ return Payment.from_row(row) if row else None
def get_wallet_payments(
@@ -179,7 +182,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 *
FROM apipayments
WHERE wallet = ? {clause}
ORDER BY time DESC
@@ -187,18 +190,35 @@ def get_wallet_payments(
(wallet_id,),
)
- return [Payment(**row) for row in rows]
+ return [Payment.from_row(row) for row in rows]
-def delete_wallet_payments_expired(wallet_id: str, *, seconds: int = 86400) -> None:
+def delete_expired_invoices() -> None:
with open_db() as db:
- db.execute(
+ rows = db.fetchall(
"""
- DELETE
- FROM apipayments WHERE wallet = ? AND pending = 1 AND time < strftime('%s', 'now') - ?
- """,
- (wallet_id, seconds),
+ SELECT bolt11
+ FROM apipayments
+ WHERE pending = 1 AND amount > 0 AND time < strftime('%s', 'now') - 86400
+ """
)
+ for (payment_request,) in rows:
+ try:
+ invoice = bolt11.decode(payment_request)
+ except:
+ continue
+
+ expiration_date = datetime.datetime.fromtimestamp(invoice.date + invoice.expiry)
+ if expiration_date > datetime.datetime.utcnow():
+ continue
+
+ db.execute(
+ """
+ DELETE FROM apipayments
+ WHERE pending = 1 AND payment_hash = ?
+ """,
+ (invoice.payment_hash,),
+ )
# payments
@@ -206,18 +226,41 @@ 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_request: str,
+ payment_hash: str,
+ amount: int,
+ memo: str,
+ fee: int = 0,
+ preimage: Optional[str] = None,
+ pending: bool = True,
+ extra: Optional[Dict] = None,
) -> Payment:
with open_db() as db:
db.execute(
"""
- INSERT INTO apipayments (wallet, payhash, amount, pending, memo, fee)
- VALUES (?, ?, ?, ?, ?, ?)
+ INSERT INTO apipayments
+ (wallet, checking_id, bolt11, hash, preimage,
+ amount, pending, memo, fee, extra)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
- (wallet_id, checking_id, amount, int(pending), memo, fee),
+ (
+ wallet_id,
+ checking_id,
+ payment_request,
+ payment_hash,
+ preimage,
+ amount,
+ int(pending),
+ memo,
+ fee,
+ json.dumps(extra) if extra and extra != {} and type(extra) is dict else None,
+ ),
)
- new_payment = get_wallet_payment(wallet_id, checking_id)
+ new_payment = get_wallet_payment(wallet_id, payment_hash)
assert new_payment, "Newly created payment couldn't be retrieved"
return new_payment
@@ -225,9 +268,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 apipayments SET pending = ? WHERE checking_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 apipayments WHERE checking_id = ?", (checking_id,))
+
+
+def check_internal(payment_hash: str) -> Optional[str]:
+ with open_db() as db:
+ row = db.fetchone("SELECT checking_id FROM apipayments WHERE hash = ?", (payment_hash,))
+ if not row:
+ return None
+ else:
+ return row["checking_id"]
diff --git a/lnbits/core/migrations.py b/lnbits/core/migrations.py
index a8443003..78631f44 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
@@ -70,6 +71,40 @@ def m001_initial(db):
)
+def m002_add_fields_to_apipayments(db):
+ """
+ Adding fields to apipayments for better accounting,
+ and renaming payhash to checking_id since that is what it really is.
+ """
+ db.execute("ALTER TABLE apipayments RENAME COLUMN payhash TO checking_id")
+ db.execute("ALTER TABLE apipayments ADD COLUMN hash TEXT")
+ db.execute("CREATE INDEX by_hash ON apipayments (hash)")
+ db.execute("ALTER TABLE apipayments ADD COLUMN preimage TEXT")
+ db.execute("ALTER TABLE apipayments ADD COLUMN bolt11 TEXT")
+ db.execute("ALTER TABLE apipayments ADD COLUMN extra TEXT")
+
+ import json
+
+ rows = db.fetchall("SELECT * FROM apipayments")
+ for row in rows:
+ if not row["memo"] or not row["memo"].startswith("#"):
+ continue
+
+ for ext in ["withdraw", "events", "lnticket", "paywall", "tpos"]:
+ prefix = "#" + ext + " "
+ if row["memo"].startswith(prefix):
+ new = row["memo"][len(prefix) :]
+ db.execute(
+ """
+ UPDATE apipayments SET extra = ?, memo = ?
+ WHERE checking_id = ? AND memo = ?
+ """,
+ (json.dumps({"tag": ext}), new, row["checking_id"], row["memo"]),
+ )
+ break
+
+
def migrate():
with open_db() as db:
m001_initial(db)
+ m002_add_fields_to_apipayments(db)
diff --git a/lnbits/core/models.py b/lnbits/core/models.py
index 10a87ad1..ed4ed43d 100644
--- a/lnbits/core/models.py
+++ b/lnbits/core/models.py
@@ -1,4 +1,6 @@
-from typing import List, NamedTuple, Optional
+import json
+from typing import List, NamedTuple, Optional, Dict
+from sqlite3 import Row
class User(NamedTuple):
@@ -29,10 +31,10 @@ class Wallet(NamedTuple):
def balance(self) -> int:
return self.balance_msat // 1000
- def get_payment(self, checking_id: str) -> Optional["Payment"]:
+ def get_payment(self, payment_hash: str) -> Optional["Payment"]:
from .crud import get_wallet_payment
- return get_wallet_payment(self.id, checking_id)
+ return get_wallet_payment(self.id, payment_hash)
def get_payments(
self, *, complete: bool = True, pending: bool = False, outgoing: bool = True, incoming: bool = True
@@ -41,11 +43,6 @@ class Wallet(NamedTuple):
return get_wallet_payments(self.id, complete=complete, pending=pending, outgoing=outgoing, incoming=incoming)
- def delete_expired_payments(self, seconds: int = 86400) -> None:
- from .crud import delete_wallet_payments_expired
-
- delete_wallet_payments_expired(self.id, seconds=seconds)
-
class Payment(NamedTuple):
checking_id: str
@@ -54,6 +51,29 @@ class Payment(NamedTuple):
fee: int
memo: str
time: int
+ bolt11: str
+ preimage: str
+ payment_hash: str
+ extra: Dict
+
+ @classmethod
+ def from_row(cls, row: Row):
+ return cls(
+ checking_id=row["checking_id"],
+ payment_hash=row["hash"],
+ bolt11=row["bolt11"],
+ preimage=row["preimage"],
+ extra=json.loads(row["extra"] or "{}"),
+ pending=row["pending"],
+ amount=row["amount"],
+ fee=row["fee"],
+ memo=row["memo"],
+ time=row["time"],
+ )
+
+ @property
+ def tag(self) -> Optional[str]:
+ return self.extra.get("tag")
@property
def msat(self) -> int:
@@ -71,6 +91,10 @@ class Payment(NamedTuple):
def is_out(self) -> bool:
return self.amount < 0
+ @property
+ def is_uncheckable(self) -> bool:
+ return self.checking_id.startswith("temp_") or self.checking_id.startswith("internal_")
+
def set_pending(self, pending: bool) -> None:
from .crud import update_payment_status
diff --git a/lnbits/core/services.py b/lnbits/core/services.py
index 717cfc17..6f39111f 100644
--- a/lnbits/core/services.py
+++ b/lnbits/core/services.py
@@ -1,72 +1,112 @@
-from typing import Optional, Tuple
+from typing import Optional, Tuple, Dict, TypedDict
-from lnbits.bolt11 import decode as bolt11_decode # type: ignore
+from lnbits import bolt11
from lnbits.helpers import urlsafe_short_hash
from lnbits.settings import WALLET
+from lnbits.wallets.base import PaymentStatus
-from .crud import get_wallet, create_payment, delete_payment
+from .crud import get_wallet, create_payment, delete_payment, check_internal, update_payment_status, get_wallet_payment
-def create_invoice(*, wallet_id: str, amount: int, memo: str, description_hash: bytes = None) -> Tuple[str, str]:
-
- try:
- ok, checking_id, payment_request, error_message = WALLET.create_invoice(
- amount=amount, memo=memo, description_hash=description_hash
- )
- except Exception as e:
- ok, error_message = False, str(e)
+def create_invoice(
+ *, wallet_id: str, amount: int, memo: str, description_hash: Optional[bytes] = None, extra: Optional[Dict] = None,
+) -> Tuple[str, str]:
+ invoice_memo = None if description_hash else memo
+ storeable_memo = memo
+ ok, checking_id, payment_request, error_message = WALLET.create_invoice(
+ amount=amount, memo=invoice_memo, description_hash=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_request=payment_request,
+ payment_hash=invoice.payment_hash,
+ amount=amount_msat,
+ memo=storeable_memo,
+ extra=extra,
+ )
- return checking_id, payment_request
+ return invoice.payment_hash, payment_request
-def pay_invoice(*, wallet_id: str, bolt11: str, max_sat: Optional[int] = None) -> str:
+def pay_invoice(
+ *, wallet_id: str, payment_request: str, max_sat: Optional[int] = None, extra: Optional[Dict] = None
+) -> str:
temp_id = f"temp_{urlsafe_short_hash()}"
- try:
- invoice = bolt11_decode(bolt11)
+ internal_id = f"internal_{urlsafe_short_hash()}"
- if invoice.amount_msat == 0:
- raise ValueError("Amountless invoices not supported.")
+ invoice = bolt11.decode(payment_request)
+ if invoice.amount_msat == 0:
+ raise ValueError("Amountless invoices not supported.")
+ if max_sat and invoice.amount_msat > max_sat * 1000:
+ raise ValueError("Amount in invoice is too high.")
- if max_sat and invoice.amount_msat > max_sat * 1000:
- raise ValueError("Amount in invoice is too high.")
+ # put all parameters that don't change here
+ PaymentKwargs = TypedDict(
+ "PaymentKwargs",
+ {
+ "wallet_id": str,
+ "payment_request": str,
+ "payment_hash": str,
+ "amount": int,
+ "memo": str,
+ "extra": Optional[Dict],
+ },
+ )
+ payment_kwargs: PaymentKwargs = dict(
+ wallet_id=wallet_id,
+ payment_request=payment_request,
+ payment_hash=invoice.payment_hash,
+ amount=-invoice.amount_msat,
+ memo=invoice.description or "",
+ extra=extra,
+ )
+ # check_internal() returns the checking_id of the invoice we're waiting for
+ internal = check_internal(invoice.payment_hash)
+ if internal:
+ # create a new payment from this wallet
+ create_payment(checking_id=internal_id, fee=0, pending=False, **payment_kwargs)
+ else:
+ # create a temporary payment here so we can check if
+ # the balance is enough in the next step
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,
- )
+ create_payment(checking_id=temp_id, fee=-fee_reserve, **payment_kwargs)
- 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)
+ # do the balance check
+ wallet = get_wallet(wallet_id)
+ assert wallet, "invalid wallet id"
+ if wallet.balance_msat < 0:
+ raise PermissionError("Insufficient balance.")
+ if internal:
+ # mark the invoice from the other side as not pending anymore
+ # so the other side only has access to his new money when we are sure
+ # the payer has enough to deduct from
+ update_payment_status(checking_id=internal, pending=False)
+ else:
+ # actually pay the external invoice
+ ok, checking_id, fee_msat, error_message = WALLET.pay_invoice(payment_request)
if ok:
- create_payment(
- wallet_id=wallet_id,
- checking_id=checking_id,
- amount=-invoice.amount_msat,
- fee=fee_msat,
- memo=invoice.description,
- )
-
- except Exception as e:
- ok, error_message = False, str(e)
-
- delete_payment(temp_id)
+ create_payment(checking_id=checking_id, fee=fee_msat, **payment_kwargs)
+ delete_payment(temp_id)
if not ok:
raise Exception(error_message or "Unexpected backend error.")
- return checking_id
+ return invoice.payment_hash
-def check_payment(*, checking_id: str) -> str:
- pass
+def check_invoice_status(wallet_id: str, payment_hash: str) -> PaymentStatus:
+ payment = get_wallet_payment(wallet_id, payment_hash)
+ if not payment:
+ return PaymentStatus(None)
+
+ return WALLET.get_invoice_status(payment.checking_id)
diff --git a/lnbits/core/static/js/wallet.js b/lnbits/core/static/js/wallet.js
index 20e7778f..140ca479 100644
--- a/lnbits/core/static/js/wallet.js
+++ b/lnbits/core/static/js/wallet.js
@@ -1,3 +1,5 @@
+/* globals decode, Vue, VueQrcodeReader, VueQrcode, Quasar, LNbits, _, EventHub, Chart */
+
Vue.component(VueQrcode.name, VueQrcode)
Vue.use(VueQrcodeReader)
@@ -115,6 +117,7 @@ new Vue({
mixins: [windowMixin],
data: function () {
return {
+ user: LNbits.map.user(window.user),
receive: {
show: false,
status: 'pending',
@@ -138,7 +141,12 @@ new Vue({
payments: [],
paymentsTable: {
columns: [
- {name: 'memo', align: 'left', label: 'Memo', field: 'memo'},
+ {
+ name: 'memo',
+ align: 'left',
+ label: 'Memo',
+ field: 'memo'
+ },
{
name: 'date',
align: 'left',
@@ -171,7 +179,7 @@ new Vue({
computed: {
filteredPayments: function () {
var q = this.paymentsTable.filter
- if (!q || q == '') return this.payments
+ if (!q || q === '') return this.payments
return LNbits.utils.search(this.payments, q)
},
@@ -261,7 +269,7 @@ new Vue({
self.receive.paymentChecker = setInterval(function () {
LNbits.api
- .getPayment(self.g.wallet, response.data.checking_id)
+ .getPayment(self.g.wallet, response.data.payment_hash)
.then(function (response) {
if (response.data.paid) {
self.fetchPayments()
@@ -308,11 +316,11 @@ new Vue({
_.each(invoice.data.tags, function (tag) {
if (_.isObject(tag) && _.has(tag, 'description')) {
- if (tag.description == 'payment_hash') {
+ if (tag.description === 'payment_hash') {
cleanInvoice.hash = tag.value
- } else if (tag.description == 'description') {
+ } else if (tag.description === 'description') {
cleanInvoice.description = tag.value
- } else if (tag.description == 'expiry') {
+ } else if (tag.description === 'expiry') {
var expireDate = new Date(
(invoice.data.time_stamp + tag.value) * 1000
)
@@ -330,7 +338,7 @@ new Vue({
payInvoice: function () {
var self = this
- dismissPaymentMsg = this.$q.notify({
+ let dismissPaymentMsg = this.$q.notify({
timeout: 0,
message: 'Processing payment...',
icon: null
@@ -341,7 +349,7 @@ new Vue({
.then(function (response) {
self.send.paymentChecker = setInterval(function () {
LNbits.api
- .getPayment(self.g.wallet, response.data.checking_id)
+ .getPayment(self.g.wallet, response.data.payment_hash)
.then(function (res) {
if (res.data.paid) {
self.send.show = false
diff --git a/lnbits/core/templates/core/_api_docs.html b/lnbits/core/templates/core/_api_docs.html
index 38c35685..f1fddd5e 100644
--- a/lnbits/core/templates/core/_api_docs.html
+++ b/lnbits/core/templates/core/_api_docs.html
@@ -23,7 +23,7 @@
Returns 201 CREATED (application/json)
{"checking_id": <string>, "payment_request":
+ >{"payment_hash": <string>, "payment_request":
<string>}
{"checking_id": <string>}
+ {"payment_hash": <string>}
curl -X POST {{ request.url_root }}api/v1/payments -d '{"out": true,
@@ -73,7 +73,7 @@
GET
- /api/v1/payments/<checking_id>
Headers
{"X-Api-Key": "{{ wallet.inkey }}"}
@@ -83,9 +83,9 @@
{"paid": <bool>}
Curl example
curl -X GET {{ request.url_root }}api/v1/payments/<checking_id>
- -H "X-Api-Key: {{ wallet.inkey }}" -H "Content-type:
- application/json"curl -X GET {{ request.url_root
+ }}api/v1/payments/<payment_hash> -H "X-Api-Key:
+ {{ wallet.inkey }}" -H "Content-type: application/json"
diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html
index 6d19bcaa..2d476746 100644
--- a/lnbits/core/templates/core/wallet.html
+++ b/lnbits/core/templates/core/wallet.html
@@ -8,7 +8,7 @@
{% endblock %} {% block scripts %} {{ window_vars(user, wallet) }}
{% assets filters='rjsmin', output='__bundle__/core/chart.js',
-'vendor/moment@2.25.1/moment.min.js', 'vendor/chart.js@2.9.3/chart.min.js' %}
+'vendor/moment@2.27.0/moment.min.js', 'vendor/chart.js@2.9.3/chart.min.js' %}
{% endassets %} {% assets filters='rjsmin', output='__bundle__/core/wallet.js',
'vendor/bolt11/utils.js', 'vendor/bolt11/decoder.js',
@@ -76,7 +76,7 @@
clearable
v-model="paymentsTable.filter"
debounce="300"
- placeholder="Search by memo, amount"
+ placeholder="Search by tag, memo, amount"
class="q-mb-md"
>
@@ -84,7 +84,7 @@
dense
flat
:data="filteredPayments"
- row-key="payhash"
+ row-key="payment_hash"
:columns="paymentsTable.columns"
:pagination.sync="paymentsTable.pagination"
>
@@ -103,14 +103,28 @@
{"checking_id": <string>, "payment_request":
+ >{"payment_hash": <string>, "payment_request":
<string>}
{"checking_id": <string>}
+ {"payment_hash": <string>}
curl -X POST {{ request.url_root
}}paywall/api/v1/paywalls/<paywall_id>/check_invoice -d
- '{"checking_id": <string>}' -H "Content-type: application/json"
+ '{"payment_hash": <string>}' -H "Content-type: application/json"
diff --git a/lnbits/extensions/paywall/templates/paywall/display.html b/lnbits/extensions/paywall/templates/paywall/display.html
index f3b7c6ff..b9248ed3 100644
--- a/lnbits/extensions/paywall/templates/paywall/display.html
+++ b/lnbits/extensions/paywall/templates/paywall/display.html
@@ -121,7 +121,7 @@
axios
.post(
'/paywall/api/v1/paywalls/{{ paywall.id }}/check_invoice',
- {checking_id: response.data.checking_id}
+ {payment_hash: response.data.payment_hash}
)
.then(function (res) {
if (res.data.paid) {
diff --git a/lnbits/extensions/paywall/views_api.py b/lnbits/extensions/paywall/views_api.py
index 012d355d..85786a5f 100644
--- a/lnbits/extensions/paywall/views_api.py
+++ b/lnbits/extensions/paywall/views_api.py
@@ -2,9 +2,8 @@ from flask import g, jsonify, request
from http import HTTPStatus
from lnbits.core.crud import get_user, get_wallet
-from lnbits.core.services import create_invoice
+from lnbits.core.services import create_invoice, check_invoice_status
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
-from lnbits.settings import WALLET
from lnbits.extensions.paywall import paywall_ext
from .crud import create_paywall, get_paywall, get_paywalls, delete_paywall
@@ -64,17 +63,17 @@ def api_paywall_create_invoice(paywall_id):
try:
amount = g.data["amount"] if g.data["amount"] > paywall.amount else paywall.amount
- checking_id, payment_request = create_invoice(
- wallet_id=paywall.wallet, amount=amount, memo=f"#paywall {paywall.memo}"
+ payment_hash, payment_request = create_invoice(
+ wallet_id=paywall.wallet, amount=amount, memo=f"{paywall.memo}", extra={"tag": "paywall"}
)
except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
- return jsonify({"checking_id": checking_id, "payment_request": payment_request}), HTTPStatus.CREATED
+ return jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.CREATED
@paywall_ext.route("/api/v1/paywalls/{"checking_id": <string>,"payment_request":
+ >{"id": <string>, "name": <string>, "admin":
+ <string>, "email": <string>, "password":
<string>}
{"checking_id": <string>,"payment_request":
- <string>}{"id": <string>, "admin": <string>, "name":
+ <string>, "user": <string>, "adminkey": <string>,
+ "inkey": <string>}
0
obj.isOut = obj.amount < 0
- obj.isPaid = obj.pending == 0
+ obj.isPaid = obj.pending === 0
obj._q = [obj.memo, obj.sat].join(' ').toLowerCase()
return obj
}
@@ -146,8 +164,6 @@ var LNbits = {
})
},
search: function (data, q, field, separator) {
- var field = field || '_q'
-
try {
var queries = q.toLowerCase().split(separator || ' ')
return data.filter(function (obj) {
@@ -155,7 +171,7 @@ var LNbits = {
_.each(queries, function (q) {
if (obj[field].indexOf(q) !== -1) matches++
})
- return matches == queries.length
+ return matches === queries.length
})
} catch (err) {
return data
@@ -255,7 +271,7 @@ var windowMixin = {
})
.map(function (obj) {
if (user) {
- obj.isEnabled = user.extensions.indexOf(obj.code) != -1
+ obj.isEnabled = user.extensions.indexOf(obj.code) !== -1
} else {
obj.isEnabled = false
}
diff --git a/package.json b/package.json
index d2b65603..835ff779 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,5 @@
{
"devDependencies": {
"prettier": "^2.0.5"
- },
- "scripts": {
- "lint": "prettier --write lnbits/static/js/** lnbits/core/static/js/** lnbits/extensions/*/templates/**"
}
}