payment logic safer and adapt db calls to new schema.
This commit is contained in:
parent
754782b598
commit
582aaa812b
|
@ -1,10 +1,10 @@
|
||||||
import os
|
import os
|
||||||
import lnurl
|
import lnurl
|
||||||
import requests
|
import requests
|
||||||
import time
|
|
||||||
|
|
||||||
from flask import Flask, jsonify, render_template, request
|
from flask import Flask, jsonify, render_template, request
|
||||||
|
|
||||||
|
from . import bolt11
|
||||||
from .db import Database
|
from .db import Database
|
||||||
from .helpers import encrypt
|
from .helpers import encrypt
|
||||||
from .settings import INVOICE_KEY, ADMIN_KEY, API_ENDPOINT, DATABASE_PATH, LNBITS_PATH
|
from .settings import INVOICE_KEY, ADMIN_KEY, API_ENDPOINT, DATABASE_PATH, LNBITS_PATH
|
||||||
|
@ -115,7 +115,7 @@ def lnurlwallet():
|
||||||
inkey = encrypt(adminkey)
|
inkey = encrypt(adminkey)
|
||||||
|
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"INSERT INTO wallets (hash, balance, transactions, name, user, adminkey, inkey) VALUES ('"
|
"INSERT INTO wallets (hash, name, user, adminkey, inkey) VALUES ('"
|
||||||
+ thewal
|
+ thewal
|
||||||
+ "',',0,"
|
+ "',',0,"
|
||||||
+ str(withdraw)
|
+ str(withdraw)
|
||||||
|
@ -197,8 +197,7 @@ def wallet():
|
||||||
inkey = encrypt(adminkey)
|
inkey = encrypt(adminkey)
|
||||||
|
|
||||||
db.execute(
|
db.execute(
|
||||||
"INSERT INTO wallets (hash, balance, transactions, name, user, adminkey, inkey) "
|
"INSERT INTO wallets (hash, name, user, adminkey, inkey) VALUES (?, ?, ?, ?, ?)",
|
||||||
"VALUES (?, 0, 0, ?, ?, ?, ?)",
|
|
||||||
(thewal, thenme, theid, adminkey, inkey),
|
(thewal, thenme, theid, adminkey, inkey),
|
||||||
)
|
)
|
||||||
rows = db.fetchall("SELECT * FROM wallets WHERE user = ?", (theid,))
|
rows = db.fetchall("SELECT * FROM wallets WHERE user = ?", (theid,))
|
||||||
|
@ -222,8 +221,7 @@ def wallet():
|
||||||
inkey = encrypt(adminkey)
|
inkey = encrypt(adminkey)
|
||||||
|
|
||||||
db.execute(
|
db.execute(
|
||||||
"INSERT INTO wallets (hash, balance, transactions, name, user, adminkey, inkey) "
|
"INSERT INTO wallets (hash, name, user, adminkey, inkey) " "VALUES (?, ?, ?, ?, ?)",
|
||||||
"VALUES (?, 0, 0, ?, ?, ?, ?)",
|
|
||||||
(thewal, thenme, theid, adminkey, inkey),
|
(thewal, thenme, theid, adminkey, inkey),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -247,8 +245,7 @@ def wallet():
|
||||||
inkey = encrypt(adminkey)
|
inkey = encrypt(adminkey)
|
||||||
|
|
||||||
db.execute(
|
db.execute(
|
||||||
"INSERT INTO wallets (hash, balance, transactions, name, user, adminkey, inkey) "
|
"INSERT INTO wallets (hash, name, user, adminkey, inkey) " "VALUES (?, ?, ?, ?, ?)",
|
||||||
"VALUES (?, 0, 0, ?, ?, ?, ?)",
|
|
||||||
(thewal, thenme, theid, adminkey, inkey),
|
(thewal, thenme, theid, adminkey, inkey),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -281,28 +278,27 @@ def api_invoices():
|
||||||
return jsonify({"ERROR": "NO MEMO"}), 200
|
return jsonify({"ERROR": "NO MEMO"}), 200
|
||||||
|
|
||||||
with Database() as db:
|
with Database() as db:
|
||||||
rows = db.fetchall("SELECT * FROM wallets WHERE inkey = ?", (request.headers["Grpc-Metadata-macaroon"],))
|
wallet_row = db.fetchone(
|
||||||
|
"SELECT hash FROM wallets WHERE inkey = ? OR adminkey = ?",
|
||||||
|
(request.headers["Grpc-Metadata-macaroon"], request.headers["Grpc-Metadata-macaroon"],),
|
||||||
|
)
|
||||||
|
|
||||||
if not rows:
|
if not wallet_row:
|
||||||
return jsonify({"ERROR": "NO KEY"}), 200
|
return jsonify({"ERROR": "NO KEY"}), 200
|
||||||
|
|
||||||
dataj = {"amt": postedjson["value"], "memo": postedjson["memo"]}
|
r = requests.post(
|
||||||
headers = {"Authorization": f"Basic {INVOICE_KEY}"}
|
url=f"{API_ENDPOINT}/addinvoice",
|
||||||
r = requests.post(url=f"{API_ENDPOINT}/addinvoice", json=dataj, headers=headers)
|
json={"amt": postedjson["value"], "memo": postedjson["memo"]},
|
||||||
|
headers={"Authorization": f"Basic {INVOICE_KEY}"},
|
||||||
|
)
|
||||||
data = r.json()
|
data = r.json()
|
||||||
|
|
||||||
pay_req = data["pay_req"]
|
pay_req = data["pay_req"]
|
||||||
payment_hash = data["payment_hash"]
|
payment_hash = data["payment_hash"]
|
||||||
|
|
||||||
db.execute(
|
db.execute(
|
||||||
"INSERT INTO apipayments (payhash, amount, wallet, paid, inkey, memo) VALUES (?, ?, ?, 0, ?, ?)",
|
"INSERT INTO apipayments (payhash, amount, wallet, pending, memo) VALUES (?, ?, ?, true, ?)",
|
||||||
(
|
(payment_hash, postedjson["value"] * 1000, wallet_row[0], postedjson["memo"],),
|
||||||
payment_hash,
|
|
||||||
postedjson["value"],
|
|
||||||
rows[0][0],
|
|
||||||
request.headers["Grpc-Metadata-macaroon"],
|
|
||||||
postedjson["memo"],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return jsonify({"pay_req": pay_req, "payment_hash": payment_hash}), 200
|
return jsonify({"pay_req": pay_req, "payment_hash": payment_hash}), 200
|
||||||
|
@ -313,15 +309,17 @@ def api_transactions():
|
||||||
if request.headers["Content-Type"] != "application/json":
|
if request.headers["Content-Type"] != "application/json":
|
||||||
return jsonify({"ERROR": "MUST BE JSON"}), 200
|
return jsonify({"ERROR": "MUST BE JSON"}), 200
|
||||||
|
|
||||||
postedjson = request.json
|
data = request.json
|
||||||
|
|
||||||
if "payment_request" not in postedjson:
|
if "payment_request" not in data:
|
||||||
return jsonify({"ERROR": "NO PAY REQ"}), 200
|
return jsonify({"ERROR": "NO PAY REQ"}), 200
|
||||||
|
|
||||||
with Database() as db:
|
with Database() as db:
|
||||||
wallets = db.fetchall("SELECT * FROM wallets WHERE adminkey = ?", (request.headers["Grpc-Metadata-macaroon"],))
|
wallet_row = db.fetchone(
|
||||||
|
"SELECT hash FROM wallets WHERE adminkey = ?", (request.headers["Grpc-Metadata-macaroon"],)
|
||||||
|
)
|
||||||
|
|
||||||
if not wallets:
|
if not wallet_row:
|
||||||
return jsonify({"ERROR": "BAD AUTH"}), 200
|
return jsonify({"ERROR": "BAD AUTH"}), 200
|
||||||
|
|
||||||
# TODO: check this unused code
|
# TODO: check this unused code
|
||||||
|
@ -350,31 +348,65 @@ def api_transactions():
|
||||||
"""
|
"""
|
||||||
# ---------------------------------
|
# ---------------------------------
|
||||||
|
|
||||||
dataj = {"invoice": postedjson["payment_request"]}
|
# decode the invoice
|
||||||
headers = {"Authorization": f"Basic {ADMIN_KEY}"}
|
invoice = bolt11.decode(data["payment_request"])
|
||||||
r = requests.post(url=f"{API_ENDPOINT}/payinvoice", json=dataj, headers=headers)
|
|
||||||
data = r.json()
|
|
||||||
|
|
||||||
|
# insert the payment
|
||||||
db.execute(
|
db.execute(
|
||||||
"INSERT INTO apipayments (payhash, amount, wallet, paid, adminkey, memo) VALUES (?, ?, ?, 1, ?, ?)'",
|
"INSERT INTO apipayments (payhash, amount, fee, wallet, pending, memo) VALUES (?, ?, ?, true, ?)'",
|
||||||
(
|
(
|
||||||
data["decoded"]["payment_hash"],
|
invoice.payment_hash,
|
||||||
str(-int(data["decoded"]["num_satoshis"])),
|
-int(invoice.amount_msat),
|
||||||
wallets[0][0],
|
-int(invoice.amount_msat * 0.01),
|
||||||
request.headers["Grpc-Metadata-macaroon"],
|
wallet_row[0],
|
||||||
data["decoded"]["description"],
|
invoice.description,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
payment = db.fetchall("SELECT * FROM apipayments WHERE payhash = ?", (data["decoded"]["payment_hash"],))[0]
|
# check balance
|
||||||
|
balance = db.fetchone(
|
||||||
|
"""
|
||||||
|
WITH wallettransactions AS (
|
||||||
|
SELECT *
|
||||||
|
FROM apipayments
|
||||||
|
WHERE wallet = ?
|
||||||
|
)
|
||||||
|
|
||||||
lastamt = str(wallets[0][1]).split(",")
|
SELECT sum(s) FROM (
|
||||||
newamt = int(lastamt[-1]) - int(data["decoded"]["num_satoshis"])
|
SELECT sum(amount) AS s -- incoming
|
||||||
updamt = wallets[0][1] + "," + str(newamt)
|
FROM wallettransactions
|
||||||
transactions = f"{wallets[0][2]}!{payment[5]},{time.time()},{payment[1]},{newamt}"
|
WHERE amount > 0 AND pending = false -- don't sum pending
|
||||||
|
UNION ALL
|
||||||
|
SELECT sum(amount + fee) AS s -- outgoing, sum fees
|
||||||
|
FROM wallettransactions
|
||||||
|
WHERE amount < 0 -- do sum pending
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
(wallet_row[0],),
|
||||||
|
)
|
||||||
|
if balance < 0:
|
||||||
|
return jsonify({"ERROR": "INSUFFICIENT BALANCE"}), 403
|
||||||
|
|
||||||
|
# actually send the payment
|
||||||
|
r = requests.post(
|
||||||
|
url=f"{API_ENDPOINT}/payinvoice",
|
||||||
|
json={"invoice": data["payment_request"]},
|
||||||
|
headers={"Authorization": f"Basic {ADMIN_KEY}"},
|
||||||
|
)
|
||||||
|
if not r.ok:
|
||||||
|
return jsonify({"ERROR": "UNEXPECTED PAYMENT ERROR"}), 500
|
||||||
|
|
||||||
|
data = r.json()
|
||||||
|
if r.ok and data["error"]:
|
||||||
|
# payment didn't went through, delete it here
|
||||||
|
# (these guarantees specific to lntxbot)
|
||||||
|
db.execute("DELETE FROM apipayments WHERE payhash = ?", (invoice.payment_hash,))
|
||||||
|
return jsonify({"PAID": "FALSE"}), 200
|
||||||
|
|
||||||
|
# payment went through, not pending anymore, save actual fees
|
||||||
db.execute(
|
db.execute(
|
||||||
"UPDATE wallets SET balance = ?, transactions = ? WHERE hash = ?", (updamt, transactions, wallets[0][0])
|
"UPDATE apipayments SET pending = false, fee = ? WHERE payhash = ?",
|
||||||
|
(data["fee_msat"], invoice.payment_hash,),
|
||||||
)
|
)
|
||||||
|
|
||||||
return jsonify({"PAID": "TRUE"}), 200
|
return jsonify({"PAID": "TRUE"}), 200
|
||||||
|
@ -386,31 +418,30 @@ def api_checkinvoice(payhash):
|
||||||
return jsonify({"ERROR": "MUST BE JSON"}), 200
|
return jsonify({"ERROR": "MUST BE JSON"}), 200
|
||||||
|
|
||||||
with Database() as db:
|
with Database() as db:
|
||||||
payment = db.fetchall("SELECT * FROM apipayments WHERE payhash = ?", (payhash,))[0]
|
payment_row = db.fetchone(
|
||||||
|
"""
|
||||||
|
SELECT pending FROM apipayments
|
||||||
|
INNER JOIN wallets AS w ON apipayments.wallet = w.hash
|
||||||
|
WHERE payhash = ?
|
||||||
|
AND (w.adminkey = ? OR w.invkey = ?)
|
||||||
|
""",
|
||||||
|
(payhash, request.headers["Grpc-Metadata-macaroon"], request.headers["Grpc-Metadata-macaroon"]),
|
||||||
|
)
|
||||||
|
|
||||||
if request.headers["Grpc-Metadata-macaroon"] != payment[4]:
|
if not payment_row:
|
||||||
return jsonify({"ERROR": "WRONG KEY"}), 400
|
return jsonify({"ERROR": "NO INVOICE"}), 404
|
||||||
|
|
||||||
if payment[3] != "0":
|
if not payment_row[0]: # pending
|
||||||
return jsonify({"PAID": "TRUE"}), 200
|
return jsonify({"PAID": "TRUE"}), 200
|
||||||
|
|
||||||
headers = {"Authorization": f"Basic {INVOICE_KEY}"}
|
headers = {"Authorization": f"Basic {INVOICE_KEY}"}
|
||||||
r = requests.post(url=f"{API_ENDPOINT}/invoicestatus/{payhash}", headers=headers)
|
r = requests.post(url=f"{API_ENDPOINT}/invoicestatus/{payhash}", headers=headers)
|
||||||
data = r.json()
|
if not r.ok:
|
||||||
|
|
||||||
if data == "":
|
|
||||||
return jsonify({"PAID": "FALSE"}), 400
|
return jsonify({"PAID": "FALSE"}), 400
|
||||||
|
|
||||||
wallet = db.fetchall("SELECT * FROM wallets WHERE hash = ?", (payment[2],))[0]
|
data = r.json()
|
||||||
|
if "preimage" not in data or not data["preimage"]:
|
||||||
|
return jsonify({"PAID": "FALSE"}), 400
|
||||||
|
|
||||||
lastamt = wallet[1].split(",")
|
db.execute("UPDATE apipayments SET pending = false WHERE payhash = ?", (payhash,))
|
||||||
newamt = int(lastamt[-1]) + int(payment[1])
|
return jsonify({"PAID": "TRUE"}), 200
|
||||||
updamt = wallet[1] + "," + str(newamt)
|
|
||||||
transactions = f"{wallet[2]}!{payment[5]},{time.time()},{payment[1]},{newamt}"
|
|
||||||
|
|
||||||
db.execute(
|
|
||||||
"UPDATE wallets SET balance = ?, transactions = ? WHERE hash = ?", (updamt, transactions, payment[2])
|
|
||||||
)
|
|
||||||
db.execute("UPDATE apipayments SET paid = '1' WHERE payhash = ?", (payhash,))
|
|
||||||
|
|
||||||
return jsonify({"PAID": "TRUE"}), 200
|
|
||||||
|
|
|
@ -21,6 +21,10 @@ class Database:
|
||||||
self.cursor.execute(query, values)
|
self.cursor.execute(query, values)
|
||||||
return self.cursor.fetchall()
|
return self.cursor.fetchall()
|
||||||
|
|
||||||
|
def fetchone(self, query: str, values: tuple):
|
||||||
|
self.cursor.execute(query, values)
|
||||||
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
def execute(self, query: str, values: tuple) -> None:
|
def execute(self, query: str, values: tuple) -> None:
|
||||||
"""Given a query, cursor.execute() it."""
|
"""Given a query, cursor.execute() it."""
|
||||||
self.cursor.execute(query, values)
|
self.cursor.execute(query, values)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user