refactor: "payments" is the name, and API updates
This commit is contained in:
+29
-23
@@ -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,))
|
||||
|
||||
+25
-13
@@ -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)
|
||||
|
||||
+124
-42
@@ -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"))
|
||||
|
||||
+76
-24
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user