mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2024-11-19 09:54:21 +01:00
refactor: "payments" is the name, and API updates
This commit is contained in:
parent
6e26e06aea
commit
d862b16ee6
@ -3,18 +3,16 @@ import json
|
||||
import requests
|
||||
import uuid
|
||||
|
||||
from flask import g, Flask, jsonify, redirect, render_template, request, url_for
|
||||
from flask import Flask, redirect, render_template, request, url_for
|
||||
from flask_assets import Environment, Bundle
|
||||
from flask_compress import Compress
|
||||
from flask_talisman import Talisman
|
||||
from lnurl import Lnurl, LnurlWithdrawResponse
|
||||
|
||||
from . import bolt11
|
||||
from .core import core_app
|
||||
from .decorators import api_validate_post_request
|
||||
from .db import init_databases, open_db
|
||||
from .helpers import ExtensionManager, megajson
|
||||
from .settings import WALLET, DEFAULT_USER_WALLET_NAME, FEE_RESERVE
|
||||
from .settings import WALLET, DEFAULT_USER_WALLET_NAME
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
@ -147,93 +145,5 @@ def lnurlwallet():
|
||||
return redirect(url_for("wallet", usr=user_id, wal=wallet_id))
|
||||
|
||||
|
||||
@app.route("/api/v1/channels/transactions", methods=["GET", "POST"])
|
||||
@api_validate_post_request(required_params=["payment_request"])
|
||||
def api_transactions():
|
||||
|
||||
with open_db() as db:
|
||||
wallet = db.fetchone("SELECT id FROM wallets WHERE adminkey = ?", (request.headers["Grpc-Metadata-macaroon"],))
|
||||
|
||||
if not wallet:
|
||||
return jsonify({"message": "BAD AUTH"}), 401
|
||||
|
||||
# decode the invoice
|
||||
invoice = bolt11.decode(g.data["payment_request"])
|
||||
if invoice.amount_msat == 0:
|
||||
return jsonify({"message": "AMOUNTLESS INVOICES NOT SUPPORTED"}), 400
|
||||
|
||||
# insert the payment
|
||||
db.execute(
|
||||
"INSERT OR IGNORE INTO apipayments (payhash, amount, fee, wallet, pending, memo) VALUES (?, ?, ?, ?, 1, ?)",
|
||||
(
|
||||
invoice.payment_hash,
|
||||
-int(invoice.amount_msat),
|
||||
-int(invoice.amount_msat) * FEE_RESERVE,
|
||||
wallet["id"],
|
||||
invoice.description,
|
||||
),
|
||||
)
|
||||
|
||||
# check balance
|
||||
balance = db.fetchone("SELECT balance/1000 FROM balances WHERE wallet = ?", (wallet["id"],))[0]
|
||||
if balance < 0:
|
||||
db.execute("DELETE FROM apipayments WHERE payhash = ? AND wallet = ?", (invoice.payment_hash, wallet["id"]))
|
||||
return jsonify({"message": "INSUFFICIENT BALANCE"}), 403
|
||||
|
||||
# check if the invoice is an internal one
|
||||
if db.fetchone("SELECT count(*) FROM apipayments WHERE payhash = ?", (invoice.payment_hash,))[0] == 2:
|
||||
# internal. mark both sides as fulfilled.
|
||||
db.execute("UPDATE apipayments SET pending = 0, fee = 0 WHERE payhash = ?", (invoice.payment_hash,))
|
||||
else:
|
||||
# actually send the payment
|
||||
r = WALLET.pay_invoice(g.data["payment_request"])
|
||||
|
||||
if not r.raw_response.ok or r.failed:
|
||||
return jsonify({"message": "UNEXPECTED PAYMENT ERROR"}), 500
|
||||
|
||||
# payment went through, not pending anymore, save actual fees
|
||||
db.execute(
|
||||
"UPDATE apipayments SET pending = 0, fee = ? WHERE payhash = ? AND wallet = ?",
|
||||
(r.fee_msat, invoice.payment_hash, wallet["id"]),
|
||||
)
|
||||
|
||||
return jsonify({"PAID": "TRUE", "payment_hash": invoice.payment_hash}), 200
|
||||
|
||||
|
||||
@app.route("/api/v1/checkpending", methods=["POST"])
|
||||
def api_checkpending():
|
||||
with open_db() as db:
|
||||
for pendingtx in db.fetchall(
|
||||
"""
|
||||
SELECT
|
||||
payhash,
|
||||
CASE
|
||||
WHEN amount < 0 THEN 'send'
|
||||
ELSE 'recv'
|
||||
END AS kind
|
||||
FROM apipayments
|
||||
INNER JOIN wallets ON apipayments.wallet = wallets.id
|
||||
WHERE time > strftime('%s', 'now') - 86400
|
||||
AND pending = 1
|
||||
AND (adminkey = ? OR inkey = ?)
|
||||
""",
|
||||
(request.headers["Grpc-Metadata-macaroon"], request.headers["Grpc-Metadata-macaroon"]),
|
||||
):
|
||||
payhash = pendingtx["payhash"]
|
||||
kind = pendingtx["kind"]
|
||||
|
||||
if kind == "send":
|
||||
payment_complete = WALLET.get_payment_status(payhash).settled
|
||||
if payment_complete:
|
||||
db.execute("UPDATE apipayments SET pending = 0 WHERE payhash = ?", (payhash,))
|
||||
elif payment_complete is False:
|
||||
db.execute("DELETE FROM apipayments WHERE payhash = ?", (payhash,))
|
||||
|
||||
elif kind == "recv" and WALLET.get_invoice_status(payhash).settled:
|
||||
db.execute("UPDATE apipayments SET pending = 0 WHERE payhash = ?", (payhash,))
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
|
@ -4,7 +4,7 @@ from lnbits.db import open_db
|
||||
from lnbits.settings import DEFAULT_USER_WALLET_NAME, FEE_RESERVE
|
||||
from typing import List, Optional
|
||||
|
||||
from .models import User, Transaction, Wallet
|
||||
from .models import User, Wallet, Payment
|
||||
|
||||
|
||||
# accounts
|
||||
@ -34,7 +34,7 @@ def get_user(user_id: str) -> Optional[User]:
|
||||
extensions = db.fetchall("SELECT extension FROM extensions WHERE user = ? AND active = 1", (user_id,))
|
||||
wallets = db.fetchall(
|
||||
"""
|
||||
SELECT *, COALESCE((SELECT balance/1000 FROM balances WHERE wallet = wallets.id), 0) * ? AS balance
|
||||
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) * ? AS balance_msat
|
||||
FROM wallets
|
||||
WHERE user = ?
|
||||
""",
|
||||
@ -96,7 +96,7 @@ def get_wallet(wallet_id: str) -> Optional[Wallet]:
|
||||
with open_db() as db:
|
||||
row = db.fetchone(
|
||||
"""
|
||||
SELECT *, COALESCE((SELECT balance/1000 FROM balances WHERE wallet = wallets.id), 0) * ? AS balance
|
||||
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) * ? AS balance_msat
|
||||
FROM wallets
|
||||
WHERE id = ?
|
||||
""",
|
||||
@ -111,7 +111,7 @@ def get_wallet_for_key(key: str, key_type: str = "invoice") -> Optional[Wallet]:
|
||||
check_field = "adminkey" if key_type == "admin" else "inkey"
|
||||
row = db.fetchone(
|
||||
f"""
|
||||
SELECT *, COALESCE((SELECT balance/1000 FROM balances WHERE wallet = wallets.id), 0) * ? AS balance
|
||||
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) * ? AS balance_msat
|
||||
FROM wallets
|
||||
WHERE {check_field} = ?
|
||||
""",
|
||||
@ -121,11 +121,11 @@ def get_wallet_for_key(key: str, key_type: str = "invoice") -> Optional[Wallet]:
|
||||
return Wallet(**row) if row else None
|
||||
|
||||
|
||||
# wallet transactions
|
||||
# -------------------
|
||||
# wallet payments
|
||||
# ---------------
|
||||
|
||||
|
||||
def get_wallet_transaction(wallet_id: str, payhash: str) -> Optional[Transaction]:
|
||||
def get_wallet_payment(wallet_id: str, payhash: str) -> Optional[Payment]:
|
||||
with open_db() as db:
|
||||
row = db.fetchone(
|
||||
"""
|
||||
@ -136,45 +136,51 @@ def get_wallet_transaction(wallet_id: str, payhash: str) -> Optional[Transaction
|
||||
(wallet_id, payhash),
|
||||
)
|
||||
|
||||
return Transaction(**row) if row else None
|
||||
return Payment(**row) if row else None
|
||||
|
||||
|
||||
def get_wallet_transactions(wallet_id: str, *, pending: bool = False) -> List[Transaction]:
|
||||
def get_wallet_payments(wallet_id: str, *, include_all_pending: bool = False) -> List[Payment]:
|
||||
with open_db() as db:
|
||||
if include_all_pending:
|
||||
clause = "pending = 1"
|
||||
else:
|
||||
clause = "((amount > 0 AND pending = 0) OR amount < 0)"
|
||||
|
||||
rows = db.fetchall(
|
||||
"""
|
||||
f"""
|
||||
SELECT payhash, amount, fee, pending, memo, time
|
||||
FROM apipayments
|
||||
WHERE wallet = ? AND pending = ?
|
||||
WHERE wallet = ? AND {clause}
|
||||
ORDER BY time DESC
|
||||
""",
|
||||
(wallet_id, int(pending)),
|
||||
(wallet_id,),
|
||||
)
|
||||
|
||||
return [Transaction(**row) for row in rows]
|
||||
return [Payment(**row) for row in rows]
|
||||
|
||||
|
||||
# transactions
|
||||
# ------------
|
||||
# payments
|
||||
# --------
|
||||
|
||||
|
||||
def create_transaction(*, wallet_id: str, payhash: str, amount: str, memo: str) -> Transaction:
|
||||
def create_payment(*, wallet_id: str, payhash: str, amount: str, memo: str, fee: int = 0) -> Payment:
|
||||
with open_db() as db:
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO apipayments (wallet, payhash, amount, pending, memo)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
INSERT INTO apipayments (wallet, payhash, amount, pending, memo, fee)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(wallet_id, payhash, amount, 1, memo),
|
||||
(wallet_id, payhash, amount, 1, memo, fee),
|
||||
)
|
||||
|
||||
return get_wallet_transaction(wallet_id, payhash)
|
||||
return get_wallet_payment(wallet_id, payhash)
|
||||
|
||||
|
||||
def update_transaction_status(payhash: str, pending: bool) -> None:
|
||||
def update_payment_status(payhash: str, pending: bool) -> None:
|
||||
with open_db() as db:
|
||||
db.execute("UPDATE apipayments SET pending = ? WHERE payhash = ?", (int(pending), payhash,))
|
||||
|
||||
|
||||
def check_pending_transactions(wallet_id: str) -> None:
|
||||
pass
|
||||
def delete_payment(payhash: str) -> None:
|
||||
with open_db() as db:
|
||||
db.execute("DELETE FROM apipayments WHERE payhash = ?", (payhash,))
|
||||
|
@ -1,4 +1,3 @@
|
||||
from decimal import Decimal
|
||||
from typing import List, NamedTuple, Optional
|
||||
|
||||
|
||||
@ -24,20 +23,24 @@ class Wallet(NamedTuple):
|
||||
user: str
|
||||
adminkey: str
|
||||
inkey: str
|
||||
balance: Decimal
|
||||
balance_msat: int
|
||||
|
||||
def get_transaction(self, payhash: str) -> "Transaction":
|
||||
from .crud import get_wallet_transaction
|
||||
@property
|
||||
def balance(self) -> int:
|
||||
return int(self.balance / 1000)
|
||||
|
||||
return get_wallet_transaction(self.id, payhash)
|
||||
def get_payment(self, payhash: str) -> "Payment":
|
||||
from .crud import get_wallet_payment
|
||||
|
||||
def get_transactions(self) -> List["Transaction"]:
|
||||
from .crud import get_wallet_transactions
|
||||
return get_wallet_payment(self.id, payhash)
|
||||
|
||||
return get_wallet_transactions(self.id)
|
||||
def get_payments(self, *, include_all_pending: bool = False) -> List["Payment"]:
|
||||
from .crud import get_wallet_payments
|
||||
|
||||
return get_wallet_payments(self.id, include_all_pending=include_all_pending)
|
||||
|
||||
|
||||
class Transaction(NamedTuple):
|
||||
class Payment(NamedTuple):
|
||||
payhash: str
|
||||
pending: bool
|
||||
amount: int
|
||||
@ -54,10 +57,19 @@ class Transaction(NamedTuple):
|
||||
return self.amount / 1000
|
||||
|
||||
@property
|
||||
def tx_type(self) -> str:
|
||||
return "payment" if self.amount < 0 else "invoice"
|
||||
def is_in(self) -> bool:
|
||||
return self.amount > 0
|
||||
|
||||
@property
|
||||
def is_out(self) -> bool:
|
||||
return self.amount < 0
|
||||
|
||||
def set_pending(self, pending: bool) -> None:
|
||||
from .crud import update_transaction_status
|
||||
from .crud import update_payment_status
|
||||
|
||||
update_transaction_status(self.payhash, pending)
|
||||
update_payment_status(self.payhash, pending)
|
||||
|
||||
def delete(self) -> None:
|
||||
from .crud import delete_payment
|
||||
|
||||
delete_payment(self.payhash)
|
||||
|
@ -1,29 +1,36 @@
|
||||
Vue.component(VueQrcode.name, VueQrcode);
|
||||
|
||||
|
||||
function generateChart(canvas, transactions) {
|
||||
function generateChart(canvas, payments) {
|
||||
var txs = [];
|
||||
var n = 0;
|
||||
var data = {
|
||||
labels: [],
|
||||
sats: [],
|
||||
income: [],
|
||||
outcome: [],
|
||||
cumulative: []
|
||||
};
|
||||
|
||||
_.each(transactions.sort(function (a, b) {
|
||||
_.each(payments.slice(0).sort(function (a, b) {
|
||||
return a.time - b.time;
|
||||
}), function (tx) {
|
||||
txs.push({
|
||||
day: Quasar.utils.date.formatDate(tx.date, 'YYYY-MM-DDTHH:00'),
|
||||
hour: Quasar.utils.date.formatDate(tx.date, 'YYYY-MM-DDTHH:00'),
|
||||
sat: tx.sat,
|
||||
});
|
||||
});
|
||||
|
||||
_.each(_.groupBy(txs, 'day'), function (value, day) {
|
||||
var sat = _.reduce(value, function(memo, tx) { return memo + tx.sat; }, 0);
|
||||
n = n + sat;
|
||||
_.each(_.groupBy(txs, 'hour'), function (value, day) {
|
||||
var income = _.reduce(value, function(memo, tx) {
|
||||
return (tx.sat >= 0) ? memo + tx.sat : memo;
|
||||
}, 0);
|
||||
var outcome = _.reduce(value, function(memo, tx) {
|
||||
return (tx.sat < 0) ? memo + Math.abs(tx.sat) : memo;
|
||||
}, 0);
|
||||
n = n + income - outcome;
|
||||
data.labels.push(day);
|
||||
data.sats.push(sat);
|
||||
data.income.push(income);
|
||||
data.outcome.push(outcome);
|
||||
data.cumulative.push(n);
|
||||
});
|
||||
|
||||
@ -36,19 +43,25 @@ function generateChart(canvas, transactions) {
|
||||
data: data.cumulative,
|
||||
type: 'line',
|
||||
label: 'balance',
|
||||
borderColor: '#673ab7', // deep-purple
|
||||
backgroundColor: '#673ab7', // deep-purple
|
||||
borderColor: '#673ab7',
|
||||
borderWidth: 4,
|
||||
pointRadius: 3,
|
||||
fill: false
|
||||
},
|
||||
{
|
||||
data: data.sats,
|
||||
data: data.income,
|
||||
type: 'bar',
|
||||
label: 'tx',
|
||||
backgroundColor: function (ctx) {
|
||||
var value = ctx.dataset.data[ctx.dataIndex];
|
||||
return (value < 0) ? '#e91e63' : '#4caf50'; // pink : green
|
||||
}
|
||||
label: 'in',
|
||||
barPercentage: 0.75,
|
||||
backgroundColor: window.Color('rgb(76,175,80)').alpha(0.5).rgbString() // green
|
||||
},
|
||||
{
|
||||
data: data.outcome,
|
||||
type: 'bar',
|
||||
label: 'out',
|
||||
barPercentage: 0.75,
|
||||
backgroundColor: window.Color('rgb(233,30,99)').alpha(0.5).rgbString() // pink
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -64,12 +77,22 @@ function generateChart(canvas, transactions) {
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
display: true,
|
||||
offset: true,
|
||||
time: {
|
||||
minUnit: 'hour',
|
||||
stepSize: 3
|
||||
}
|
||||
}],
|
||||
},
|
||||
// performance tweaks
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -80,7 +103,6 @@ new Vue({
|
||||
mixins: [windowMixin],
|
||||
data: function () {
|
||||
return {
|
||||
txUpdate: null,
|
||||
receive: {
|
||||
show: false,
|
||||
status: 'pending',
|
||||
@ -97,7 +119,8 @@ new Vue({
|
||||
bolt11: ''
|
||||
}
|
||||
},
|
||||
transactionsTable: {
|
||||
payments: [],
|
||||
paymentsTable: {
|
||||
columns: [
|
||||
{name: 'memo', align: 'left', label: 'Memo', field: 'memo'},
|
||||
{name: 'date', align: 'left', label: 'Date', field: 'date', sortable: true},
|
||||
@ -107,24 +130,38 @@ new Vue({
|
||||
rowsPerPage: 10
|
||||
}
|
||||
},
|
||||
transactionsChart: {
|
||||
paymentsChart: {
|
||||
show: false
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
balance: function () {
|
||||
if (this.payments.length) {
|
||||
return _.pluck(this.payments, 'amount').reduce(function (a, b) { return a + b; }, 0) / 1000;
|
||||
}
|
||||
return this.w.wallet.sat;
|
||||
},
|
||||
fbalance: function () {
|
||||
return LNbits.utils.formatSat(this.balance)
|
||||
},
|
||||
canPay: function () {
|
||||
if (!this.send.invoice) return false;
|
||||
return this.send.invoice.sat < this.w.wallet.balance;
|
||||
return this.send.invoice.sat < this.balance;
|
||||
},
|
||||
transactions: function () {
|
||||
var data = (this.txUpdate) ? this.txUpdate : this.w.transactions;
|
||||
return data.sort(function (a, b) {
|
||||
return b.time - a.time;
|
||||
});
|
||||
pendingPaymentsExist: function () {
|
||||
return (this.payments)
|
||||
? _.where(this.payments, {pending: 1}).length > 0
|
||||
: false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showChart: function () {
|
||||
this.paymentsChart.show = true;
|
||||
this.$nextTick(function () {
|
||||
generateChart(this.$refs.canvas, this.payments);
|
||||
});
|
||||
},
|
||||
showReceiveDialog: function () {
|
||||
this.receive = {
|
||||
show: true,
|
||||
@ -133,7 +170,8 @@ new Vue({
|
||||
data: {
|
||||
amount: null,
|
||||
memo: ''
|
||||
}
|
||||
},
|
||||
paymentChecker: null
|
||||
};
|
||||
},
|
||||
showSendDialog: function () {
|
||||
@ -145,11 +183,11 @@ new Vue({
|
||||
}
|
||||
};
|
||||
},
|
||||
showChart: function () {
|
||||
this.transactionsChart.show = true;
|
||||
this.$nextTick(function () {
|
||||
generateChart(this.$refs.canvas, this.transactions);
|
||||
});
|
||||
closeReceiveDialog: function () {
|
||||
var checker = this.receive.paymentChecker;
|
||||
setTimeout(function () {
|
||||
clearInterval(checker);
|
||||
}, 10000);
|
||||
},
|
||||
createInvoice: function () {
|
||||
var self = this;
|
||||
@ -159,15 +197,15 @@ new Vue({
|
||||
self.receive.status = 'success';
|
||||
self.receive.paymentReq = response.data.payment_request;
|
||||
|
||||
var check_invoice = setInterval(function () {
|
||||
LNbits.api.getInvoice(self.w.wallet, response.data.payment_hash).then(function (response) {
|
||||
self.receive.paymentChecker = setInterval(function () {
|
||||
LNbits.api.getPayment(self.w.wallet, response.data.payment_hash).then(function (response) {
|
||||
if (response.data.paid) {
|
||||
self.refreshTransactions();
|
||||
self.fetchPayments();
|
||||
self.receive.show = false;
|
||||
clearInterval(check_invoice);
|
||||
clearInterval(self.receive.paymentChecker);
|
||||
}
|
||||
});
|
||||
}, 3000);
|
||||
}, 2000);
|
||||
|
||||
}).catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error);
|
||||
@ -177,8 +215,14 @@ new Vue({
|
||||
decodeInvoice: function () {
|
||||
try {
|
||||
var invoice = decode(this.send.data.bolt11);
|
||||
} catch (err) {
|
||||
this.$q.notify({type: 'warning', message: err});
|
||||
} catch (error) {
|
||||
this.$q.notify({
|
||||
timeout: 3000,
|
||||
type: 'warning',
|
||||
message: error + '.',
|
||||
caption: '400 BAD REQUEST',
|
||||
icon: null
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -203,19 +247,57 @@ new Vue({
|
||||
this.send.invoice = Object.freeze(cleanInvoice);
|
||||
},
|
||||
payInvoice: function () {
|
||||
alert('pay!');
|
||||
var self = this;
|
||||
|
||||
dismissPaymentMsg = this.$q.notify({
|
||||
timeout: 0,
|
||||
message: 'Processing payment...',
|
||||
icon: null
|
||||
});
|
||||
|
||||
LNbits.api.payInvoice(this.w.wallet, this.send.data.bolt11).catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error);
|
||||
});
|
||||
|
||||
paymentChecker = setInterval(function () {
|
||||
LNbits.api.getPayment(self.w.wallet, self.send.invoice.hash).then(function (response) {
|
||||
if (response.data.paid) {
|
||||
this.send.show = false;
|
||||
clearInterval(paymentChecker);
|
||||
dismissPaymentMsg();
|
||||
self.fetchPayments();
|
||||
}
|
||||
});
|
||||
}, 2000);
|
||||
},
|
||||
deleteWallet: function (walletId, user) {
|
||||
LNbits.href.deleteWallet(walletId, user);
|
||||
},
|
||||
refreshTransactions: function (notify) {
|
||||
fetchPayments: function (checkPending) {
|
||||
var self = this;
|
||||
|
||||
LNbits.api.getTransactions(this.w.wallet).then(function (response) {
|
||||
self.txUpdate = response.data.map(function (obj) {
|
||||
return LNbits.map.transaction(obj);
|
||||
return LNbits.api.getPayments(this.w.wallet, checkPending).then(function (response) {
|
||||
self.payments = response.data.map(function (obj) {
|
||||
return LNbits.map.payment(obj);
|
||||
}).sort(function (a, b) {
|
||||
return b.time - a.time;
|
||||
});
|
||||
});
|
||||
},
|
||||
checkPendingPayments: function () {
|
||||
var dismissMsg = this.$q.notify({
|
||||
timeout: 0,
|
||||
message: 'Checking pending transactions...',
|
||||
icon: null
|
||||
});
|
||||
|
||||
this.fetchPayments(true).then(function () {
|
||||
dismissMsg();
|
||||
});
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
this.fetchPayments();
|
||||
this.checkPendingPayments();
|
||||
}
|
||||
});
|
||||
|
@ -4,9 +4,9 @@
|
||||
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js"></script>
|
||||
{{ window_vars(user, wallet) }}
|
||||
{% assets filters='rjsmin', output='__bundle__/core/chart.js',
|
||||
'vendor/moment@2.24.0/moment.min.js',
|
||||
'vendor/chart.js@2.9.3/chart.min.js' %}
|
||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
||||
{% endassets %}
|
||||
@ -24,7 +24,7 @@
|
||||
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<h4 class="q-my-none"><strong>{% raw %}{{ w.wallet.fsat }}{% endraw %}</strong> sat</h4>
|
||||
<h3 class="q-my-none"><strong>{% raw %}{{ fbalance }}{% endraw %}</strong> sat</h3>
|
||||
</q-card-section>
|
||||
<div class="row q-pb-md q-px-md q-col-gutter-md">
|
||||
<div class="col">
|
||||
@ -50,16 +50,19 @@
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn flat color="grey" onclick="exportbut()">Export to CSV</q-btn>
|
||||
<!--<q-btn v-if="pendingPaymentsExist" dense flat round icon="update" color="grey" @click="checkPendingPayments">
|
||||
<q-tooltip>Check pending</q-tooltip>
|
||||
</q-btn>-->
|
||||
<q-btn dense flat round icon="show_chart" color="grey" @click="showChart">
|
||||
<q-tooltip>Show chart</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<q-table dense flat
|
||||
:data="transactions"
|
||||
:data="payments"
|
||||
row-key="payhash"
|
||||
:columns="transactionsTable.columns"
|
||||
:pagination.sync="transactionsTable.pagination">
|
||||
:columns="paymentsTable.columns"
|
||||
:pagination.sync="paymentsTable.pagination">
|
||||
{% raw %}
|
||||
<template v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
@ -71,11 +74,14 @@
|
||||
</q-tr>
|
||||
</template>
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-tr :props="props" v-if="props.row.isPaid">
|
||||
<q-td auto-width class="lnbits__q-table__icon-td">
|
||||
<q-icon size="14px"
|
||||
<q-icon v-if="props.row.isPaid" size="14px"
|
||||
:name="(props.row.sat < 0) ? 'call_made' : 'call_received'"
|
||||
:color="(props.row.sat < 0) ? 'pink' : 'green'"></q-icon>
|
||||
<q-icon v-else name="settings_ethernet" color="grey">
|
||||
<q-tooltip>Pending</q-tooltip>
|
||||
</q-icon>
|
||||
</q-td>
|
||||
<q-td key="memo" :props="props">
|
||||
{{ props.row.memo }}
|
||||
@ -169,8 +175,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-dialog v-model="receive.show" position="top">
|
||||
<q-card class="q-pa-md" style="width: 500px">
|
||||
<q-dialog v-model="receive.show" position="top" @hide="closeReceiveDialog">
|
||||
<q-card class="q-pa-lg" style="width: 500px">
|
||||
<q-form v-if="!receive.paymentReq" class="q-gutter-md">
|
||||
<q-input filled dense
|
||||
v-model.number="receive.data.amount"
|
||||
@ -208,7 +214,7 @@
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="send.show" position="top">
|
||||
<q-card class="q-pa-md" style="width: 500px">
|
||||
<q-card class="q-pa-lg" style="width: 500px">
|
||||
<q-form v-if="!send.invoice" class="q-gutter-md">
|
||||
<q-input filled dense
|
||||
v-model="send.data.bolt11"
|
||||
@ -232,6 +238,7 @@
|
||||
<div v-else>
|
||||
{% raw %}
|
||||
<h6 class="q-my-none">{{ send.invoice.fsat }} sat</h6>
|
||||
<q-separator class="q-my-sm"></q-separator>
|
||||
<p style="word-break: break-all">
|
||||
<strong>Memo:</strong> {{ send.invoice.description }}<br>
|
||||
<strong>Expire date:</strong> {{ send.invoice.expireDate }}<br>
|
||||
@ -252,8 +259,8 @@
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="transactionsChart.show" position="top">
|
||||
<q-card class="q-pa-md" style="width: 800px; max-width: unset">
|
||||
<q-dialog v-model="paymentsChart.show" position="top">
|
||||
<q-card class="q-pa-sm" style="width: 800px; max-width: unset">
|
||||
<q-card-section>
|
||||
<canvas ref="canvas" width="600" height="400"></canvas>
|
||||
</q-card-section>
|
||||
|
@ -79,13 +79,15 @@ def wallet():
|
||||
@check_user_exists()
|
||||
def deletewallet():
|
||||
wallet_id = request.args.get("wal", type=str)
|
||||
user_wallet_ids = g.user.wallet_ids
|
||||
|
||||
if wallet_id not in g.user.wallet_ids:
|
||||
if wallet_id not in user_wallet_ids:
|
||||
abort(Status.FORBIDDEN, "Not your wallet.")
|
||||
else:
|
||||
delete_wallet(user_id=g.user.id, wallet_id=wallet_id)
|
||||
user_wallet_ids.remove(wallet_id)
|
||||
|
||||
if g.user.wallets:
|
||||
return redirect(url_for("core.wallet", usr=g.user.id, wal=g.user.wallets[0].id))
|
||||
if user_wallet_ids:
|
||||
return redirect(url_for("core.wallet", usr=g.user.id, wal=user_wallet_ids[0]))
|
||||
|
||||
return redirect(url_for("core.home"))
|
||||
|
@ -1,17 +1,30 @@
|
||||
from flask import g, jsonify
|
||||
from flask import g, jsonify, request
|
||||
|
||||
from lnbits import bolt11
|
||||
from lnbits.core import core_app
|
||||
from lnbits.decorators import api_check_wallet_macaroon, api_validate_post_request
|
||||
from lnbits.helpers import Status
|
||||
from lnbits.settings import WALLET
|
||||
from lnbits.settings import FEE_RESERVE, WALLET
|
||||
|
||||
from .crud import create_transaction
|
||||
from .crud import create_payment
|
||||
|
||||
|
||||
@core_app.route("/api/v1/invoices", methods=["POST"])
|
||||
@api_validate_post_request(required_params=["amount", "memo"])
|
||||
@core_app.route("/api/v1/payments", methods=["GET"])
|
||||
@api_check_wallet_macaroon(key_type="invoice")
|
||||
def api_invoices():
|
||||
def api_payments():
|
||||
if "check_pending" in request.args:
|
||||
for payment in g.wallet.get_payments(include_all_pending=True):
|
||||
if payment.is_out:
|
||||
payment.set_pending(WALLET.get_payment_status(payment.payhash).pending)
|
||||
elif payment.is_in:
|
||||
payment.set_pending(WALLET.get_invoice_status(payment.payhash).pending)
|
||||
|
||||
return jsonify(g.wallet.get_payments()), Status.OK
|
||||
|
||||
|
||||
@api_check_wallet_macaroon(key_type="invoice")
|
||||
@api_validate_post_request(required_params=["amount", "memo"])
|
||||
def api_payments_create_invoice():
|
||||
if not isinstance(g.data["amount"], int) or g.data["amount"] < 1:
|
||||
return jsonify({"message": "`amount` needs to be a positive integer."}), Status.BAD_REQUEST
|
||||
|
||||
@ -25,38 +38,77 @@ def api_invoices():
|
||||
server_error = True
|
||||
|
||||
if server_error:
|
||||
return jsonify({"message": "Unexpected backend error. Try again later."}), 500
|
||||
return jsonify({"message": "Unexpected backend error. Try again later."}), Status.INTERNAL_SERVER_ERROR
|
||||
|
||||
amount_msat = g.data["amount"] * 1000
|
||||
create_transaction(wallet_id=g.wallet.id, payhash=payhash, amount=amount_msat, memo=g.data["memo"])
|
||||
create_payment(wallet_id=g.wallet.id, payhash=payhash, amount=amount_msat, memo=g.data["memo"])
|
||||
|
||||
return jsonify({"payment_request": payment_request, "payment_hash": payhash}), Status.CREATED
|
||||
|
||||
|
||||
@core_app.route("/api/v1/invoices/<payhash>", defaults={"incoming": True}, methods=["GET"])
|
||||
@core_app.route("/api/v1/payments/<payhash>", defaults={"incoming": False}, methods=["GET"])
|
||||
@api_check_wallet_macaroon(key_type="invoice")
|
||||
def api_transaction(payhash, incoming):
|
||||
tx = g.wallet.get_transaction(payhash)
|
||||
@api_validate_post_request(required_params=["bolt11"])
|
||||
def api_payments_pay_invoice():
|
||||
if not isinstance(g.data["bolt11"], str) or not g.data["bolt11"].strip():
|
||||
return jsonify({"message": "`bolt11` needs to be a valid string."}), Status.BAD_REQUEST
|
||||
|
||||
if not tx:
|
||||
return jsonify({"message": "Transaction does not exist."}), Status.NOT_FOUND
|
||||
elif not tx.pending:
|
||||
try:
|
||||
invoice = bolt11.decode(g.data["bolt11"])
|
||||
|
||||
if invoice.amount_msat == 0:
|
||||
return jsonify({"message": "Amountless invoices not supported."}), Status.BAD_REQUEST
|
||||
|
||||
if invoice.amount_msat > g.wallet.balance_msat:
|
||||
return jsonify({"message": "Insufficient balance."}), Status.FORBIDDEN
|
||||
|
||||
create_payment(
|
||||
wallet_id=g.wallet.id,
|
||||
payhash=invoice.payment_hash,
|
||||
amount=-invoice.amount_msat,
|
||||
memo=invoice.description,
|
||||
fee=-invoice.amount_msat * FEE_RESERVE,
|
||||
)
|
||||
|
||||
r, server_error, fee_msat, error_message = WALLET.pay_invoice(g.data["bolt11"])
|
||||
|
||||
except Exception as e:
|
||||
server_error = True
|
||||
error_message = str(e)
|
||||
|
||||
if server_error:
|
||||
return jsonify({"message": error_message}), Status.INTERNAL_SERVER_ERROR
|
||||
|
||||
return jsonify({"payment_hash": invoice.payment_hash}), Status.CREATED
|
||||
|
||||
|
||||
@core_app.route("/api/v1/payments", methods=["POST"])
|
||||
@api_validate_post_request(required_params=["out"])
|
||||
def api_payments_create():
|
||||
if g.data["out"] is True:
|
||||
return api_payments_pay_invoice()
|
||||
return api_payments_create_invoice()
|
||||
|
||||
|
||||
@core_app.route("/api/v1/payments/<payhash>", methods=["GET"])
|
||||
@api_check_wallet_macaroon(key_type="invoice")
|
||||
def api_payment(payhash):
|
||||
payment = g.wallet.get_payment(payhash)
|
||||
|
||||
if not payment:
|
||||
return jsonify({"message": "Payment does not exist."}), Status.NOT_FOUND
|
||||
elif not payment.pending:
|
||||
return jsonify({"paid": True}), Status.OK
|
||||
|
||||
try:
|
||||
is_settled = WALLET.get_invoice_status(payhash).settled
|
||||
if payment.is_out:
|
||||
is_paid = WALLET.get_payment_status(payhash).paid
|
||||
elif payment.is_in:
|
||||
is_paid = WALLET.get_invoice_status(payhash).paid
|
||||
except Exception:
|
||||
return jsonify({"paid": False}), Status.OK
|
||||
|
||||
if is_settled is True:
|
||||
tx.set_pending(False)
|
||||
if is_paid is True:
|
||||
payment.set_pending(False)
|
||||
return jsonify({"paid": True}), Status.OK
|
||||
|
||||
return jsonify({"paid": False}), Status.OK
|
||||
|
||||
|
||||
@core_app.route("/api/v1/transactions", methods=["GET"])
|
||||
@api_check_wallet_macaroon(key_type="invoice")
|
||||
def api_transactions():
|
||||
return jsonify(g.wallet.get_transactions()), Status.OK
|
||||
|
@ -47,8 +47,9 @@ class Status:
|
||||
PAYMENT_REQUIRED = 402
|
||||
FORBIDDEN = 403
|
||||
NOT_FOUND = 404
|
||||
TOO_MANY_REQUESTS = 429
|
||||
METHOD_NOT_ALLOWED = 405
|
||||
TOO_MANY_REQUESTS = 429
|
||||
INTERNAL_SERVER_ERROR = 500
|
||||
|
||||
|
||||
class MegaEncoder(json.JSONEncoder):
|
||||
|
@ -13,22 +13,27 @@ var LNbits = {
|
||||
});
|
||||
},
|
||||
createInvoice: function (wallet, amount, memo) {
|
||||
return this.request('post', '/api/v1/invoices', wallet.inkey, {
|
||||
return this.request('post', '/api/v1/payments', wallet.inkey, {
|
||||
out: false,
|
||||
amount: amount,
|
||||
memo: memo
|
||||
});
|
||||
},
|
||||
getInvoice: function (wallet, payhash) {
|
||||
return this.request('get', '/api/v1/invoices/' + payhash, wallet.inkey);
|
||||
payInvoice: function (wallet, bolt11) {
|
||||
return this.request('post', '/api/v1/payments', wallet.inkey, {
|
||||
out: true,
|
||||
bolt11: bolt11
|
||||
});
|
||||
},
|
||||
getTransactions: function (wallet) {
|
||||
return this.request('get', '/api/v1/transactions', wallet.inkey);
|
||||
getPayments: function (wallet, checkPending) {
|
||||
var query_param = (checkPending) ? '?check_pending' : '';
|
||||
return this.request('get', ['/api/v1/payments', query_param].join(''), wallet.inkey);
|
||||
},
|
||||
getPayment: function (wallet, payhash) {
|
||||
return this.request('get', '/api/v1/payments/' + payhash, wallet.inkey);
|
||||
}
|
||||
},
|
||||
href: {
|
||||
openWallet: function (wallet) {
|
||||
window.location.href = '/wallet?usr=' + wallet.user + '&wal=' + wallet.id;
|
||||
},
|
||||
createWallet: function (walletName, userId) {
|
||||
window.location.href = '/wallet?' + (userId ? 'usr=' + userId + '&' : '') + 'nme=' + walletName;
|
||||
},
|
||||
@ -42,14 +47,6 @@ var LNbits = {
|
||||
obj.url = ['/', obj.code, '/'].join('');
|
||||
return obj;
|
||||
},
|
||||
transaction: function (data) {
|
||||
var obj = _.object(['payhash', 'pending', 'amount', 'fee', 'memo', 'time'], data);
|
||||
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm')
|
||||
obj.msat = obj.amount;
|
||||
obj.sat = obj.msat / 1000;
|
||||
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.sat);
|
||||
return obj;
|
||||
},
|
||||
user: function (data) {
|
||||
var obj = _.object(['id', 'email', 'extensions', 'wallets'], data);
|
||||
var mapWallet = this.wallet;
|
||||
@ -62,10 +59,22 @@ var LNbits = {
|
||||
},
|
||||
wallet: function (data) {
|
||||
var obj = _.object(['id', 'name', 'user', 'adminkey', 'inkey', 'balance'], data);
|
||||
obj.sat = Math.round(obj.balance);
|
||||
obj.msat = obj.balance;
|
||||
obj.sat = Math.round(obj.balance / 1000);
|
||||
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.sat);
|
||||
obj.url = ['/wallet?usr=', obj.user, '&wal=', obj.id].join('');
|
||||
return obj;
|
||||
},
|
||||
payment: function (data) {
|
||||
var obj = _.object(['payhash', 'pending', 'amount', 'fee', 'memo', 'time'], data);
|
||||
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm')
|
||||
obj.msat = obj.amount;
|
||||
obj.sat = obj.msat / 1000;
|
||||
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.sat);
|
||||
obj.isIn = obj.amount > 0;
|
||||
obj.isOut = obj.amount < 0;
|
||||
obj.isPaid = obj.pending == 0;
|
||||
return obj;
|
||||
}
|
||||
},
|
||||
utils: {
|
||||
@ -79,11 +88,10 @@ var LNbits = {
|
||||
500: 'negative'
|
||||
}
|
||||
Quasar.plugins.Notify.create({
|
||||
progress: true,
|
||||
timeout: 3000,
|
||||
type: types[error.response.status] || 'warning',
|
||||
message: error.response.data.message || null,
|
||||
caption: [error.response.status, ' ', error.response.statusText].join('') || null,
|
||||
caption: [error.response.status, ' ', error.response.statusText].join('').toUpperCase() || null,
|
||||
icon: null
|
||||
});
|
||||
}
|
||||
@ -98,7 +106,7 @@ var windowMixin = {
|
||||
extensions: [],
|
||||
user: null,
|
||||
wallet: null,
|
||||
transactions: [],
|
||||
payments: [],
|
||||
}
|
||||
};
|
||||
},
|
||||
@ -122,11 +130,6 @@ var windowMixin = {
|
||||
if (window.wallet) {
|
||||
this.w.wallet = Object.freeze(LNbits.map.wallet(window.wallet));
|
||||
}
|
||||
if (window.transactions) {
|
||||
this.w.transactions = window.transactions.map(function (data) {
|
||||
return LNbits.map.transaction(data);
|
||||
});
|
||||
}
|
||||
if (window.extensions) {
|
||||
var user = this.w.user;
|
||||
this.w.extensions = Object.freeze(window.extensions.map(function (data) {
|
||||
|
1
lnbits/static/vendor/moment@2.24.0/moment.min.js
vendored
Normal file
1
lnbits/static/vendor/moment@2.24.0/moment.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -6,7 +6,6 @@
|
||||
{% endif %}
|
||||
{% if wallet %}
|
||||
window.wallet = {{ wallet | tojson | safe }};
|
||||
window.transactions = {{ wallet.get_transactions() | tojson | safe }};
|
||||
{% endif %}
|
||||
</script>
|
||||
{%- endmacro %}
|
||||
|
@ -13,11 +13,16 @@ class PaymentResponse(NamedTuple):
|
||||
raw_response: Response
|
||||
failed: bool = False
|
||||
fee_msat: int = 0
|
||||
error_message: Optional[str] = None
|
||||
|
||||
|
||||
class TxStatus(NamedTuple):
|
||||
class PaymentStatus(NamedTuple):
|
||||
raw_response: Response
|
||||
settled: Optional[bool] = None
|
||||
paid: Optional[bool] = None
|
||||
|
||||
@property
|
||||
def pending(self) -> bool:
|
||||
return self.paid is not True
|
||||
|
||||
|
||||
class Wallet(ABC):
|
||||
@ -26,13 +31,13 @@ class Wallet(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def pay_invoice(self, bolt11: str) -> Response:
|
||||
def pay_invoice(self, bolt11: str) -> PaymentResponse:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_invoice_status(self, payment_hash: str) -> TxStatus:
|
||||
def get_invoice_status(self, payment_hash: str) -> PaymentStatus:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_payment_status(self, payment_hash: str) -> TxStatus:
|
||||
def get_payment_status(self, payment_hash: str) -> PaymentStatus:
|
||||
pass
|
||||
|
@ -1,5 +1,5 @@
|
||||
from requests import get, post
|
||||
from .base import InvoiceResponse, PaymentResponse, TxStatus, Wallet
|
||||
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
|
||||
|
||||
|
||||
class LndWallet(Wallet):
|
||||
@ -41,15 +41,15 @@ class LndWallet(Wallet):
|
||||
)
|
||||
return PaymentResponse(r, not r.ok)
|
||||
|
||||
def get_invoice_status(self, payment_hash: str) -> TxStatus:
|
||||
def get_invoice_status(self, payment_hash: str) -> PaymentStatus:
|
||||
r = get(url=f"{self.endpoint}/v1/invoice/{payment_hash}", headers=self.auth_read, verify=False)
|
||||
|
||||
if not r.ok or "settled" not in r.json():
|
||||
return TxStatus(r, None)
|
||||
return PaymentStatus(r, None)
|
||||
|
||||
return TxStatus(r, r.json()["settled"])
|
||||
return PaymentStatus(r, r.json()["settled"])
|
||||
|
||||
def get_payment_status(self, payment_hash: str) -> TxStatus:
|
||||
def get_payment_status(self, payment_hash: str) -> PaymentStatus:
|
||||
r = get(
|
||||
url=f"{self.endpoint}/v1/payments",
|
||||
headers=self.auth_admin,
|
||||
@ -58,11 +58,11 @@ class LndWallet(Wallet):
|
||||
)
|
||||
|
||||
if not r.ok:
|
||||
return TxStatus(r, None)
|
||||
return PaymentStatus(r, None)
|
||||
|
||||
payments = [p for p in r.json()["payments"] if p["payment_hash"] == payment_hash]
|
||||
payment = payments[0] if payments else None
|
||||
|
||||
# check payment.status: https://api.lightning.community/rest/index.html?python#peersynctype
|
||||
statuses = {"UNKNOWN": None, "IN_FLIGHT": None, "SUCCEEDED": True, "FAILED": False}
|
||||
return TxStatus(r, statuses[payment["status"]] if payment else None)
|
||||
return PaymentStatus(r, statuses[payment["status"]] if payment else None)
|
||||
|
@ -1,6 +1,6 @@
|
||||
from requests import get, post
|
||||
|
||||
from .base import InvoiceResponse, PaymentResponse, TxStatus, Wallet
|
||||
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
|
||||
|
||||
|
||||
class LNPayWallet(Wallet):
|
||||
@ -37,20 +37,14 @@ class LNPayWallet(Wallet):
|
||||
|
||||
return PaymentResponse(r, not r.ok)
|
||||
|
||||
def get_invoice_status(self, payment_hash: str) -> TxStatus:
|
||||
def get_invoice_status(self, payment_hash: str) -> PaymentStatus:
|
||||
return self.get_payment_status(payment_hash)
|
||||
|
||||
def get_payment_status(self, payment_hash: str) -> PaymentStatus:
|
||||
r = get(url=f"{self.endpoint}/user/lntx/{payment_hash}", headers=self.auth_api)
|
||||
|
||||
if not r.ok:
|
||||
return TxStatus(r, None)
|
||||
return PaymentStatus(r, None)
|
||||
|
||||
statuses = {0: None, 1: True, -1: False}
|
||||
return TxStatus(r, statuses[r.json()["settled"]])
|
||||
|
||||
def get_payment_status(self, payment_hash: str) -> TxStatus:
|
||||
r = get(url=f"{self.endpoint}/user/lntx/{payment_hash}", headers=self.auth_api)
|
||||
|
||||
if not r.ok:
|
||||
return TxStatus(r, None)
|
||||
|
||||
statuses = {0: None, 1: True, -1: False}
|
||||
return TxStatus(r, statuses[r.json()["settled"]])
|
||||
return PaymentStatus(r, statuses[r.json()["settled"]])
|
||||
|
@ -1,6 +1,6 @@
|
||||
from requests import post
|
||||
|
||||
from .base import InvoiceResponse, PaymentResponse, TxStatus, Wallet
|
||||
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
|
||||
|
||||
|
||||
class LntxbotWallet(Wallet):
|
||||
@ -23,38 +23,39 @@ class LntxbotWallet(Wallet):
|
||||
|
||||
def pay_invoice(self, bolt11: str) -> PaymentResponse:
|
||||
r = post(url=f"{self.endpoint}/payinvoice", headers=self.auth_admin, json={"invoice": bolt11})
|
||||
failed, fee_msat = not r.ok, 0
|
||||
failed, fee_msat, error_message = not r.ok, 0, None
|
||||
|
||||
if r.ok:
|
||||
data = r.json()
|
||||
if "error" in data and data["error"]:
|
||||
failed = True
|
||||
error_message = data["message"]
|
||||
elif "fee_msat" in data:
|
||||
fee_msat = data["fee_msat"]
|
||||
|
||||
return PaymentResponse(r, failed, fee_msat)
|
||||
return PaymentResponse(r, failed, fee_msat, error_message)
|
||||
|
||||
def get_invoice_status(self, payment_hash: str) -> TxStatus:
|
||||
def get_invoice_status(self, payment_hash: str) -> PaymentStatus:
|
||||
r = post(url=f"{self.endpoint}/invoicestatus/{payment_hash}?wait=false", headers=self.auth_invoice)
|
||||
|
||||
if not r.ok:
|
||||
return TxStatus(r, None)
|
||||
return PaymentStatus(r, None)
|
||||
|
||||
data = r.json()
|
||||
|
||||
if "error" in data:
|
||||
return TxStatus(r, None)
|
||||
return PaymentStatus(r, None)
|
||||
|
||||
if "preimage" not in data or not data["preimage"]:
|
||||
return TxStatus(r, False)
|
||||
return PaymentStatus(r, False)
|
||||
|
||||
return TxStatus(r, True)
|
||||
return PaymentStatus(r, True)
|
||||
|
||||
def get_payment_status(self, payment_hash: str) -> TxStatus:
|
||||
def get_payment_status(self, payment_hash: str) -> PaymentStatus:
|
||||
r = post(url=f"{self.endpoint}/paymentstatus/{payment_hash}", headers=self.auth_invoice)
|
||||
|
||||
if not r.ok or "error" in r.json():
|
||||
return TxStatus(r, None)
|
||||
return PaymentStatus(r, None)
|
||||
|
||||
statuses = {"complete": True, "failed": False, "unknown": None}
|
||||
return TxStatus(r, statuses[r.json().get("status", "unknown")])
|
||||
statuses = {"complete": True, "failed": False, "pending": None, "unknown": None}
|
||||
return PaymentStatus(r, statuses[r.json().get("status", "unknown")])
|
||||
|
@ -1,6 +1,6 @@
|
||||
from requests import get, post
|
||||
|
||||
from .base import InvoiceResponse, PaymentResponse, TxStatus, Wallet
|
||||
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
|
||||
|
||||
|
||||
class OpenNodeWallet(Wallet):
|
||||
@ -28,20 +28,20 @@ class OpenNodeWallet(Wallet):
|
||||
r = post(url=f"{self.endpoint}/v2/withdrawals", headers=self.auth_admin, json={"type": "ln", "address": bolt11})
|
||||
return PaymentResponse(r, not r.ok)
|
||||
|
||||
def get_invoice_status(self, payment_hash: str) -> TxStatus:
|
||||
def get_invoice_status(self, payment_hash: str) -> PaymentStatus:
|
||||
r = get(url=f"{self.endpoint}/v1/charge/{payment_hash}", headers=self.auth_invoice)
|
||||
|
||||
if not r.ok:
|
||||
return TxStatus(r, None)
|
||||
return PaymentStatus(r, None)
|
||||
|
||||
statuses = {"processing": None, "paid": True, "unpaid": False}
|
||||
return TxStatus(r, statuses[r.json()["data"]["status"]])
|
||||
return PaymentStatus(r, statuses[r.json()["data"]["status"]])
|
||||
|
||||
def get_payment_status(self, payment_hash: str) -> TxStatus:
|
||||
def get_payment_status(self, payment_hash: str) -> PaymentStatus:
|
||||
r = get(url=f"{self.endpoint}/v1/withdrawal/{payment_hash}", headers=self.auth_admin)
|
||||
|
||||
if not r.ok:
|
||||
return TxStatus(r, None)
|
||||
return PaymentStatus(r, None)
|
||||
|
||||
statuses = {"pending": None, "confirmed": True, "error": False, "failed": False}
|
||||
return TxStatus(r, statuses[r.json()["data"]["status"]])
|
||||
return PaymentStatus(r, statuses[r.json()["data"]["status"]])
|
||||
|
Loading…
Reference in New Issue
Block a user