mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-01-19 05:33:47 +01:00
finish webhooks for normal invoices with two extra columns.
This commit is contained in:
parent
4623220316
commit
1c922a5ddc
@ -253,13 +253,14 @@ async def create_payment(
|
||||
preimage: Optional[str] = None,
|
||||
pending: bool = True,
|
||||
extra: Optional[Dict] = None,
|
||||
webhook: Optional[str] = None,
|
||||
) -> Payment:
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT INTO apipayments
|
||||
(wallet, checking_id, bolt11, hash, preimage,
|
||||
amount, pending, memo, fee, extra)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
amount, pending, memo, fee, extra, webhook)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
wallet_id,
|
||||
@ -272,6 +273,7 @@ async def create_payment(
|
||||
memo,
|
||||
fee,
|
||||
json.dumps(extra) if extra and extra != {} and type(extra) is dict else None,
|
||||
webhook,
|
||||
),
|
||||
)
|
||||
|
||||
@ -283,11 +285,7 @@ async def create_payment(
|
||||
|
||||
async def update_payment_status(checking_id: str, pending: bool) -> None:
|
||||
await db.execute(
|
||||
"UPDATE apipayments SET pending = ? WHERE checking_id = ?",
|
||||
(
|
||||
int(pending),
|
||||
checking_id,
|
||||
),
|
||||
"UPDATE apipayments SET pending = ? WHERE checking_id = ?", (int(pending), checking_id,),
|
||||
)
|
||||
|
||||
|
||||
|
@ -120,3 +120,13 @@ async def m002_add_fields_to_apipayments(db):
|
||||
# catching errors like this won't be necessary in anymore now that we
|
||||
# keep track of db versions so no migration ever runs twice.
|
||||
pass
|
||||
|
||||
|
||||
async def m003_add_invoice_webhook(db):
|
||||
"""
|
||||
Special column for webhook endpoints that can be assigned
|
||||
to each different invoice.
|
||||
"""
|
||||
|
||||
await db.execute("ALTER TABLE apipayments ADD COLUMN webhook TEXT")
|
||||
await db.execute("ALTER TABLE apipayments ADD COLUMN webhook_status TEXT")
|
||||
|
@ -40,11 +40,7 @@ class Wallet(NamedTuple):
|
||||
hashing_key = hashlib.sha256(self.id.encode("utf-8")).digest()
|
||||
linking_key = hmac.digest(hashing_key, domain.encode("utf-8"), "sha256")
|
||||
|
||||
return SigningKey.from_string(
|
||||
linking_key,
|
||||
curve=SECP256k1,
|
||||
hashfunc=hashlib.sha256,
|
||||
)
|
||||
return SigningKey.from_string(linking_key, curve=SECP256k1, hashfunc=hashlib.sha256,)
|
||||
|
||||
async def get_payment(self, payment_hash: str) -> Optional["Payment"]:
|
||||
from .crud import get_wallet_payment
|
||||
@ -84,6 +80,8 @@ class Payment(NamedTuple):
|
||||
payment_hash: str
|
||||
extra: Dict
|
||||
wallet_id: str
|
||||
webhook: str
|
||||
webhook_status: int
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row):
|
||||
@ -99,6 +97,8 @@ class Payment(NamedTuple):
|
||||
memo=row["memo"],
|
||||
time=row["time"],
|
||||
wallet_id=row["wallet"],
|
||||
webhook=row["webhook"],
|
||||
webhook_status=row["webhook_status"],
|
||||
)
|
||||
|
||||
@property
|
||||
|
@ -28,6 +28,7 @@ async def create_invoice(
|
||||
memo: str,
|
||||
description_hash: Optional[bytes] = None,
|
||||
extra: Optional[Dict] = None,
|
||||
webhook: Optional[str] = None,
|
||||
) -> Tuple[str, str]:
|
||||
await db.begin()
|
||||
invoice_memo = None if description_hash else memo
|
||||
@ -50,6 +51,7 @@ async def create_invoice(
|
||||
amount=amount_msat,
|
||||
memo=storeable_memo,
|
||||
extra=extra,
|
||||
webhook=webhook,
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
@ -131,10 +133,7 @@ async def pay_invoice(
|
||||
payment: PaymentResponse = WALLET.pay_invoice(payment_request)
|
||||
if payment.ok and payment.checking_id:
|
||||
await create_payment(
|
||||
checking_id=payment.checking_id,
|
||||
fee=payment.fee_msat,
|
||||
preimage=payment.preimage,
|
||||
**payment_kwargs,
|
||||
checking_id=payment.checking_id, fee=payment.fee_msat, preimage=payment.preimage, **payment_kwargs,
|
||||
)
|
||||
await delete_payment(temp_id)
|
||||
else:
|
||||
@ -154,8 +153,7 @@ async def redeem_lnurl_withdraw(wallet_id: str, res: LnurlWithdrawResponse, memo
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
await client.get(
|
||||
res.callback.base,
|
||||
params={**res.callback.query_params, **{"k1": res.k1, "pr": payment_request}},
|
||||
res.callback.base, params={**res.callback.query_params, **{"k1": res.k1, "pr": payment_request}},
|
||||
)
|
||||
|
||||
|
||||
@ -212,11 +210,7 @@ async def perform_lnurlauth(callback: str) -> Optional[LnurlErrorResponse]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(
|
||||
callback,
|
||||
params={
|
||||
"k1": k1.hex(),
|
||||
"key": key.verifying_key.to_string("compressed").hex(),
|
||||
"sig": sig.hex(),
|
||||
},
|
||||
params={"k1": k1.hex(), "key": key.verifying_key.to_string("compressed").hex(), "sig": sig.hex(),},
|
||||
)
|
||||
try:
|
||||
resp = json.loads(r.text)
|
||||
@ -225,9 +219,7 @@ async def perform_lnurlauth(callback: str) -> Optional[LnurlErrorResponse]:
|
||||
|
||||
return LnurlErrorResponse(reason=resp["reason"])
|
||||
except (KeyError, json.decoder.JSONDecodeError):
|
||||
return LnurlErrorResponse(
|
||||
reason=r.text[:200] + "..." if len(r.text) > 200 else r.text,
|
||||
)
|
||||
return LnurlErrorResponse(reason=r.text[:200] + "..." if len(r.text) > 200 else r.text,)
|
||||
|
||||
|
||||
async def check_invoice_status(wallet_id: str, payment_hash: str) -> PaymentStatus:
|
||||
|
@ -3,6 +3,8 @@ import httpx
|
||||
from typing import List
|
||||
|
||||
from lnbits.tasks import register_invoice_listener
|
||||
from . import db
|
||||
from .models import Payment
|
||||
|
||||
sse_listeners: List[trio.MemorySendChannel] = []
|
||||
|
||||
@ -16,17 +18,37 @@ async def register_listeners():
|
||||
async def wait_for_paid_invoices(invoice_paid_chan: trio.MemoryReceiveChannel):
|
||||
async for payment in invoice_paid_chan:
|
||||
# send information to sse channel
|
||||
for send_channel in sse_listeners:
|
||||
try:
|
||||
send_channel.send_nowait(payment)
|
||||
except trio.WouldBlock:
|
||||
print("removing sse listener", send_channel)
|
||||
sse_listeners.remove(send_channel)
|
||||
await dispatch_sse(payment)
|
||||
|
||||
# dispatch webhook
|
||||
if payment.extra and "webhook" in payment.extra:
|
||||
async with httpx.AsyncClient() as client:
|
||||
try:
|
||||
await client.post(payment.extra["webhook"], json=payment._asdict(), timeout=40)
|
||||
except (httpx.ConnectError, httpx.RequestError):
|
||||
pass
|
||||
if payment.webhook and not payment.webhook_status:
|
||||
await dispatch_webhook(payment)
|
||||
|
||||
|
||||
async def dispatch_sse(payment: Payment):
|
||||
for send_channel in sse_listeners:
|
||||
try:
|
||||
send_channel.send_nowait(payment)
|
||||
except trio.WouldBlock:
|
||||
print("removing sse listener", send_channel)
|
||||
sse_listeners.remove(send_channel)
|
||||
|
||||
|
||||
async def dispatch_webhook(payment: Payment):
|
||||
async with httpx.AsyncClient() as client:
|
||||
data = payment._asdict()
|
||||
try:
|
||||
r = await client.post(payment.webhook, json=data, timeout=40,)
|
||||
await mark_webhook_sent(payment, r.status_code)
|
||||
except (httpx.ConnectError, httpx.RequestError):
|
||||
await mark_webhook_sent(payment, -1)
|
||||
|
||||
|
||||
async def mark_webhook_sent(payment: Payment, status: int) -> None:
|
||||
await db.execute(
|
||||
"""
|
||||
UPDATE apipayments SET webhook_status = ?
|
||||
WHERE hash = ?
|
||||
""",
|
||||
(status, payment.payment_hash),
|
||||
)
|
||||
|
@ -55,8 +55,9 @@
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||
<code
|
||||
>curl -X POST {{ request.url_root }}api/v1/payments -d '{"out": false,
|
||||
"amount": <int>, "memo": <string>}' -H "X-Api-Key:
|
||||
<i>{{ wallet.inkey }}</i>" -H "Content-type: application/json"</code
|
||||
"amount": <int>, "memo": <string>, "webhook":
|
||||
<url:string>}' -H "X-Api-Key: <i>{{ wallet.inkey }}</i>" -H
|
||||
"Content-type: application/json"</code
|
||||
>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
@ -46,6 +46,7 @@ async def api_payments():
|
||||
"description_hash": {"type": "string", "empty": False, "required": True, "excludes": "memo"},
|
||||
"lnurl_callback": {"type": "string", "nullable": True, "required": False},
|
||||
"extra": {"type": "dict", "nullable": True, "required": False},
|
||||
"webhook": {"type": "string", "empty": False, "required": False},
|
||||
}
|
||||
)
|
||||
async def api_payments_create_invoice():
|
||||
@ -62,7 +63,8 @@ async def api_payments_create_invoice():
|
||||
amount=g.data["amount"],
|
||||
memo=memo,
|
||||
description_hash=description_hash,
|
||||
extra=g.data["extra"],
|
||||
extra=g.data.get("extra"),
|
||||
webhook=g.data.get("webhook"),
|
||||
)
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
|
@ -140,7 +140,10 @@ window.LNbits = {
|
||||
'bolt11',
|
||||
'preimage',
|
||||
'payment_hash',
|
||||
'extra'
|
||||
'extra',
|
||||
'wallet_id',
|
||||
'webhook',
|
||||
'webhook_status'
|
||||
],
|
||||
data
|
||||
)
|
||||
|
@ -204,6 +204,15 @@ Vue.component('lnbits-payment-details', {
|
||||
<div class="col-3"><b>Payment hash</b>:</div>
|
||||
<div class="col-9 text-wrap mono">{{ payment.payment_hash }}</div>
|
||||
</div>
|
||||
<div class="row" v-if="payment.webhook">
|
||||
<div class="col-3"><b>Webhook</b>:</div>
|
||||
<div class="col-9 text-wrap mono">
|
||||
{{ payment.webhook }}
|
||||
<q-badge :color="webhookStatusColor" text-color="white">
|
||||
{{ webhookStatusText }}
|
||||
</q-badge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="hasPreimage">
|
||||
<div class="col-3"><b>Payment proof</b>:</div>
|
||||
<div class="col-9 text-wrap mono">{{ payment.preimage }}</div>
|
||||
@ -243,6 +252,19 @@ Vue.component('lnbits-payment-details', {
|
||||
this.payment.extra.success_action
|
||||
)
|
||||
},
|
||||
webhookStatusColor() {
|
||||
return this.payment.webhook_status >= 300 ||
|
||||
this.payment.webhook_status < 0
|
||||
? 'red-10'
|
||||
: !this.payment.webhook_status
|
||||
? 'cyan-7'
|
||||
: 'green-10'
|
||||
},
|
||||
webhookStatusText() {
|
||||
return this.payment.webhook_status
|
||||
? this.payment.webhook_status
|
||||
: 'not sent yet'
|
||||
},
|
||||
hasTag() {
|
||||
return this.payment.extra && !!this.payment.extra.tag
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user