diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py
index 1c8c71ad..7d9f2a5b 100644
--- a/lnbits/core/crud.py
+++ b/lnbits/core/crud.py
@@ -451,6 +451,34 @@ async def update_payment_details(
return
+async def update_payment_extra(
+ payment_hash: str,
+ extra: dict,
+ conn: Optional[Connection] = None,
+) -> None:
+ """
+ Only update the `extra` field for the payment.
+ Old values in the `extra` JSON object will be kept unless the new `extra` overwrites them.
+ """
+
+ row = await (conn or db).fetchone(
+ "SELECT hash, extra from apipayments WHERE hash = ?",
+ (payment_hash,),
+ )
+ if not row:
+ return
+ db_extra = json.loads(row["extra"] if row["extra"] else "{}")
+ db_extra.update(extra)
+
+ await (conn or db).execute(
+ """
+ UPDATE apipayments SET extra = ?
+ WHERE hash = ?
+ """,
+ (json.dumps(db_extra), payment_hash),
+ )
+
+
async def delete_payment(checking_id: str, conn: Optional[Connection] = None) -> None:
await (conn or db).execute(
"DELETE FROM apipayments WHERE checking_id = ?", (checking_id,)
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index ebce4b85..2bd19978 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -214,7 +214,8 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
lnurl_response = resp["reason"]
else:
lnurl_response = True
- except (httpx.ConnectError, httpx.RequestError):
+ except (httpx.ConnectError, httpx.RequestError) as ex:
+ logger.error(ex)
lnurl_response = False
return {
diff --git a/lnbits/extensions/lnurlp/tasks.py b/lnbits/extensions/lnurlp/tasks.py
index 5de47f2e..72805603 100644
--- a/lnbits/extensions/lnurlp/tasks.py
+++ b/lnbits/extensions/lnurlp/tasks.py
@@ -5,6 +5,7 @@ import httpx
from loguru import logger
from lnbits.core import db as core_db
+from lnbits.core.crud import update_payment_extra
from lnbits.core.models import Payment
from lnbits.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener
@@ -66,10 +67,4 @@ async def mark_webhook_sent(
payment.extra["wh_message"] = reason_phrase
payment.extra["wh_response"] = text
- await core_db.execute(
- """
- UPDATE apipayments SET extra = ?
- WHERE hash = ?
- """,
- (json.dumps(payment.extra), payment.payment_hash),
- )
+ await update_payment_extra(payment.payment_hash, payment.extra)
diff --git a/lnbits/extensions/withdraw/crud.py b/lnbits/extensions/withdraw/crud.py
index 9868b057..83404c62 100644
--- a/lnbits/extensions/withdraw/crud.py
+++ b/lnbits/extensions/withdraw/crud.py
@@ -27,9 +27,11 @@ async def create_withdraw_link(
open_time,
usescsv,
webhook_url,
+ webhook_headers,
+ webhook_body,
custom_url
)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
link_id,
@@ -45,6 +47,8 @@ async def create_withdraw_link(
int(datetime.now().timestamp()) + data.wait_time,
usescsv,
data.webhook_url,
+ data.webhook_headers,
+ data.webhook_body,
data.custom_url,
),
)
diff --git a/lnbits/extensions/withdraw/lnurl.py b/lnbits/extensions/withdraw/lnurl.py
index 5737e54f..7260df1e 100644
--- a/lnbits/extensions/withdraw/lnurl.py
+++ b/lnbits/extensions/withdraw/lnurl.py
@@ -11,6 +11,7 @@ from loguru import logger
from starlette.requests import Request
from starlette.responses import HTMLResponse
+from lnbits.core.crud import update_payment_extra
from lnbits.core.services import pay_invoice
from . import withdraw_ext
@@ -44,7 +45,11 @@ async def api_lnurl_response(request: Request, unique_hash):
"minWithdrawable": link.min_withdrawable * 1000,
"maxWithdrawable": link.max_withdrawable * 1000,
"defaultDescription": link.title,
+ "webhook_url": link.webhook_url,
+ "webhook_headers": link.webhook_headers,
+ "webhook_body": link.webhook_body,
}
+
return json.dumps(withdrawResponse)
@@ -56,7 +61,7 @@ async def api_lnurl_response(request: Request, unique_hash):
name="withdraw.api_lnurl_callback",
summary="lnurl withdraw callback",
description="""
- This enpoints allows you to put unique_hash, k1
+ This endpoints allows you to put unique_hash, k1
and a payment_request to get your payment_request paid.
""",
response_description="JSON with status",
@@ -143,18 +148,37 @@ async def api_lnurl_callback(
if link.webhook_url:
async with httpx.AsyncClient() as client:
try:
- r = await client.post(
- link.webhook_url,
- json={
+ kwargs = {
+ "json": {
"payment_hash": payment_hash,
"payment_request": payment_request,
"lnurlw": link.id,
},
- timeout=40,
+ "timeout": 40,
+ }
+ if link.webhook_body:
+ kwargs["json"]["body"] = json.loads(link.webhook_body)
+ if link.webhook_headers:
+ kwargs["headers"] = json.loads(link.webhook_headers)
+
+ r: httpx.Response = await client.post(link.webhook_url, **kwargs)
+ await update_payment_extra(
+ payment_hash,
+ {
+ "wh_success": r.is_success,
+ "wh_message": r.reason_phrase,
+ "wh_response": r.text,
+ },
)
except Exception as exc:
# webhook fails shouldn't cause the lnurlw to fail since invoice is already paid
- logger.error("Caught exception when dispatching webhook url:", exc)
+ logger.error(
+ "Caught exception when dispatching webhook url: " + str(exc)
+ )
+ await update_payment_extra(
+ payment_hash,
+ {"wh_success": False, "wh_message": str(exc)},
+ )
return {"status": "OK"}
diff --git a/lnbits/extensions/withdraw/migrations.py b/lnbits/extensions/withdraw/migrations.py
index 0c6ed4fc..95805ae7 100644
--- a/lnbits/extensions/withdraw/migrations.py
+++ b/lnbits/extensions/withdraw/migrations.py
@@ -122,3 +122,13 @@ async def m005_add_custom_print_design(db):
Adds custom print design
"""
await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN custom_url TEXT;")
+
+
+async def m006_webhook_headers_and_body(db):
+ """
+ Add headers and body to webhooks
+ """
+ await db.execute(
+ "ALTER TABLE withdraw.withdraw_link ADD COLUMN webhook_headers TEXT;"
+ )
+ await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN webhook_body TEXT;")
diff --git a/lnbits/extensions/withdraw/models.py b/lnbits/extensions/withdraw/models.py
index 2672537f..51c6a1cf 100644
--- a/lnbits/extensions/withdraw/models.py
+++ b/lnbits/extensions/withdraw/models.py
@@ -16,6 +16,8 @@ class CreateWithdrawData(BaseModel):
wait_time: int = Query(..., ge=1)
is_unique: bool
webhook_url: str = Query(None)
+ webhook_headers: str = Query(None)
+ webhook_body: str = Query(None)
custom_url: str = Query(None)
@@ -35,6 +37,8 @@ class WithdrawLink(BaseModel):
usescsv: str = Query(None)
number: int = Query(0)
webhook_url: str = Query(None)
+ webhook_headers: str = Query(None)
+ webhook_body: str = Query(None)
custom_url: str = Query(None)
@property
diff --git a/lnbits/extensions/withdraw/static/js/index.js b/lnbits/extensions/withdraw/static/js/index.js
index a3eaa593..ced78439 100644
--- a/lnbits/extensions/withdraw/static/js/index.js
+++ b/lnbits/extensions/withdraw/static/js/index.js
@@ -63,7 +63,8 @@ new Vue({
secondMultiplierOptions: ['seconds', 'minutes', 'hours'],
data: {
is_unique: false,
- use_custom: false
+ use_custom: false,
+ has_webhook: false
}
},
simpleformDialog: {
@@ -188,23 +189,35 @@ new Vue({
},
updateWithdrawLink: function (wallet, data) {
var self = this
+ const body = _.pick(
+ data,
+ 'title',
+ 'min_withdrawable',
+ 'max_withdrawable',
+ 'uses',
+ 'wait_time',
+ 'is_unique',
+ 'webhook_url',
+ 'webhook_headers',
+ 'webhook_body',
+ 'custom_url'
+ )
+
+ if (data.has_webhook) {
+ body = {
+ ...body,
+ webhook_url: data.webhook_url,
+ webhook_headers: data.webhook_headers,
+ webhook_body: data.webhook_body
+ }
+ }
LNbits.api
.request(
'PUT',
'/withdraw/api/v1/links/' + data.id,
wallet.adminkey,
- _.pick(
- data,
- 'title',
- 'min_withdrawable',
- 'max_withdrawable',
- 'uses',
- 'wait_time',
- 'is_unique',
- 'webhook_url',
- 'custom_url'
- )
+ body
)
.then(function (response) {
self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) {
diff --git a/lnbits/extensions/withdraw/templates/withdraw/index.html b/lnbits/extensions/withdraw/templates/withdraw/index.html
index 27684f6b..3ae244e6 100644
--- a/lnbits/extensions/withdraw/templates/withdraw/index.html
+++ b/lnbits/extensions/withdraw/templates/withdraw/index.html
@@ -209,7 +209,13 @@
+
+
+