mirror of
https://github.com/lnbits/lnbits.git
synced 2025-07-07 22:10:22 +02:00
Merge pull request #1227 from motorina0/withdraw_callback_extra
Withdraw webhook improvements
This commit is contained in:
@ -451,6 +451,34 @@ async def update_payment_details(
|
|||||||
return
|
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:
|
async def delete_payment(checking_id: str, conn: Optional[Connection] = None) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"DELETE FROM apipayments WHERE checking_id = ?", (checking_id,)
|
"DELETE FROM apipayments WHERE checking_id = ?", (checking_id,)
|
||||||
|
@ -214,7 +214,8 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
|
|||||||
lnurl_response = resp["reason"]
|
lnurl_response = resp["reason"]
|
||||||
else:
|
else:
|
||||||
lnurl_response = True
|
lnurl_response = True
|
||||||
except (httpx.ConnectError, httpx.RequestError):
|
except (httpx.ConnectError, httpx.RequestError) as ex:
|
||||||
|
logger.error(ex)
|
||||||
lnurl_response = False
|
lnurl_response = False
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -5,6 +5,7 @@ import httpx
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.core import db as core_db
|
from lnbits.core import db as core_db
|
||||||
|
from lnbits.core.crud import update_payment_extra
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
from lnbits.helpers import get_current_extension_name
|
from lnbits.helpers import get_current_extension_name
|
||||||
from lnbits.tasks import register_invoice_listener
|
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_message"] = reason_phrase
|
||||||
payment.extra["wh_response"] = text
|
payment.extra["wh_response"] = text
|
||||||
|
|
||||||
await core_db.execute(
|
await update_payment_extra(payment.payment_hash, payment.extra)
|
||||||
"""
|
|
||||||
UPDATE apipayments SET extra = ?
|
|
||||||
WHERE hash = ?
|
|
||||||
""",
|
|
||||||
(json.dumps(payment.extra), payment.payment_hash),
|
|
||||||
)
|
|
||||||
|
@ -27,9 +27,11 @@ async def create_withdraw_link(
|
|||||||
open_time,
|
open_time,
|
||||||
usescsv,
|
usescsv,
|
||||||
webhook_url,
|
webhook_url,
|
||||||
|
webhook_headers,
|
||||||
|
webhook_body,
|
||||||
custom_url
|
custom_url
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
link_id,
|
link_id,
|
||||||
@ -45,6 +47,8 @@ async def create_withdraw_link(
|
|||||||
int(datetime.now().timestamp()) + data.wait_time,
|
int(datetime.now().timestamp()) + data.wait_time,
|
||||||
usescsv,
|
usescsv,
|
||||||
data.webhook_url,
|
data.webhook_url,
|
||||||
|
data.webhook_headers,
|
||||||
|
data.webhook_body,
|
||||||
data.custom_url,
|
data.custom_url,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -11,6 +11,7 @@ from loguru import logger
|
|||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
from starlette.responses import HTMLResponse
|
from starlette.responses import HTMLResponse
|
||||||
|
|
||||||
|
from lnbits.core.crud import update_payment_extra
|
||||||
from lnbits.core.services import pay_invoice
|
from lnbits.core.services import pay_invoice
|
||||||
|
|
||||||
from . import withdraw_ext
|
from . import withdraw_ext
|
||||||
@ -44,7 +45,11 @@ async def api_lnurl_response(request: Request, unique_hash):
|
|||||||
"minWithdrawable": link.min_withdrawable * 1000,
|
"minWithdrawable": link.min_withdrawable * 1000,
|
||||||
"maxWithdrawable": link.max_withdrawable * 1000,
|
"maxWithdrawable": link.max_withdrawable * 1000,
|
||||||
"defaultDescription": link.title,
|
"defaultDescription": link.title,
|
||||||
|
"webhook_url": link.webhook_url,
|
||||||
|
"webhook_headers": link.webhook_headers,
|
||||||
|
"webhook_body": link.webhook_body,
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.dumps(withdrawResponse)
|
return json.dumps(withdrawResponse)
|
||||||
|
|
||||||
|
|
||||||
@ -56,7 +61,7 @@ async def api_lnurl_response(request: Request, unique_hash):
|
|||||||
name="withdraw.api_lnurl_callback",
|
name="withdraw.api_lnurl_callback",
|
||||||
summary="lnurl withdraw callback",
|
summary="lnurl withdraw callback",
|
||||||
description="""
|
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.
|
and a payment_request to get your payment_request paid.
|
||||||
""",
|
""",
|
||||||
response_description="JSON with status",
|
response_description="JSON with status",
|
||||||
@ -143,18 +148,37 @@ async def api_lnurl_callback(
|
|||||||
if link.webhook_url:
|
if link.webhook_url:
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
try:
|
try:
|
||||||
r = await client.post(
|
kwargs = {
|
||||||
link.webhook_url,
|
"json": {
|
||||||
json={
|
|
||||||
"payment_hash": payment_hash,
|
"payment_hash": payment_hash,
|
||||||
"payment_request": payment_request,
|
"payment_request": payment_request,
|
||||||
"lnurlw": link.id,
|
"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:
|
except Exception as exc:
|
||||||
# webhook fails shouldn't cause the lnurlw to fail since invoice is already paid
|
# 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"}
|
return {"status": "OK"}
|
||||||
|
|
||||||
|
@ -122,3 +122,13 @@ async def m005_add_custom_print_design(db):
|
|||||||
Adds custom print design
|
Adds custom print design
|
||||||
"""
|
"""
|
||||||
await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN custom_url TEXT;")
|
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;")
|
||||||
|
@ -16,6 +16,8 @@ class CreateWithdrawData(BaseModel):
|
|||||||
wait_time: int = Query(..., ge=1)
|
wait_time: int = Query(..., ge=1)
|
||||||
is_unique: bool
|
is_unique: bool
|
||||||
webhook_url: str = Query(None)
|
webhook_url: str = Query(None)
|
||||||
|
webhook_headers: str = Query(None)
|
||||||
|
webhook_body: str = Query(None)
|
||||||
custom_url: str = Query(None)
|
custom_url: str = Query(None)
|
||||||
|
|
||||||
|
|
||||||
@ -35,6 +37,8 @@ class WithdrawLink(BaseModel):
|
|||||||
usescsv: str = Query(None)
|
usescsv: str = Query(None)
|
||||||
number: int = Query(0)
|
number: int = Query(0)
|
||||||
webhook_url: str = Query(None)
|
webhook_url: str = Query(None)
|
||||||
|
webhook_headers: str = Query(None)
|
||||||
|
webhook_body: str = Query(None)
|
||||||
custom_url: str = Query(None)
|
custom_url: str = Query(None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -63,7 +63,8 @@ new Vue({
|
|||||||
secondMultiplierOptions: ['seconds', 'minutes', 'hours'],
|
secondMultiplierOptions: ['seconds', 'minutes', 'hours'],
|
||||||
data: {
|
data: {
|
||||||
is_unique: false,
|
is_unique: false,
|
||||||
use_custom: false
|
use_custom: false,
|
||||||
|
has_webhook: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
simpleformDialog: {
|
simpleformDialog: {
|
||||||
@ -188,23 +189,35 @@ new Vue({
|
|||||||
},
|
},
|
||||||
updateWithdrawLink: function (wallet, data) {
|
updateWithdrawLink: function (wallet, data) {
|
||||||
var self = this
|
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
|
LNbits.api
|
||||||
.request(
|
.request(
|
||||||
'PUT',
|
'PUT',
|
||||||
'/withdraw/api/v1/links/' + data.id,
|
'/withdraw/api/v1/links/' + data.id,
|
||||||
wallet.adminkey,
|
wallet.adminkey,
|
||||||
_.pick(
|
body
|
||||||
data,
|
|
||||||
'title',
|
|
||||||
'min_withdrawable',
|
|
||||||
'max_withdrawable',
|
|
||||||
'uses',
|
|
||||||
'wait_time',
|
|
||||||
'is_unique',
|
|
||||||
'webhook_url',
|
|
||||||
'custom_url'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) {
|
self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) {
|
||||||
|
@ -209,7 +209,13 @@
|
|||||||
</q-select>
|
</q-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<q-toggle
|
||||||
|
label="Webhook"
|
||||||
|
color="secodary"
|
||||||
|
v-model="formDialog.data.has_webhook"
|
||||||
|
></q-toggle>
|
||||||
<q-input
|
<q-input
|
||||||
|
v-if="formDialog.data.has_webhook"
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
v-model="formDialog.data.webhook_url"
|
v-model="formDialog.data.webhook_url"
|
||||||
@ -217,6 +223,24 @@
|
|||||||
label="Webhook URL (optional)"
|
label="Webhook URL (optional)"
|
||||||
hint="A URL to be called whenever this link gets used."
|
hint="A URL to be called whenever this link gets used."
|
||||||
></q-input>
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
v-if="formDialog.data.has_webhook"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model="formDialog.data.webhook_headers"
|
||||||
|
type="text"
|
||||||
|
label="Webhook Headers (optional)"
|
||||||
|
hint="Custom data as JSON string, send headers along with the webhook."
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
v-if="formDialog.data.has_webhook"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model="formDialog.data.webhook_body"
|
||||||
|
type="text"
|
||||||
|
label="Webhook custom data (optional)"
|
||||||
|
hint="Custom data as JSON string, will get posted along with webhook 'body' field."
|
||||||
|
></q-input>
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item tag="label" class="rounded-borders">
|
<q-item tag="label" class="rounded-borders">
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
|
Reference in New Issue
Block a user