From 6c2b312c92f0e67acefb0ec1192befb064a87a0f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 28 Apr 2025 12:28:27 +0300 Subject: [PATCH] fix: webhook call on invoice pay (#3119) --- lnbits/core/crud/payments.py | 2 +- lnbits/core/models/payments.py | 2 +- lnbits/core/services/notifications.py | 24 +++++++++++++----------- tests/unit/test_pay_invoice.py | 11 ++++++++++- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/lnbits/core/crud/payments.py b/lnbits/core/crud/payments.py index 20e0e63e..0b6e61fa 100644 --- a/lnbits/core/crud/payments.py +++ b/lnbits/core/crud/payments.py @@ -508,7 +508,7 @@ async def is_internal_status_success( return payment.status == PaymentState.SUCCESS.value -async def mark_webhook_sent(payment_hash: str, status: int) -> None: +async def mark_webhook_sent(payment_hash: str, status: str) -> None: await db.execute( """ UPDATE apipayments SET webhook_status = :status diff --git a/lnbits/core/models/payments.py b/lnbits/core/models/payments.py index 122f72aa..6bacbe15 100644 --- a/lnbits/core/models/payments.py +++ b/lnbits/core/models/payments.py @@ -64,7 +64,7 @@ class Payment(BaseModel): memo: str | None = None expiry: datetime | None = None webhook: str | None = None - webhook_status: int | None = None + webhook_status: str | None = None preimage: str | None = None tag: str | None = None extension: str | None = None diff --git a/lnbits/core/services/notifications.py b/lnbits/core/services/notifications.py index a9798e42..cb587e29 100644 --- a/lnbits/core/services/notifications.py +++ b/lnbits/core/services/notifications.py @@ -205,24 +205,23 @@ async def dispatch_webhook(payment: Payment): logger.debug("sending webhook", payment.webhook) if not payment.webhook: - return await mark_webhook_sent(payment.payment_hash, -1) + return await mark_webhook_sent(payment.payment_hash, "-1") headers = {"User-Agent": settings.user_agent} async with httpx.AsyncClient(headers=headers) as client: - data = payment.dict() try: check_callback_url(payment.webhook) - r = await client.post(payment.webhook, json=data, timeout=40) + r = await client.post(payment.webhook, json=payment.json(), timeout=40) r.raise_for_status() - await mark_webhook_sent(payment.payment_hash, r.status_code) + await mark_webhook_sent(payment.payment_hash, str(r.status_code)) except httpx.HTTPStatusError as exc: - await mark_webhook_sent(payment.payment_hash, exc.response.status_code) + await mark_webhook_sent(payment.payment_hash, str(exc.response.status_code)) logger.warning( f"webhook returned a bad status_code: {exc.response.status_code} " f"while requesting {exc.request.url!r}." ) except httpx.RequestError: - await mark_webhook_sent(payment.payment_hash, -1) + await mark_webhook_sent(payment.payment_hash, "-1") logger.warning(f"Could not send webhook to {payment.webhook}") @@ -230,18 +229,21 @@ async def send_payment_notification(wallet: Wallet, payment: Payment): try: await send_ws_payment_notification(wallet, payment) except Exception as e: - logger.error("Error sending websocket payment notification", e) + logger.error(f"Error sending websocket payment notification {e!s}") try: send_chat_payment_notification(wallet, payment) except Exception as e: - logger.error("Error sending chat payment notification", e) + logger.error(f"Error sending chat payment notification {e!s}") try: await send_payment_push_notification(wallet, payment) except Exception as e: - logger.error("Error sending push payment notification", e) + logger.error(f"Error sending push payment notification {e!s}") - if payment.webhook and not payment.webhook_status: - await dispatch_webhook(payment) + try: + if payment.webhook and not payment.webhook_status: + await dispatch_webhook(payment) + except Exception as e: + logger.error(f"Error dispatching webhook: {e!s}") async def send_ws_payment_notification(wallet: Wallet, payment: Payment): diff --git a/tests/unit/test_pay_invoice.py b/tests/unit/test_pay_invoice.py index 461543c0..774824cf 100644 --- a/tests/unit/test_pay_invoice.py +++ b/tests/unit/test_pay_invoice.py @@ -9,6 +9,7 @@ from bolt11.types import MilliSatoshi from pytest_mock.plugin import MockerFixture from lnbits.core.crud import get_standalone_payment, get_wallet +from lnbits.core.crud.payments import get_payment from lnbits.core.models import Payment, PaymentState, Wallet from lnbits.core.services import create_invoice, pay_invoice from lnbits.exceptions import InvoiceError, PaymentError @@ -179,7 +180,12 @@ async def test_notification_for_internal_payment(to_wallet: Wallet): invoice_queue: asyncio.Queue = asyncio.Queue() register_invoice_listener(invoice_queue, test_name) - payment = await create_invoice(wallet_id=to_wallet.id, amount=123, memo=test_name) + payment = await create_invoice( + wallet_id=to_wallet.id, + amount=123, + memo=test_name, + webhook="http://test.404.lnbits.com", + ) await pay_invoice( wallet_id=to_wallet.id, payment_request=payment.bolt11, extra={"tag": "lnurlp"} ) @@ -192,6 +198,9 @@ async def test_notification_for_internal_payment(to_wallet: Wallet): assert _payment.status == PaymentState.SUCCESS.value assert _payment.bolt11 == payment.bolt11 assert _payment.amount == 123_000 + updated_payment = await get_payment(_payment.checking_id) + assert updated_payment.webhook_status == "404" + break # we found our payment, success