Browse Source

extra fields on apipayments + index payments by payment_hash

aiosqlite
fiatjaf 5 years ago
parent
commit
bf3c44b3c4
  1. 50
      lnbits/core/crud.py
  2. 72
      lnbits/core/migrations.py
  3. 31
      lnbits/core/models.py
  4. 102
      lnbits/core/services.py
  5. 18
      lnbits/core/static/js/wallet.js
  6. 12
      lnbits/core/templates/core/_api_docs.html
  7. 2
      lnbits/core/templates/core/wallet.html
  8. 42
      lnbits/core/views/api.py
  9. 2
      lnbits/core/views/generic.py
  10. 34
      lnbits/extensions/amilk/views_api.py
  11. 24
      lnbits/extensions/events/crud.py
  12. 2
      lnbits/extensions/events/templates/events/display.html
  13. 25
      lnbits/extensions/events/views_api.py
  14. 16
      lnbits/extensions/lnticket/crud.py
  15. 2
      lnbits/extensions/lnticket/templates/lnticket/display.html
  16. 25
      lnbits/extensions/lnticket/views_api.py
  17. 6
      lnbits/extensions/paywall/templates/paywall/_api_docs.html
  18. 2
      lnbits/extensions/paywall/templates/paywall/display.html
  19. 13
      lnbits/extensions/paywall/views_api.py
  20. 2
      lnbits/extensions/tpos/templates/tpos/tpos.html
  21. 18
      lnbits/extensions/tpos/views_api.py
  22. 8
      lnbits/extensions/usermanager/templates/usermanager/_api_docs.html
  23. 3
      lnbits/extensions/withdraw/views_api.py
  24. 20
      lnbits/static/js/base.js

50
lnbits/core/crud.py

@ -1,4 +1,4 @@
from typing import List, Optional from typing import List, Optional, Dict
from uuid import uuid4 from uuid import uuid4
from lnbits.db import open_db from lnbits.db import open_db
@ -136,18 +136,18 @@ def get_wallet_for_key(key: str, key_type: str = "invoice") -> Optional[Wallet]:
# --------------- # ---------------
def get_wallet_payment(wallet_id: str, checking_id: str) -> Optional[Payment]: def get_wallet_payment(wallet_id: str, payment_hash: str) -> Optional[Payment]:
with open_db() as db: with open_db() as db:
row = db.fetchone( row = db.fetchone(
""" """
SELECT id as checking_id, amount, fee, pending, memo, time SELECT *
FROM apipayment FROM apipayments
WHERE wallet = ? AND id = ? WHERE wallet = ? AND hash = ?
""", """,
(wallet_id, checking_id), (wallet_id, payment_hash),
) )
return Payment(**row) if row else None return Payment.from_row(row) if row else None
def get_wallet_payments( def get_wallet_payments(
@ -179,7 +179,7 @@ def get_wallet_payments(
with open_db() as db: with open_db() as db:
rows = db.fetchall( rows = db.fetchall(
f""" f"""
SELECT id as checking_id, amount, fee, pending, memo, time SELECT *
FROM apipayments FROM apipayments
WHERE wallet = ? {clause} WHERE wallet = ? {clause}
ORDER BY time DESC ORDER BY time DESC
@ -187,7 +187,7 @@ def get_wallet_payments(
(wallet_id,), (wallet_id,),
) )
return [Payment(**row) for row in rows] return [Payment.from_row(row) for row in rows]
def delete_wallet_payments_expired(wallet_id: str, *, seconds: int = 86400) -> None: def delete_wallet_payments_expired(wallet_id: str, *, seconds: int = 86400) -> None:
@ -195,7 +195,7 @@ def delete_wallet_payments_expired(wallet_id: str, *, seconds: int = 86400) -> N
db.execute( db.execute(
""" """
DELETE DELETE
FROM apipayment WHERE wallet = ? AND pending = 1 AND time < strftime('%s', 'now') - ? FROM apipayments WHERE wallet = ? AND pending = 1 AND time < strftime('%s', 'now') - ?
""", """,
(wallet_id, seconds), (wallet_id, seconds),
) )
@ -206,18 +206,30 @@ def delete_wallet_payments_expired(wallet_id: str, *, seconds: int = 86400) -> N
def create_payment( def create_payment(
*, wallet_id: str, checking_id: str, payment_hash: str, amount: int, memo: str, fee: int = 0, pending: bool = True *,
wallet_id: str,
checking_id: str,
payment_request: str,
payment_hash: str,
amount: int,
memo: str,
fee: int = 0,
preimage: Optional[str] = None,
pending: bool = True,
extra: Optional[Dict] = None,
) -> Payment: ) -> Payment:
with open_db() as db: with open_db() as db:
db.execute( db.execute(
""" """
INSERT INTO apipayment (wallet, id, payment_hash, amount, pending, memo, fee) INSERT INTO apipayments
VALUES (?, ?, ?, ?, ?, ?, ?) (wallet, checking_id, bolt11, hash, preimage,
amount, pending, memo, fee, extra)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", """,
(wallet_id, checking_id, payment_hash, amount, int(pending), memo, fee), (wallet_id, checking_id, payment_request, payment_hash, preimage, amount, int(pending), memo, fee, extra),
) )
new_payment = get_wallet_payment(wallet_id, checking_id) new_payment = get_wallet_payment(wallet_id, payment_hash)
assert new_payment, "Newly created payment couldn't be retrieved" assert new_payment, "Newly created payment couldn't be retrieved"
return new_payment return new_payment
@ -225,18 +237,18 @@ def create_payment(
def update_payment_status(checking_id: str, pending: bool) -> None: def update_payment_status(checking_id: str, pending: bool) -> None:
with open_db() as db: with open_db() as db:
db.execute("UPDATE apipayment SET pending = ? WHERE id = ?", (int(pending), checking_id,)) db.execute("UPDATE apipayments SET pending = ? WHERE checking_id = ?", (int(pending), checking_id,))
def delete_payment(checking_id: str) -> None: def delete_payment(checking_id: str) -> None:
with open_db() as db: with open_db() as db:
db.execute("DELETE FROM apipayment WHERE id = ?", (checking_id,)) db.execute("DELETE FROM apipayments WHERE checking_id = ?", (checking_id,))
def check_internal(payment_hash: str) -> None: def check_internal(payment_hash: str) -> None:
with open_db() as db: with open_db() as db:
row = db.fetchone("SELECT * FROM apipayment WHERE payment_hash = ?", (payment_hash,)) row = db.fetchone("SELECT checking_id FROM apipayments WHERE hash = ?", (payment_hash,))
if not row: if not row:
return False return False
else: else:
return row['id'] return row["checking_id"]

72
lnbits/core/migrations.py

@ -69,74 +69,22 @@ def m001_initial(db):
GROUP BY wallet; GROUP BY wallet;
""" """
) )
db.execute("DROP VIEW balances")
db.execute(
"""
CREATE VIEW IF NOT EXISTS balances AS
SELECT wallet, COALESCE(SUM(s), 0) AS balance FROM (
SELECT wallet, SUM(amount) AS s -- incoming
FROM apipayment
WHERE amount > 0 AND pending = 0 -- don't sum pending
GROUP BY wallet
UNION ALL
SELECT wallet, SUM(amount + fee) AS s -- outgoing, sum fees
FROM apipayment
WHERE amount < 0 -- do sum pending
GROUP BY wallet
)
GROUP BY wallet;
"""
)
def m002_changed(db):
db.execute( def m002_add_fields_to_apipayments(db):
""" """
CREATE TABLE IF NOT EXISTS apipayment ( Adding fields to apipayments for better accounting,
id TEXT NOT NULL, and renaming payhash to checking_id since that is what it really is.
payment_hash TEXT NOT NULL,
amount INTEGER NOT NULL,
fee INTEGER NOT NULL DEFAULT 0,
wallet TEXT NOT NULL,
pending BOOLEAN NOT NULL,
memo TEXT,
time TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now')),
UNIQUE (wallet, id)
);
""" """
) db.execute("ALTER TABLE apipayments RENAME COLUMN payhash TO checking_id")
db.execute("ALTER TABLE apipayments ADD COLUMN hash TEXT")
db.execute("CREATE INDEX by_hash ON apipayments (hash)")
db.execute("ALTER TABLE apipayments ADD COLUMN preimage TEXT")
db.execute("ALTER TABLE apipayments ADD COLUMN bolt11 TEXT")
db.execute("ALTER TABLE apipayments ADD COLUMN extra TEXT")
for row in [list(row) for row in db.fetchall("SELECT * FROM apipayments")]:
db.execute(
"""
INSERT INTO apipayment (
id,
payment_hash,
amount,
fee,
wallet,
pending,
memo,
time
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
(
row[0],
"oldinvoice",
row[1],
row[2],
row[3],
row[4],
row[5],
row[6],
),
)
db.execute("DROP TABLE apipayments")
def migrate(): def migrate():
with open_db() as db: with open_db() as db:
m001_initial(db) m001_initial(db)
m002_changed(db) m002_add_fields_to_apipayments(db)

31
lnbits/core/models.py

@ -1,4 +1,6 @@
from typing import List, NamedTuple, Optional import json
from typing import List, NamedTuple, Optional, Dict
from sqlite3 import Row
class User(NamedTuple): class User(NamedTuple):
@ -29,10 +31,10 @@ class Wallet(NamedTuple):
def balance(self) -> int: def balance(self) -> int:
return self.balance_msat // 1000 return self.balance_msat // 1000
def get_payment(self, checking_id: str) -> Optional["Payment"]: def get_payment(self, payment_hash: str) -> Optional["Payment"]:
from .crud import get_wallet_payment from .crud import get_wallet_payment
return get_wallet_payment(self.id, checking_id) return get_wallet_payment(self.id, payment_hash)
def get_payments( def get_payments(
self, *, complete: bool = True, pending: bool = False, outgoing: bool = True, incoming: bool = True self, *, complete: bool = True, pending: bool = False, outgoing: bool = True, incoming: bool = True
@ -54,6 +56,29 @@ class Payment(NamedTuple):
fee: int fee: int
memo: str memo: str
time: int time: int
bolt11: str
preimage: str
payment_hash: str
extra: Dict
@classmethod
def from_row(cls, row: Row):
return cls(
checking_id=row["checking_id"],
payment_hash=row["hash"],
bolt11=row["bolt11"],
preimage=row["preimage"],
extra=json.loads(row["extra"] or "{}"),
pending=row["pending"],
amount=row["amount"],
fee=row["fee"],
memo=row["memo"],
time=row["time"],
)
@property
def tag(self) -> Optional[str]:
return self.extra.get("tag")
@property @property
def msat(self) -> int: def msat(self) -> int:

102
lnbits/core/services.py

@ -1,14 +1,20 @@
from typing import Optional, Tuple from typing import Optional, Tuple, Dict
from lnbits.bolt11 import decode as bolt11_decode # type: ignore from lnbits import bolt11
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
from lnbits.settings import WALLET from lnbits.settings import WALLET
from .crud import get_wallet, create_payment, delete_payment, check_internal, update_payment_status from .crud import get_wallet, create_payment, delete_payment, check_internal, update_payment_status, get_wallet_payment
def create_invoice(*, wallet_id: str, amount: int, memo: str, description_hash: bytes = None) -> Tuple[str, str]: def create_invoice(
*,
wallet_id: str,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
extra: Optional[Dict] = None,
) -> Tuple[str, str]:
try: try:
ok, checking_id, payment_request, error_message = WALLET.create_invoice( ok, checking_id, payment_request, error_message = WALLET.create_invoice(
amount=amount, memo=memo, description_hash=description_hash amount=amount, memo=memo, description_hash=description_hash
@ -18,77 +24,81 @@ def create_invoice(*, wallet_id: str, amount: int, memo: str, description_hash:
if not ok: if not ok:
raise Exception(error_message or "Unexpected backend error.") raise Exception(error_message or "Unexpected backend error.")
invoice = bolt11_decode(payment_request)
invoice = bolt11.decode(payment_request)
amount_msat = amount * 1000 amount_msat = amount * 1000
create_payment(wallet_id=wallet_id, checking_id=checking_id, payment_hash=invoice.payment_hash, amount=amount_msat, memo=memo) create_payment(
wallet_id=wallet_id,
checking_id=checking_id,
payment_request=payment_request,
payment_hash=invoice.payment_hash,
amount=amount_msat,
memo=memo,
extra=extra,
)
return checking_id, payment_request return invoice.payment_hash, payment_request
def pay_invoice(*, wallet_id: str, bolt11: str, max_sat: Optional[int] = None) -> str: def pay_invoice(
*, wallet_id: str, payment_request: str, max_sat: Optional[int] = None, extra: Optional[Dict] = None
) -> str:
temp_id = f"temp_{urlsafe_short_hash()}" temp_id = f"temp_{urlsafe_short_hash()}"
try: try:
invoice = bolt11_decode(bolt11) invoice = bolt11.decode(payment_request)
internal = check_internal(invoice.payment_hash)
if invoice.amount_msat == 0: if invoice.amount_msat == 0:
raise ValueError("Amountless invoices not supported.") raise ValueError("Amountless invoices not supported.")
if max_sat and invoice.amount_msat > max_sat * 1000: if max_sat and invoice.amount_msat > max_sat * 1000:
raise ValueError("Amount in invoice is too high.") raise ValueError("Amount in invoice is too high.")
fee_reserve = max(1000, int(invoice.amount_msat * 0.01)) # put all parameters that don't change here
payment_kwargs = dict(
if not internal:
create_payment(
wallet_id=wallet_id, wallet_id=wallet_id,
checking_id=temp_id, payment_request=payment_request,
payment_hash=invoice.payment_hash, payment_hash=invoice.payment_hash,
amount=-invoice.amount_msat, amount=-invoice.amount_msat,
fee=-fee_reserve, memo=invoice.description,
memo=temp_id, extra=extra,
) )
# check_internal() returns the checking_id of the invoice we're waiting for
internal = check_internal(invoice.payment_hash)
if internal:
# create a new payment from this wallet
create_payment(checking_id=temp_id, fee=0, pending=False, **payment_kwargs)
else:
# create a temporary payment here so we can check if
# the balance is enough in the next step
fee_reserve = max(1000, int(invoice.amount_msat * 0.01))
create_payment(checking_id=temp_id, fee=-fee_reserve, **payment_kwargs)
# do the balance check
wallet = get_wallet(wallet_id) wallet = get_wallet(wallet_id)
assert wallet, "invalid wallet id" assert wallet, "invalid wallet id"
if wallet.balance_msat < 0: if wallet.balance_msat < 0:
raise PermissionError("Insufficient balance.") raise PermissionError("Insufficient balance.")
if internal: if internal:
create_payment( # mark the invoice from the other side as not pending anymore
wallet_id=wallet_id, # so the other side only has access to his new money when we are sure
checking_id=temp_id, # the payer has enough to deduct from
payment_hash=invoice.payment_hash,
amount=-invoice.amount_msat,
fee=0,
pending=False,
memo=invoice.description,
)
update_payment_status(checking_id=internal, pending=False) update_payment_status(checking_id=internal, pending=False)
return temp_id else:
# actually pay the external invoice
ok, checking_id, fee_msat, error_message = WALLET.pay_invoice(bolt11) ok, checking_id, fee_msat, error_message = WALLET.pay_invoice(payment_request)
if ok: if ok:
create_payment( create_payment(checking_id=checking_id, fee=fee_msat, **payment_kwargs)
wallet_id=wallet_id, delete_payment(temp_id)
checking_id=checking_id,
payment_hash=invoice.payment_hash,
amount=-invoice.amount_msat,
fee=fee_msat,
memo=invoice.description,
)
except Exception as e: except Exception as e:
ok, error_message = False, str(e) ok, error_message = False, str(e)
delete_payment(temp_id)
if not ok: if not ok:
raise Exception(error_message or "Unexpected backend error.") raise Exception(error_message or "Unexpected backend error.")
return checking_id return invoice.payment_hash
def check_payment(*, checking_id: str) -> str: def check_invoice_status(wallet_id: str, payment_hash: str) -> str:
pass payment = get_wallet_payment(wallet_id, payment_hash)
return WALLET.get_invoice_status(payment.checking_id)

18
lnbits/core/static/js/wallet.js

@ -1,3 +1,5 @@
/* globals Vue, VueQrcodeReader, VueQrcode, Quasar, LNbits, _ */
Vue.component(VueQrcode.name, VueQrcode) Vue.component(VueQrcode.name, VueQrcode)
Vue.use(VueQrcodeReader) Vue.use(VueQrcodeReader)
@ -65,14 +67,20 @@ function generateChart(canvas, payments) {
type: 'bar', type: 'bar',
label: 'in', label: 'in',
barPercentage: 0.75, barPercentage: 0.75,
backgroundColor: window.Color('rgb(76,175,80)').alpha(0.5).rgbString() // green backgroundColor: window
.Color('rgb(76,175,80)')
.alpha(0.5)
.rgbString() // green
}, },
{ {
data: data.outcome, data: data.outcome,
type: 'bar', type: 'bar',
label: 'out', label: 'out',
barPercentage: 0.75, barPercentage: 0.75,
backgroundColor: window.Color('rgb(233,30,99)').alpha(0.5).rgbString() // pink backgroundColor: window
.Color('rgb(233,30,99)')
.alpha(0.5)
.rgbString() // pink
} }
] ]
}, },
@ -261,7 +269,7 @@ new Vue({
self.receive.paymentChecker = setInterval(function() { self.receive.paymentChecker = setInterval(function() {
LNbits.api LNbits.api
.getPayment(self.g.wallet, response.data.checking_id) .getPayment(self.g.wallet, response.data.payment_hash)
.then(function(response) { .then(function(response) {
if (response.data.paid) { if (response.data.paid) {
self.fetchPayments() self.fetchPayments()
@ -330,7 +338,7 @@ new Vue({
payInvoice: function() { payInvoice: function() {
var self = this var self = this
dismissPaymentMsg = this.$q.notify({ let dismissPaymentMsg = this.$q.notify({
timeout: 0, timeout: 0,
message: 'Processing payment...', message: 'Processing payment...',
icon: null icon: null
@ -341,7 +349,7 @@ new Vue({
.then(function(response) { .then(function(response) {
self.send.paymentChecker = setInterval(function() { self.send.paymentChecker = setInterval(function() {
LNbits.api LNbits.api
.getPayment(self.g.wallet, response.data.checking_id) .getPayment(self.g.wallet, response.data.payment_hash)
.then(function(res) { .then(function(res) {
if (res.data.paid) { if (res.data.paid) {
self.send.show = false self.send.show = false

12
lnbits/core/templates/core/_api_docs.html

@ -23,7 +23,7 @@
Returns 201 CREATED (application/json) Returns 201 CREATED (application/json)
</h5> </h5>
<code <code
>{"checking_id": &lt;string&gt;, "payment_request": >{"payment_hash": &lt;string&gt;, "payment_request":
&lt;string&gt;}</code &lt;string&gt;}</code
> >
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> <h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
@ -51,7 +51,7 @@
<h5 class="text-caption q-mt-sm q-mb-none"> <h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json) Returns 201 CREATED (application/json)
</h5> </h5>
<code>{"checking_id": &lt;string&gt;}</code> <code>{"payment_hash": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> <h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code <code
>curl -X POST {{ request.url_root }}api/v1/payments -d '{"out": true, >curl -X POST {{ request.url_root }}api/v1/payments -d '{"out": true,
@ -73,7 +73,7 @@
<q-card-section> <q-card-section>
<code <code
><span class="text-light-blue">GET</span> ><span class="text-light-blue">GET</span>
/api/v1/payments/&lt;checking_id&gt;</code /api/v1/payments/&lt;payment_hash&gt;</code
> >
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5> <h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": "{{ wallet.inkey }}"}</code> <code>{"X-Api-Key": "{{ wallet.inkey }}"}</code>
@ -83,9 +83,9 @@
<code>{"paid": &lt;bool&gt;}</code> <code>{"paid": &lt;bool&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> <h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code <code
>curl -X GET {{ request.url_root }}api/v1/payments/&lt;checking_id&gt; >curl -X GET {{ request.url_root
-H "X-Api-Key: <i>{{ wallet.inkey }}"</i> -H "Content-type: }}api/v1/payments/&lt;payment_hash&gt; -H "X-Api-Key:
application/json"</code <i>{{ wallet.inkey }}"</i> -H "Content-type: application/json"</code
> >
</q-card-section> </q-card-section>
</q-card> </q-card>

2
lnbits/core/templates/core/wallet.html

@ -84,7 +84,7 @@
dense dense
flat flat
:data="filteredPayments" :data="filteredPayments"
row-key="payhash" row-key="checking_id"
:columns="paymentsTable.columns" :columns="paymentsTable.columns"
:pagination.sync="paymentsTable.pagination" :pagination.sync="paymentsTable.pagination"
> >

42
lnbits/core/views/api.py

@ -2,12 +2,12 @@ from flask import g, jsonify, request
from http import HTTPStatus from http import HTTPStatus
from binascii import unhexlify from binascii import unhexlify
from lnbits import bolt11
from lnbits.core import core_app from lnbits.core import core_app
from lnbits.core.services import create_invoice, pay_invoice
from lnbits.decorators import api_check_wallet_key, api_validate_post_request from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from lnbits.settings import WALLET from lnbits.settings import WALLET
from ..services import create_invoice, pay_invoice
@core_app.route("/api/v1/payments", methods=["GET"]) @core_app.route("/api/v1/payments", methods=["GET"])
@api_check_wallet_key("invoice") @api_check_wallet_key("invoice")
@ -41,20 +41,31 @@ def api_payments_create_invoice():
memo = g.data["memo"] memo = g.data["memo"]
try: try:
checking_id, payment_request = create_invoice( payment_hash, payment_request = create_invoice(
wallet_id=g.wallet.id, amount=g.data["amount"], memo=memo, description_hash=description_hash wallet_id=g.wallet.id, amount=g.data["amount"], memo=memo, description_hash=description_hash
) )
except Exception as e: except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
return jsonify({"checking_id": checking_id, "payment_request": payment_request}), HTTPStatus.CREATED invoice = bolt11.decode(payment_request)
return (
jsonify(
{
"payment_hash": invoice.payment_hash,
"payment_request": payment_request,
# maintain backwards compatibility with API clients:
"checking_id": invoice.payment_hash,
}
),
HTTPStatus.CREATED,
)
@api_check_wallet_key("admin") @api_check_wallet_key("admin")
@api_validate_post_request(schema={"bolt11": {"type": "string", "empty": False, "required": True}}) @api_validate_post_request(schema={"bolt11": {"type": "string", "empty": False, "required": True}})
def api_payments_pay_invoice(): def api_payments_pay_invoice():
try: try:
checking_id = pay_invoice(wallet_id=g.wallet.id, bolt11=g.data["bolt11"]) payment_hash = pay_invoice(wallet_id=g.wallet.id, payment_request=g.data["bolt11"])
except ValueError as e: except ValueError as e:
return jsonify({"message": str(e)}), HTTPStatus.BAD_REQUEST return jsonify({"message": str(e)}), HTTPStatus.BAD_REQUEST
except PermissionError as e: except PermissionError as e:
@ -62,7 +73,16 @@ def api_payments_pay_invoice():
except Exception as e: except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
return jsonify({"checking_id": checking_id}), HTTPStatus.CREATED return (
jsonify(
{
"payment_hash": payment_hash,
# maintain backwards compatibility with API clients:
"checking_id": payment_hash,
}
),
HTTPStatus.CREATED,
)
@core_app.route("/api/v1/payments", methods=["POST"]) @core_app.route("/api/v1/payments", methods=["POST"])
@ -73,10 +93,10 @@ def api_payments_create():
return api_payments_create_invoice() return api_payments_create_invoice()
@core_app.route("/api/v1/payments/<checking_id>", methods=["GET"]) @core_app.route("/api/v1/payments/<payment_hash>", methods=["GET"])
@api_check_wallet_key("invoice") @api_check_wallet_key("invoice")
def api_payment(checking_id): def api_payment(payment_hash):
payment = g.wallet.get_payment(checking_id) payment = g.wallet.get_payment(payment_hash)
if not payment: if not payment:
return jsonify({"message": "Payment does not exist."}), HTTPStatus.NOT_FOUND return jsonify({"message": "Payment does not exist."}), HTTPStatus.NOT_FOUND
@ -85,9 +105,9 @@ def api_payment(checking_id):
try: try:
if payment.is_out: if payment.is_out:
is_paid = not WALLET.get_payment_status(checking_id).pending is_paid = not WALLET.get_payment_status(payment.checking_id).pending
elif payment.is_in: elif payment.is_in:
is_paid = not WALLET.get_invoice_status(checking_id).pending is_paid = not WALLET.get_invoice_status(payment.checking_id).pending
except Exception: except Exception:
return jsonify({"paid": False}), HTTPStatus.OK return jsonify({"paid": False}), HTTPStatus.OK

2
lnbits/core/views/generic.py

@ -64,7 +64,7 @@ def wallet():
allowed_users = getenv("LNBITS_ALLOWED_USERS", "all") allowed_users = getenv("LNBITS_ALLOWED_USERS", "all")
if allowed_users != "all" and user_id not in allowed_users.split(","): if allowed_users != "all" and user_id not in allowed_users.split(","):
abort(HTTPStatus.UNAUTHORIZED, f"User not authorized.") abort(HTTPStatus.UNAUTHORIZED, "User not authorized.")
if not wallet_id: if not wallet_id:
if user.wallets and not wallet_name: if user.wallets and not wallet_name:

34
lnbits/extensions/amilk/views_api.py

@ -1,19 +1,16 @@
from flask import g, jsonify, request import requests
from flask import g, jsonify, request, abort
from http import HTTPStatus from http import HTTPStatus
from lnurl import LnurlWithdrawResponse, handle as handle_lnurl
from lnurl.exceptions import LnurlException
from time import sleep
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
from lnbits.decorators import api_check_wallet_key, api_validate_post_request from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from lnbits.core.services import create_invoice, check_invoice_status
from lnbits.extensions.amilk import amilk_ext from lnbits.extensions.amilk import amilk_ext
from .crud import create_amilk, get_amilk, get_amilks, delete_amilk from .crud import create_amilk, get_amilk, get_amilks, delete_amilk
from lnbits.core.services import create_invoice
from flask import abort, redirect, request, url_for
from lnurl import LnurlWithdrawResponse, handle as handle_lnurl
from lnurl.exceptions import LnurlException
from time import sleep
import requests
from lnbits.settings import WALLET
@amilk_ext.route("/api/v1/amilk", methods=["GET"]) @amilk_ext.route("/api/v1/amilk", methods=["GET"])
@ -36,13 +33,8 @@ def api_amilkit(amilk_id):
withdraw_res = handle_lnurl(milk.lnurl, response_class=LnurlWithdrawResponse) withdraw_res = handle_lnurl(milk.lnurl, response_class=LnurlWithdrawResponse)
except LnurlException: except LnurlException:
abort(HTTPStatus.INTERNAL_SERVER_ERROR, "Could not process withdraw LNURL.") abort(HTTPStatus.INTERNAL_SERVER_ERROR, "Could not process withdraw LNURL.")
print(withdraw_res.max_sats)
try: payment_hash, payment_request = create_invoice(wallet_id=milk.wallet, amount=withdraw_res.max_sats, memo=memo)
checking_id, payment_request = create_invoice(wallet_id=milk.wallet, amount=withdraw_res.max_sats, memo=memo)
# print(payment_request)
except Exception as e:
error_message = False, str(e)
r = requests.get( r = requests.get(
withdraw_res.callback.base, withdraw_res.callback.base,
@ -50,19 +42,17 @@ def api_amilkit(amilk_id):
) )
if not r.ok: if not r.ok:
abort(HTTPStatus.INTERNAL_SERVER_ERROR, "Could not process withdraw LNURL.") abort(HTTPStatus.INTERNAL_SERVER_ERROR, "Could not process withdraw LNURL.")
for i in range(10): for i in range(10):
invoice_status = WALLET.get_invoice_status(checking_id)
sleep(i) sleep(i)
if not invoice_status.paid: invoice_status = check_invoice_status(milk.wallet, payment_hash)
continue if invoice_status.paid:
return jsonify({"paid": True}), HTTPStatus.OK
else: else:
return jsonify({"paid": False}), HTTPStatus.OK continue
break
return jsonify({"paid": True}), HTTPStatus.OK return jsonify({"paid": False}), HTTPStatus.OK
@amilk_ext.route("/api/v1/amilk", methods=["POST"]) @amilk_ext.route("/api/v1/amilk", methods=["POST"])

24
lnbits/extensions/events/crud.py

@ -9,31 +9,31 @@ from .models import Tickets, Events
#######TICKETS######## #######TICKETS########
def create_ticket(checking_id: str, wallet: str, event: str, name: str, email: str) -> Tickets: def create_ticket(payment_hash: str, wallet: str, event: str, name: str, email: str) -> Tickets:
with open_ext_db("events") as db: with open_ext_db("events") as db:
db.execute( db.execute(
""" """
INSERT INTO ticket (id, wallet, event, name, email, registered, paid) INSERT INTO ticket (id, wallet, event, name, email, registered, paid)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
""", """,
(checking_id, wallet, event, name, email, False, False), (payment_hash, wallet, event, name, email, False, False),
) )
return get_ticket(checking_id) return get_ticket(payment_hash)
def update_ticket(paid: bool, checking_id: str) -> Tickets: def update_ticket(paid: bool, payment_hash: str) -> Tickets:
with open_ext_db("events") as db: with open_ext_db("events") as db:
row = db.fetchone("SELECT * FROM ticket WHERE id = ?", (checking_id,)) row = db.fetchone("SELECT * FROM ticket WHERE id = ?", (payment_hash,))
if row[6] == True: if row[6] == True:
return get_ticket(checking_id) return get_ticket(payment_hash)
db.execute( db.execute(
""" """
UPDATE ticket UPDATE ticket
SET paid = ? SET paid = ?
WHERE id = ? WHERE id = ?
""", """,
(paid, checking_id), (paid, payment_hash),
) )
eventdata = get_event(row[2]) eventdata = get_event(row[2])
@ -47,12 +47,12 @@ def update_ticket(paid: bool, checking_id: str) -> Tickets:
""", """,
(sold, amount_tickets, row[2]), (sold, amount_tickets, row[2]),
) )
return get_ticket(checking_id) return get_ticket(payment_hash)
def get_ticket(checking_id: str) -> Optional[Tickets]: def get_ticket(payment_hash: str) -> Optional[Tickets]:
with open_ext_db("events") as db: with open_ext_db("events") as db:
row = db.fetchone("SELECT * FROM ticket WHERE id = ?", (checking_id,)) row = db.fetchone("SELECT * FROM ticket WHERE id = ?", (payment_hash,))
return Tickets(**row) if row else None return Tickets(**row) if row else None
@ -68,9 +68,9 @@ def get_tickets(wallet_ids: Union[str, List[str]]) -> List[Tickets]:
return [Tickets(**row) for row in rows] return [Tickets(**row) for row in rows]
def delete_ticket(checking_id: str) -> None: def delete_ticket(payment_hash: str) -> None:
with open_ext_db("events") as db: with open_ext_db("events") as db:
db.execute("DELETE FROM ticket WHERE id = ?", (checking_id,)) db.execute("DELETE FROM ticket WHERE id = ?", (payment_hash,))
########EVENTS######### ########EVENTS#########

2
lnbits/extensions/events/templates/events/display.html

@ -144,7 +144,7 @@
) )
.then(function (response) { .then(function (response) {
self.paymentReq = response.data.payment_request self.paymentReq = response.data.payment_request
self.paymentCheck = response.data.checking_id self.paymentCheck = response.data.payment_hash
dismissMsg = self.$q.notify({ dismissMsg = self.$q.notify({
timeout: 0, timeout: 0,

25
lnbits/extensions/events/views_api.py

@ -2,9 +2,8 @@ from flask import g, jsonify, request
from http import HTTPStatus from http import HTTPStatus
from lnbits.core.crud import get_user, get_wallet from lnbits.core.crud import get_user, get_wallet
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice, check_invoice_status
from lnbits.decorators import api_check_wallet_key, api_validate_post_request from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from lnbits.settings import WALLET
from lnbits.extensions.events import events_ext from lnbits.extensions.events import events_ext
from .crud import ( from .crud import (
@ -108,39 +107,37 @@ def api_tickets():
} }
) )
def api_ticket_make_ticket(event_id, sats): def api_ticket_make_ticket(event_id, sats):
event = get_event(event_id) event = get_event(event_id)
if not event: if not event:
return jsonify({"message": "LNTicket does not exist."}), HTTPStatus.NOT_FOUND return jsonify({"message": "LNTicket does not exist."}), HTTPStatus.NOT_FOUND
try: try:
checking_id, payment_request = create_invoice( payment_hash, payment_request = create_invoice(
wallet_id=event.wallet, amount=int(sats), memo=f"#lnticket {event_id}" wallet_id=event.wallet, amount=int(sats), memo=f"#lnticket {event_id}"
) )
except Exception as e: except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
ticket = create_ticket(checking_id=checking_id, wallet=event.wallet, event=event_id, **g.data) ticket = create_ticket(payment_hash=payment_hash, wallet=event.wallet, event=event_id, **g.data)
if not ticket: if not ticket:
return jsonify({"message": "LNTicket could not be fetched."}), HTTPStatus.NOT_FOUND return jsonify({"message": "LNTicket could not be fetched."}), HTTPStatus.NOT_FOUND
return jsonify({"checking_id": checking_id, "payment_request": payment_request}), HTTPStatus.OK return jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.OK
@events_ext.route("/api/v1/tickets/<checking_id>", methods=["GET"]) @events_ext.route("/api/v1/tickets/<payment_hash>", methods=["GET"])
def api_ticket_send_ticket(checking_id): def api_ticket_send_ticket(payment_hash):
theticket = get_ticket(checking_id) ticket = get_ticket(payment_hash)
try: try:
is_paid = not WALLET.get_invoice_status(checking_id).pending is_paid = not check_invoice_status(ticket.wallet, payment_hash).pending
except Exception: except Exception:
return jsonify({"message": "Not paid."}), HTTPStatus.NOT_FOUND return jsonify({"message": "Not paid."}), HTTPStatus.NOT_FOUND
if is_paid: if is_paid:
wallet = get_wallet(theticket.wallet) wallet = get_wallet(ticket.wallet)
payment = wallet.get_payment(checking_id) payment = wallet.get_payment(payment_hash)
payment.set_pending(False) payment.set_pending(False)
ticket = update_ticket(paid=True, checking_id=checking_id) ticket = update_ticket(paid=True, payment_hash=payment_hash)
return jsonify({"paid": True, "ticket_id": ticket.id}), HTTPStatus.OK return jsonify({"paid": True, "ticket_id": ticket.id}), HTTPStatus.OK

16
lnbits/extensions/lnticket/crud.py

@ -9,31 +9,31 @@ from .models import Tickets, Forms
#######TICKETS######## #######TICKETS########
def create_ticket(checking_id: str, wallet: str, form: str, name: str, email: str, ltext: str, sats: int) -> Tickets: def create_ticket(payment_hash: str, wallet: str, form: str, name: str, email: str, ltext: str, sats: int) -> Tickets:
with open_ext_db("lnticket") as db: with open_ext_db("lnticket") as db:
db.execute( db.execute(
""" """
INSERT INTO ticket (id, form, email, ltext, name, wallet, sats, paid) INSERT INTO ticket (id, form, email, ltext, name, wallet, sats, paid)
VALUES (?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", """,
(checking_id, form, email, ltext, name, wallet, sats, False), (payment_hash, form, email, ltext, name, wallet, sats, False),
) )
return get_ticket(checking_id) return get_ticket(payment_hash)
def update_ticket(paid: bool, checking_id: str) -> Tickets: def update_ticket(paid: bool, payment_hash: str) -> Tickets:
with open_ext_db("lnticket") as db: with open_ext_db("lnticket") as db:
row = db.fetchone("SELECT * FROM ticket WHERE id = ?", (checking_id,)) row = db.fetchone("SELECT * FROM ticket WHERE id = ?", (payment_hash,))
if row[7] == True: if row[7] == True:
return get_ticket(checking_id) return get_ticket(payment_hash)
db.execute( db.execute(
""" """
UPDATE ticket UPDATE ticket
SET paid = ? SET paid = ?
WHERE id = ? WHERE id = ?
""", """,
(paid, checking_id), (paid, payment_hash),
) )
formdata = get_form(row[1]) formdata = get_form(row[1])
@ -46,7 +46,7 @@ def update_ticket(paid: bool, checking_id: str) -> Tickets:
""", """,
(amount, row[1]), (amount, row[1]),
) )
return get_ticket(checking_id) return get_ticket(payment_hash)
def get_ticket(ticket_id: str) -> Optional[Tickets]: def get_ticket(ticket_id: str) -> Optional[Tickets]:

2
lnbits/extensions/lnticket/templates/lnticket/display.html

@ -152,7 +152,7 @@
) )
.then(function (response) { .then(function (response) {
self.paymentReq = response.data.payment_request self.paymentReq = response.data.payment_request
self.paymentCheck = response.data.checking_id self.paymentCheck = response.data.payment_hash
dismissMsg = self.$q.notify({ dismissMsg = self.$q.notify({
timeout: 0, timeout: 0,

25
lnbits/extensions/lnticket/views_api.py

@ -2,9 +2,8 @@ from flask import g, jsonify, request
from http import HTTPStatus from http import HTTPStatus
from lnbits.core.crud import get_user, get_wallet from lnbits.core.crud import get_user, get_wallet
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice, check_invoice_status
from lnbits.decorators import api_check_wallet_key, api_validate_post_request from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from lnbits.settings import WALLET
from lnbits.extensions.lnticket import lnticket_ext from lnbits.extensions.lnticket import lnticket_ext
from .crud import ( from .crud import (
@ -104,40 +103,38 @@ def api_tickets():
} }
) )
def api_ticket_make_ticket(form_id, sats): def api_ticket_make_ticket(form_id, sats):
event = get_form(form_id) event = get_form(form_id)
if not event: if not event:
return jsonify({"message": "LNTicket does not exist."}), HTTPStatus.NOT_FOUND return jsonify({"message": "LNTicket does not exist."}), HTTPStatus.NOT_FOUND
try: try:
checking_id, payment_request = create_invoice( payment_hash, payment_request = create_invoice(
wallet_id=event.wallet, amount=int(sats), memo=f"#lnticket {form_id}" wallet_id=event.wallet, amount=int(sats), memo=f"#lnticket {form_id}"
) )
except Exception as e: except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
ticket = create_ticket(checking_id=checking_id, wallet=event.wallet, **g.data) ticket = create_ticket(payment_hash=payment_hash, wallet=event.wallet, **g.data)
if not ticket: if not ticket:
return jsonify({"message": "LNTicket could not be fetched."}), HTTPStatus.NOT_FOUND return jsonify({"message": "LNTicket could not be fetched."}), HTTPStatus.NOT_FOUND
return jsonify({"checking_id": checking_id, "payment_request": payment_request}), HTTPStatus.OK return jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.OK
@lnticket_ext.route("/api/v1/tickets/<checking_id>", methods=["GET"]) @lnticket_ext.route("/api/v1/tickets/<payment_hash>", methods=["GET"])
def api_ticket_send_ticket(checking_id): def api_ticket_send_ticket(payment_hash):
theticket = get_ticket(checking_id) ticket = get_ticket(payment_hash)
try: try:
is_paid = not WALLET.get_invoice_status(checking_id).pending is_paid = not check_invoice_status(ticket.wallet, payment_hash).pending
except Exception: except Exception:
return jsonify({"message": "Not paid."}), HTTPStatus.NOT_FOUND return jsonify({"message": "Not paid."}), HTTPStatus.NOT_FOUND
if is_paid: if is_paid:
wallet = get_wallet(theticket.wallet) wallet = get_wallet(ticket.wallet)
payment = wallet.get_payment(checking_id) payment = wallet.get_payment(payment_hash)
payment.set_pending(False) payment.set_pending(False)
ticket = update_ticket(paid=True, checking_id=checking_id) ticket = update_ticket(paid=True, payment_hash=payment_hash)
return jsonify({"paid": True, "ticket_id": ticket.id}), HTTPStatus.OK return jsonify({"paid": True, "ticket_id": ticket.id}), HTTPStatus.OK
return jsonify({"paid": False}), HTTPStatus.OK return jsonify({"paid": False}), HTTPStatus.OK

6
lnbits/extensions/paywall/templates/paywall/_api_docs.html

@ -75,7 +75,7 @@
Returns 201 CREATED (application/json) Returns 201 CREATED (application/json)
</h5> </h5>
<code <code
>{"checking_id": &lt;string&gt;, "payment_request": >{"payment_hash": &lt;string&gt;, "payment_request":
&lt;string&gt;}</code &lt;string&gt;}</code
> >
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> <h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
@ -100,7 +100,7 @@
/paywall/api/v1/paywalls/&lt;paywall_id&gt;/check_invoice</code /paywall/api/v1/paywalls/&lt;paywall_id&gt;/check_invoice</code
> >
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5> <h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code>{"checking_id": &lt;string&gt;}</code> <code>{"payment_hash": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none"> <h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json) Returns 200 OK (application/json)
</h5> </h5>
@ -113,7 +113,7 @@
<code <code
>curl -X POST {{ request.url_root >curl -X POST {{ request.url_root
}}paywall/api/v1/paywalls/&lt;paywall_id&gt;/check_invoice -d }}paywall/api/v1/paywalls/&lt;paywall_id&gt;/check_invoice -d
'{"checking_id": &lt;string&gt;}' -H "Content-type: application/json" '{"payment_hash": &lt;string&gt;}' -H "Content-type: application/json"
</code> </code>
</q-card-section> </q-card-section>
</q-card> </q-card>

2
lnbits/extensions/paywall/templates/paywall/display.html

@ -121,7 +121,7 @@
axios axios
.post( .post(
'/paywall/api/v1/paywalls/{{ paywall.id }}/check_invoice', '/paywall/api/v1/paywalls/{{ paywall.id }}/check_invoice',
{checking_id: response.data.checking_id} {payment_hash: response.data.payment_hash}
) )
.then(function (res) { .then(function (res) {
if (res.data.paid) { if (res.data.paid) {

13
lnbits/extensions/paywall/views_api.py

@ -2,9 +2,8 @@ from flask import g, jsonify, request
from http import HTTPStatus from http import HTTPStatus
from lnbits.core.crud import get_user, get_wallet from lnbits.core.crud import get_user, get_wallet
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice, check_invoice_status
from lnbits.decorators import api_check_wallet_key, api_validate_post_request from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from lnbits.settings import WALLET
from lnbits.extensions.paywall import paywall_ext from lnbits.extensions.paywall import paywall_ext
from .crud import create_paywall, get_paywall, get_paywalls, delete_paywall from .crud import create_paywall, get_paywall, get_paywalls, delete_paywall
@ -64,17 +63,17 @@ def api_paywall_create_invoice(paywall_id):
try: try:
amount = g.data["amount"] if g.data["amount"] > paywall.amount else paywall.amount amount = g.data["amount"] if g.data["amount"] > paywall.amount else paywall.amount
checking_id, payment_request = create_invoice( payment_hash, payment_request = create_invoice(
wallet_id=paywall.wallet, amount=amount, memo=f"#paywall {paywall.memo}" wallet_id=paywall.wallet, amount=amount, memo=f"#paywall {paywall.memo}"
) )
except Exception as e: except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
return jsonify({"checking_id": checking_id, "payment_request": payment_request}), HTTPStatus.CREATED return jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.CREATED
@paywall_ext.route("/api/v1/paywalls/<paywall_id>/check_invoice", methods=["POST"]) @paywall_ext.route("/api/v1/paywalls/<paywall_id>/check_invoice", methods=["POST"])
@api_validate_post_request(schema={"checking_id": {"type": "string", "empty": False, "required": True}}) @api_validate_post_request(schema={"payment_hash": {"type": "string", "empty": False, "required": True}})
def api_paywal_check_invoice(paywall_id): def api_paywal_check_invoice(paywall_id):
paywall = get_paywall(paywall_id) paywall = get_paywall(paywall_id)
@ -82,13 +81,13 @@ def api_paywal_check_invoice(paywall_id):
return jsonify({"message": "Paywall does not exist."}), HTTPStatus.NOT_FOUND return jsonify({"message": "Paywall does not exist."}), HTTPStatus.NOT_FOUND
try: try:
is_paid = not WALLET.get_invoice_status(g.data["checking_id"]).pending is_paid = not check_invoice_status(paywall.wallet, g.data["payment_hash"]).pending
except Exception: except Exception:
return jsonify({"paid": False}), HTTPStatus.OK return jsonify({"paid": False}), HTTPStatus.OK
if is_paid: if is_paid:
wallet = get_wallet(paywall.wallet) wallet = get_wallet(paywall.wallet)
payment = wallet.get_payment(g.data["checking_id"]) payment = wallet.get_payment(g.data["payment_hash"])
payment.set_pending(False) payment.set_pending(False)
return jsonify({"paid": True, "url": paywall.url, "remembers": paywall.remembers}), HTTPStatus.OK return jsonify({"paid": True, "url": paywall.url, "remembers": paywall.remembers}), HTTPStatus.OK

2
lnbits/extensions/tpos/templates/tpos/tpos.html

@ -224,7 +224,7 @@
'/tpos/api/v1/tposs/' + '/tpos/api/v1/tposs/' +
self.tposId + self.tposId +
'/invoices/' + '/invoices/' +
response.data.checking_id response.data.payment_hash
) )
.then(function (res) { .then(function (res) {
if (res.data.paid) { if (res.data.paid) {

18
lnbits/extensions/tpos/views_api.py

@ -2,9 +2,8 @@ from flask import g, jsonify, request
from http import HTTPStatus from http import HTTPStatus
from lnbits.core.crud import get_user, get_wallet from lnbits.core.crud import get_user, get_wallet
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice, check_invoice_status
from lnbits.decorators import api_check_wallet_key, api_validate_post_request from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from lnbits.settings import WALLET
from lnbits.extensions.tpos import tpos_ext from lnbits.extensions.tpos import tpos_ext
from .crud import create_tpos, get_tpos, get_tposs, delete_tpos from .crud import create_tpos, get_tpos, get_tposs, delete_tpos
@ -60,30 +59,31 @@ def api_tpos_create_invoice(tpos_id):
return jsonify({"message": "TPoS does not exist."}), HTTPStatus.NOT_FOUND return jsonify({"message": "TPoS does not exist."}), HTTPStatus.NOT_FOUND
try: try:
checking_id, payment_request = create_invoice( payment_hash, payment_request = create_invoice(
wallet_id=tpos.wallet, amount=g.data["amount"], memo=f"#tpos {tpos.name}" wallet_id=tpos.wallet, amount=g.data["amount"], memo=f"#tpos {tpos.name}"
) )
except Exception as e: except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
return jsonify({"checking_id": checking_id, "payment_request": payment_request}), HTTPStatus.CREATED return jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.CREATED
@tpos_ext.route("/api/v1/tposs/<tpos_id>/invoices/<checking_id>", methods=["GET"]) @tpos_ext.route("/api/v1/tposs/<tpos_id>/invoices/<payment_hash>", methods=["GET"])
def api_tpos_check_invoice(tpos_id, checking_id): def api_tpos_check_invoice(tpos_id, payment_hash):
tpos = get_tpos(tpos_id) tpos = get_tpos(tpos_id)
if not tpos: if not tpos:
return jsonify({"message": "TPoS does not exist."}), HTTPStatus.NOT_FOUND return jsonify({"message": "TPoS does not exist."}), HTTPStatus.NOT_FOUND
try: try:
is_paid = not WALLET.get_invoice_status(checking_id).pending is_paid = not check_invoice_status(tpos.wallet, payment_hash).pending
except Exception: except Exception as exc:
print(exc)
return jsonify({"paid": False}), HTTPStatus.OK return jsonify({"paid": False}), HTTPStatus.OK
if is_paid: if is_paid:
wallet = get_wallet(tpos.wallet) wallet = get_wallet(tpos.wallet)
payment = wallet.get_payment(checking_id) payment = wallet.get_payment(payment_hash)
payment.set_pending(False) payment.set_pending(False)
return jsonify({"paid": True}), HTTPStatus.OK return jsonify({"paid": True}), HTTPStatus.OK

8
lnbits/extensions/usermanager/templates/usermanager/_api_docs.html

@ -122,7 +122,8 @@
Returns 201 CREATED (application/json) Returns 201 CREATED (application/json)
</h5> </h5>
<code <code
>{"checking_id": &lt;string&gt;,"payment_request": >{"id": &lt;string&gt;, "name": &lt;string&gt;, "admin":
&lt;string&gt;, "email": &lt;string&gt;, "password":
&lt;string&gt;}</code &lt;string&gt;}</code
> >
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> <h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
@ -158,8 +159,9 @@
Returns 201 CREATED (application/json) Returns 201 CREATED (application/json)
</h5> </h5>
<code <code
>{"checking_id": &lt;string&gt;,"payment_request": >{"id": &lt;string&gt;, "admin": &lt;string&gt;, "name":
&lt;string&gt;}</code &lt;string&gt;, "user": &lt;string&gt;, "adminkey": &lt;string&gt;,
"inkey": &lt;string&gt;}</code
> >
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> <h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code <code

3
lnbits/extensions/withdraw/views_api.py

@ -182,14 +182,13 @@ def api_lnurl_callback(unique_hash):
return jsonify({"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."}), HTTPStatus.OK return jsonify({"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."}), HTTPStatus.OK
try: try:
pay_invoice(wallet_id=link.wallet, bolt11=payment_request, max_sat=link.max_withdrawable) pay_invoice(wallet_id=link.wallet, payment_request=payment_request, max_sat=link.max_withdrawable)
changes = { changes = {
"open_time": link.wait_time + now, "open_time": link.wait_time + now,
} }
update_withdraw_link(link.id, **changes) update_withdraw_link(link.id, **changes)
except ValueError as e: except ValueError as e:
return jsonify({"status": "ERROR", "reason": str(e)}), HTTPStatus.OK return jsonify({"status": "ERROR", "reason": str(e)}), HTTPStatus.OK
except PermissionError: except PermissionError:

20
lnbits/static/js/base.js

@ -1,3 +1,5 @@
/* globals Vue, EventHub, axios, Quasar, _ */
var LOCALE = 'en' var LOCALE = 'en'
var EventHub = new Vue() var EventHub = new Vue()
@ -35,8 +37,12 @@ var LNbits = {
wallet.inkey wallet.inkey
) )
}, },
getPayment: function (wallet, payhash) { getPayment: function(wallet, paymentHash) {
return this.request('get', '/api/v1/payments/' + payhash, wallet.inkey) return this.request(
'get',
'/api/v1/payments/' + paymentHash,
wallet.inkey
)
} }
}, },
href: { href: {
@ -88,7 +94,7 @@ var LNbits = {
}, },
payment: function(data) { payment: function(data) {
var obj = _.object( var obj = _.object(
['payhash', 'pending', 'amount', 'fee', 'memo', 'time'], ['checking_id', 'pending', 'amount', 'fee', 'memo', 'time'],
data data
) )
obj.date = Quasar.utils.date.formatDate( obj.date = Quasar.utils.date.formatDate(
@ -100,7 +106,7 @@ var LNbits = {
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.sat) obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.sat)
obj.isIn = obj.amount > 0 obj.isIn = obj.amount > 0
obj.isOut = obj.amount < 0 obj.isOut = obj.amount < 0
obj.isPaid = obj.pending == 0 obj.isPaid = obj.pending === 0
obj._q = [obj.memo, obj.sat].join(' ').toLowerCase() obj._q = [obj.memo, obj.sat].join(' ').toLowerCase()
return obj return obj
} }
@ -146,8 +152,6 @@ var LNbits = {
}) })
}, },
search: function(data, q, field, separator) { search: function(data, q, field, separator) {
var field = field || '_q'
try { try {
var queries = q.toLowerCase().split(separator || ' ') var queries = q.toLowerCase().split(separator || ' ')
return data.filter(function(obj) { return data.filter(function(obj) {
@ -155,7 +159,7 @@ var LNbits = {
_.each(queries, function(q) { _.each(queries, function(q) {
if (obj[field].indexOf(q) !== -1) matches++ if (obj[field].indexOf(q) !== -1) matches++
}) })
return matches == queries.length return matches === queries.length
}) })
} catch (err) { } catch (err) {
return data return data
@ -255,7 +259,7 @@ var windowMixin = {
}) })
.map(function(obj) { .map(function(obj) {
if (user) { if (user) {
obj.isEnabled = user.extensions.indexOf(obj.code) != -1 obj.isEnabled = user.extensions.indexOf(obj.code) !== -1
} else { } else {
obj.isEnabled = false obj.isEnabled = false
} }

Loading…
Cancel
Save