2020-09-02 17:44:54 +02:00
|
|
|
import json
|
2020-09-02 05:58:21 +02:00
|
|
|
import datetime
|
2020-04-16 17:10:53 +02:00
|
|
|
from uuid import uuid4
|
2021-03-21 21:57:33 +01:00
|
|
|
from typing import List, Optional, Dict, Any
|
2020-03-04 23:11:15 +01:00
|
|
|
|
2020-09-02 05:58:21 +02:00
|
|
|
from lnbits import bolt11
|
2020-04-21 15:44:02 +02:00
|
|
|
from lnbits.settings import DEFAULT_WALLET_NAME
|
2020-03-04 23:11:15 +01:00
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
from . import db
|
2020-03-07 22:27:00 +01:00
|
|
|
from .models import User, Wallet, Payment
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
|
|
|
|
# accounts
|
|
|
|
# --------
|
|
|
|
|
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
async def create_account() -> User:
|
2020-09-07 05:47:13 +02:00
|
|
|
user_id = uuid4().hex
|
2020-11-21 22:04:39 +01:00
|
|
|
await db.execute("INSERT INTO accounts (id) VALUES (?)", (user_id,))
|
2020-03-04 23:11:15 +01:00
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
new_account = await get_account(user_id=user_id)
|
2020-04-26 13:28:19 +02:00
|
|
|
assert new_account, "Newly created account couldn't be retrieved"
|
|
|
|
|
|
|
|
return new_account
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
async def get_account(user_id: str) -> Optional[User]:
|
2021-03-24 04:40:32 +01:00
|
|
|
row = await db.fetchone(
|
|
|
|
"SELECT id, email, pass as password FROM accounts WHERE id = ?", (user_id,)
|
|
|
|
)
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
return User(**row) if row else None
|
|
|
|
|
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
async def get_user(user_id: str) -> Optional[User]:
|
|
|
|
user = await db.fetchone("SELECT id, email FROM accounts WHERE id = ?", (user_id,))
|
2020-09-07 05:47:13 +02:00
|
|
|
|
|
|
|
if user:
|
2021-03-24 04:40:32 +01:00
|
|
|
extensions = await db.fetchall(
|
|
|
|
"SELECT extension FROM extensions WHERE user = ? AND active = 1", (user_id,)
|
|
|
|
)
|
2020-11-21 22:04:39 +01:00
|
|
|
wallets = await db.fetchall(
|
2020-09-07 05:47:13 +02:00
|
|
|
"""
|
|
|
|
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
|
|
|
|
FROM wallets
|
|
|
|
WHERE user = ?
|
|
|
|
""",
|
|
|
|
(user_id,),
|
|
|
|
)
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
return (
|
2021-03-24 04:40:32 +01:00
|
|
|
User(
|
|
|
|
**{
|
|
|
|
**user,
|
|
|
|
**{
|
|
|
|
"extensions": [e[0] for e in extensions],
|
|
|
|
"wallets": [Wallet(**w) for w in wallets],
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
2020-03-04 23:11:15 +01:00
|
|
|
if user
|
|
|
|
else None
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
async def update_user_extension(*, user_id: str, extension: str, active: int) -> None:
|
|
|
|
await db.execute(
|
2020-09-07 05:47:13 +02:00
|
|
|
"""
|
|
|
|
INSERT OR REPLACE INTO extensions (user, extension, active)
|
|
|
|
VALUES (?, ?, ?)
|
|
|
|
""",
|
|
|
|
(user_id, extension, active),
|
|
|
|
)
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
|
|
|
|
# wallets
|
|
|
|
# -------
|
|
|
|
|
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
async def create_wallet(*, user_id: str, wallet_name: Optional[str] = None) -> Wallet:
|
2020-09-07 05:47:13 +02:00
|
|
|
wallet_id = uuid4().hex
|
2020-11-21 22:04:39 +01:00
|
|
|
await db.execute(
|
2020-09-07 05:47:13 +02:00
|
|
|
"""
|
|
|
|
INSERT INTO wallets (id, name, user, adminkey, inkey)
|
|
|
|
VALUES (?, ?, ?, ?, ?)
|
|
|
|
""",
|
2021-03-24 04:40:32 +01:00
|
|
|
(
|
|
|
|
wallet_id,
|
|
|
|
wallet_name or DEFAULT_WALLET_NAME,
|
|
|
|
user_id,
|
|
|
|
uuid4().hex,
|
|
|
|
uuid4().hex,
|
|
|
|
),
|
2020-09-07 05:47:13 +02:00
|
|
|
)
|
2020-03-04 23:11:15 +01:00
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
new_wallet = await get_wallet(wallet_id=wallet_id)
|
2020-04-26 13:28:19 +02:00
|
|
|
assert new_wallet, "Newly created wallet couldn't be retrieved"
|
|
|
|
|
|
|
|
return new_wallet
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
async def delete_wallet(*, user_id: str, wallet_id: str) -> None:
|
|
|
|
await db.execute(
|
2020-09-07 05:47:13 +02:00
|
|
|
"""
|
|
|
|
UPDATE wallets AS w
|
|
|
|
SET
|
|
|
|
user = 'del:' || w.user,
|
|
|
|
adminkey = 'del:' || w.adminkey,
|
|
|
|
inkey = 'del:' || w.inkey
|
|
|
|
WHERE id = ? AND user = ?
|
|
|
|
""",
|
|
|
|
(wallet_id, user_id),
|
|
|
|
)
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
async def get_wallet(wallet_id: str) -> Optional[Wallet]:
|
|
|
|
row = await db.fetchone(
|
2020-09-07 05:47:13 +02:00
|
|
|
"""
|
|
|
|
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
|
|
|
|
FROM wallets
|
|
|
|
WHERE id = ?
|
|
|
|
""",
|
|
|
|
(wallet_id,),
|
|
|
|
)
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
return Wallet(**row) if row else None
|
|
|
|
|
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
async def get_wallet_for_key(key: str, key_type: str = "invoice") -> Optional[Wallet]:
|
|
|
|
row = await db.fetchone(
|
2020-09-07 05:47:13 +02:00
|
|
|
"""
|
|
|
|
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
|
|
|
|
FROM wallets
|
|
|
|
WHERE adminkey = ? OR inkey = ?
|
|
|
|
""",
|
|
|
|
(key, key),
|
|
|
|
)
|
2020-03-04 23:11:15 +01:00
|
|
|
|
2020-09-07 05:47:13 +02:00
|
|
|
if not row:
|
|
|
|
return None
|
2020-05-05 09:59:15 +02:00
|
|
|
|
2020-09-07 05:47:13 +02:00
|
|
|
if key_type == "admin" and row["adminkey"] != key:
|
|
|
|
return None
|
2020-05-05 09:59:15 +02:00
|
|
|
|
2020-09-07 05:47:13 +02:00
|
|
|
return Wallet(**row)
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
|
2020-03-07 22:27:00 +01:00
|
|
|
# wallet payments
|
|
|
|
# ---------------
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
|
2021-03-07 04:08:36 +01:00
|
|
|
async def get_standalone_payment(checking_id_or_hash: str) -> Optional[Payment]:
|
2020-11-21 22:04:39 +01:00
|
|
|
row = await db.fetchone(
|
2020-09-28 04:12:55 +02:00
|
|
|
"""
|
|
|
|
SELECT *
|
|
|
|
FROM apipayments
|
2021-03-07 20:13:20 +01:00
|
|
|
WHERE checking_id = ? OR hash = ?
|
2021-03-07 04:08:36 +01:00
|
|
|
LIMIT 1
|
2020-09-28 04:12:55 +02:00
|
|
|
""",
|
2021-03-07 04:08:36 +01:00
|
|
|
(checking_id_or_hash, checking_id_or_hash),
|
2020-09-28 04:12:55 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
return Payment.from_row(row) if row else None
|
|
|
|
|
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
async def get_wallet_payment(wallet_id: str, payment_hash: str) -> Optional[Payment]:
|
|
|
|
row = await db.fetchone(
|
2020-09-07 05:47:13 +02:00
|
|
|
"""
|
|
|
|
SELECT *
|
|
|
|
FROM apipayments
|
|
|
|
WHERE wallet = ? AND hash = ?
|
|
|
|
""",
|
|
|
|
(wallet_id, payment_hash),
|
|
|
|
)
|
2020-03-04 23:11:15 +01:00
|
|
|
|
2020-09-01 03:12:46 +02:00
|
|
|
return Payment.from_row(row) if row else None
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
|
2021-03-21 21:57:33 +01:00
|
|
|
async def get_payments(
|
2020-09-12 03:14:25 +02:00
|
|
|
*,
|
2021-03-21 21:57:33 +01:00
|
|
|
wallet_id: Optional[str] = None,
|
2020-09-12 03:14:25 +02:00
|
|
|
complete: bool = False,
|
|
|
|
pending: bool = False,
|
|
|
|
outgoing: bool = False,
|
|
|
|
incoming: bool = False,
|
2021-03-24 05:41:19 +01:00
|
|
|
since: Optional[int] = None,
|
2020-09-12 03:14:25 +02:00
|
|
|
exclude_uncheckable: bool = False,
|
2020-08-31 05:24:57 +02:00
|
|
|
) -> List[Payment]:
|
|
|
|
"""
|
|
|
|
Filters payments to be returned by complete | pending | outgoing | incoming.
|
|
|
|
"""
|
|
|
|
|
2021-03-24 05:41:19 +01:00
|
|
|
args: List[Any] = []
|
|
|
|
clause: List[str] = []
|
2021-03-21 21:57:33 +01:00
|
|
|
|
2021-03-24 05:41:19 +01:00
|
|
|
if since != None:
|
|
|
|
clause.append("time > ?")
|
|
|
|
args.append(since)
|
2021-03-21 21:57:33 +01:00
|
|
|
|
|
|
|
if wallet_id:
|
|
|
|
clause.append("wallet = ?")
|
2021-03-24 05:41:19 +01:00
|
|
|
args.append(wallet_id)
|
2021-03-21 21:57:33 +01:00
|
|
|
|
2020-08-31 05:24:57 +02:00
|
|
|
if complete and pending:
|
2021-03-21 21:57:33 +01:00
|
|
|
pass
|
2020-08-31 05:24:57 +02:00
|
|
|
elif complete:
|
2021-03-21 21:57:33 +01:00
|
|
|
clause.append("((amount > 0 AND pending = 0) OR amount < 0)")
|
2020-08-31 05:24:57 +02:00
|
|
|
elif pending:
|
2021-03-21 21:57:33 +01:00
|
|
|
clause.append("pending = 1")
|
2020-08-31 05:24:57 +02:00
|
|
|
else:
|
2021-03-21 21:57:33 +01:00
|
|
|
pass
|
2020-09-12 03:14:25 +02:00
|
|
|
|
2020-08-31 05:24:57 +02:00
|
|
|
if outgoing and incoming:
|
2021-03-21 21:57:33 +01:00
|
|
|
pass
|
2020-08-31 05:24:57 +02:00
|
|
|
elif outgoing:
|
2021-03-21 21:57:33 +01:00
|
|
|
clause.append("amount < 0")
|
2020-08-31 05:24:57 +02:00
|
|
|
elif incoming:
|
2021-03-21 21:57:33 +01:00
|
|
|
clause.append("amount > 0")
|
2020-08-31 05:24:57 +02:00
|
|
|
else:
|
2021-03-21 21:57:33 +01:00
|
|
|
pass
|
2020-09-12 03:14:25 +02:00
|
|
|
|
|
|
|
if exclude_uncheckable: # checkable means it has a checking_id that isn't internal
|
2021-03-21 21:57:33 +01:00
|
|
|
clause.append("checking_id NOT LIKE 'temp_%'")
|
|
|
|
clause.append("checking_id NOT LIKE 'internal_%'")
|
|
|
|
|
|
|
|
where = ""
|
|
|
|
if clause:
|
|
|
|
where = f"WHERE {' AND '.join(clause)}"
|
2020-09-12 03:14:25 +02:00
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
rows = await db.fetchall(
|
2020-09-07 05:47:13 +02:00
|
|
|
f"""
|
|
|
|
SELECT *
|
|
|
|
FROM apipayments
|
2021-03-21 21:57:33 +01:00
|
|
|
{where}
|
2020-09-07 05:47:13 +02:00
|
|
|
ORDER BY time DESC
|
|
|
|
""",
|
2021-03-24 05:41:19 +01:00
|
|
|
tuple(args),
|
2020-09-07 05:47:13 +02:00
|
|
|
)
|
2020-03-04 23:11:15 +01:00
|
|
|
|
2020-09-01 03:12:46 +02:00
|
|
|
return [Payment.from_row(row) for row in rows]
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
async def delete_expired_invoices() -> None:
|
|
|
|
rows = await db.fetchall(
|
2020-09-02 05:58:21 +02:00
|
|
|
"""
|
2020-09-07 05:47:13 +02:00
|
|
|
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
|
2020-09-02 05:58:21 +02:00
|
|
|
|
2020-09-07 05:47:13 +02:00
|
|
|
expiration_date = datetime.datetime.fromtimestamp(invoice.date + invoice.expiry)
|
|
|
|
if expiration_date > datetime.datetime.utcnow():
|
|
|
|
continue
|
2020-09-02 05:58:21 +02:00
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
await db.execute(
|
2020-09-07 05:47:13 +02:00
|
|
|
"""
|
|
|
|
DELETE FROM apipayments
|
|
|
|
WHERE pending = 1 AND hash = ?
|
|
|
|
""",
|
|
|
|
(invoice.payment_hash,),
|
|
|
|
)
|
2020-04-17 21:13:57 +02:00
|
|
|
|
|
|
|
|
2020-03-07 22:27:00 +01:00
|
|
|
# payments
|
|
|
|
# --------
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
async def create_payment(
|
2020-09-01 03:12:46 +02:00
|
|
|
*,
|
|
|
|
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,
|
2020-12-24 13:38:35 +01:00
|
|
|
webhook: Optional[str] = None,
|
2020-03-31 19:05:25 +02:00
|
|
|
) -> Payment:
|
2020-11-21 22:04:39 +01:00
|
|
|
await db.execute(
|
2020-09-07 05:47:13 +02:00
|
|
|
"""
|
|
|
|
INSERT INTO apipayments
|
|
|
|
(wallet, checking_id, bolt11, hash, preimage,
|
2020-12-24 13:38:35 +01:00
|
|
|
amount, pending, memo, fee, extra, webhook)
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
2020-09-07 05:47:13 +02:00
|
|
|
""",
|
|
|
|
(
|
|
|
|
wallet_id,
|
|
|
|
checking_id,
|
|
|
|
payment_request,
|
|
|
|
payment_hash,
|
|
|
|
preimage,
|
|
|
|
amount,
|
|
|
|
int(pending),
|
|
|
|
memo,
|
|
|
|
fee,
|
2021-03-24 04:40:32 +01:00
|
|
|
json.dumps(extra)
|
|
|
|
if extra and extra != {} and type(extra) is dict
|
|
|
|
else None,
|
2020-12-24 13:38:35 +01:00
|
|
|
webhook,
|
2020-09-07 05:47:13 +02:00
|
|
|
),
|
|
|
|
)
|
2020-03-04 23:11:15 +01:00
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
new_payment = await get_wallet_payment(wallet_id, payment_hash)
|
2020-04-26 13:28:19 +02:00
|
|
|
assert new_payment, "Newly created payment couldn't be retrieved"
|
|
|
|
|
|
|
|
return new_payment
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
async def update_payment_status(checking_id: str, pending: bool) -> None:
|
|
|
|
await db.execute(
|
2021-03-07 23:18:02 +01:00
|
|
|
"UPDATE apipayments SET pending = ? WHERE checking_id = ?",
|
|
|
|
(
|
|
|
|
int(pending),
|
|
|
|
checking_id,
|
|
|
|
),
|
2020-09-07 05:47:13 +02:00
|
|
|
)
|
2020-03-04 23:11:15 +01:00
|
|
|
|
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
async def delete_payment(checking_id: str) -> None:
|
|
|
|
await db.execute("DELETE FROM apipayments WHERE checking_id = ?", (checking_id,))
|
2020-08-19 18:53:27 +02:00
|
|
|
|
|
|
|
|
2020-11-21 22:04:39 +01:00
|
|
|
async def check_internal(payment_hash: str) -> Optional[str]:
|
|
|
|
row = await db.fetchone(
|
2020-09-07 05:47:13 +02:00
|
|
|
"""
|
|
|
|
SELECT checking_id FROM apipayments
|
|
|
|
WHERE hash = ? AND pending AND amount > 0
|
|
|
|
""",
|
|
|
|
(payment_hash,),
|
|
|
|
)
|
|
|
|
if not row:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return row["checking_id"]
|