2021-11-17 22:20:48 +01:00
|
|
|
import json
|
2021-08-20 13:44:03 +02:00
|
|
|
from datetime import datetime
|
2021-10-18 11:58:09 +02:00
|
|
|
from http import HTTPStatus
|
2021-08-20 13:44:03 +02:00
|
|
|
|
2022-07-16 14:23:03 +02:00
|
|
|
import httpx
|
2023-01-04 16:29:47 +01:00
|
|
|
import shortuuid
|
2023-01-04 18:32:18 +01:00
|
|
|
from fastapi import HTTPException, Query, Request, Response
|
2022-07-16 14:23:03 +02:00
|
|
|
from loguru import logger
|
2021-08-20 13:44:03 +02:00
|
|
|
|
2022-12-21 14:05:28 +01:00
|
|
|
from lnbits.core.crud import update_payment_extra
|
2021-10-18 11:58:09 +02:00
|
|
|
from lnbits.core.services import pay_invoice
|
|
|
|
|
2021-08-20 13:44:03 +02:00
|
|
|
from . import withdraw_ext
|
2023-01-04 18:32:18 +01:00
|
|
|
from .crud import (
|
|
|
|
get_withdraw_link_by_hash,
|
|
|
|
increment_withdraw_link,
|
|
|
|
remove_unique_withdraw_link,
|
|
|
|
)
|
2023-01-04 16:29:47 +01:00
|
|
|
from .models import WithdrawLink
|
2021-08-20 13:44:03 +02:00
|
|
|
|
|
|
|
|
2021-10-17 19:33:29 +02:00
|
|
|
@withdraw_ext.get(
|
|
|
|
"/api/v1/lnurl/{unique_hash}",
|
2023-01-04 18:27:39 +01:00
|
|
|
response_class=Response,
|
2021-10-17 19:33:29 +02:00
|
|
|
name="withdraw.api_lnurl_response",
|
|
|
|
)
|
2021-10-08 18:10:25 +02:00
|
|
|
async def api_lnurl_response(request: Request, unique_hash):
|
2021-08-20 13:44:03 +02:00
|
|
|
link = await get_withdraw_link_by_hash(unique_hash)
|
|
|
|
|
|
|
|
if not link:
|
2021-09-30 15:42:04 +02:00
|
|
|
raise HTTPException(
|
2021-10-17 19:33:29 +02:00
|
|
|
status_code=HTTPStatus.NOT_FOUND, detail="Withdraw link does not exist."
|
2021-08-20 13:44:03 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
if link.is_spent:
|
2022-06-01 14:53:05 +02:00
|
|
|
raise HTTPException(
|
|
|
|
status_code=HTTPStatus.NOT_FOUND, detail="Withdraw is spent."
|
|
|
|
)
|
2021-11-10 22:12:47 +01:00
|
|
|
url = request.url_for("withdraw.api_lnurl_callback", unique_hash=link.unique_hash)
|
|
|
|
withdrawResponse = {
|
|
|
|
"tag": "withdrawRequest",
|
|
|
|
"callback": url,
|
|
|
|
"k1": link.k1,
|
|
|
|
"minWithdrawable": link.min_withdrawable * 1000,
|
|
|
|
"maxWithdrawable": link.max_withdrawable * 1000,
|
|
|
|
"defaultDescription": link.title,
|
2022-12-21 14:05:28 +01:00
|
|
|
"webhook_url": link.webhook_url,
|
|
|
|
"webhook_headers": link.webhook_headers,
|
|
|
|
"webhook_body": link.webhook_body,
|
2021-11-10 22:12:47 +01:00
|
|
|
}
|
2022-12-21 14:05:28 +01:00
|
|
|
|
2021-11-10 22:12:47 +01:00
|
|
|
return json.dumps(withdrawResponse)
|
2021-08-20 13:44:03 +02:00
|
|
|
|
|
|
|
|
2022-11-19 21:12:33 +01:00
|
|
|
@withdraw_ext.get(
|
|
|
|
"/api/v1/lnurl/cb/{unique_hash}",
|
|
|
|
name="withdraw.api_lnurl_callback",
|
|
|
|
summary="lnurl withdraw callback",
|
|
|
|
description="""
|
2022-12-21 14:05:28 +01:00
|
|
|
This endpoints allows you to put unique_hash, k1
|
2022-11-19 21:12:33 +01:00
|
|
|
and a payment_request to get your payment_request paid.
|
|
|
|
""",
|
|
|
|
response_description="JSON with status",
|
|
|
|
responses={
|
|
|
|
200: {"description": "status: OK"},
|
|
|
|
400: {"description": "k1 is wrong or link open time or withdraw not working."},
|
|
|
|
404: {"description": "withdraw link not found."},
|
|
|
|
405: {"description": "withdraw link is spent."},
|
|
|
|
},
|
|
|
|
)
|
2021-10-17 19:33:29 +02:00
|
|
|
async def api_lnurl_callback(
|
2022-06-01 14:53:05 +02:00
|
|
|
unique_hash,
|
|
|
|
k1: str = Query(...),
|
|
|
|
pr: str = Query(...),
|
|
|
|
id_unique_hash=None,
|
2021-10-17 19:33:29 +02:00
|
|
|
):
|
2021-08-20 13:44:03 +02:00
|
|
|
link = await get_withdraw_link_by_hash(unique_hash)
|
|
|
|
now = int(datetime.now().timestamp())
|
|
|
|
if not link:
|
2021-09-30 15:42:04 +02:00
|
|
|
raise HTTPException(
|
2022-11-19 21:12:33 +01:00
|
|
|
status_code=HTTPStatus.NOT_FOUND, detail="withdraw not found."
|
2021-08-20 13:44:03 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
if link.is_spent:
|
2022-06-01 14:53:05 +02:00
|
|
|
raise HTTPException(
|
2022-11-19 21:12:33 +01:00
|
|
|
status_code=HTTPStatus.METHOD_NOT_ALLOWED, detail="withdraw is spent."
|
2022-06-01 14:53:05 +02:00
|
|
|
)
|
2021-11-04 13:46:22 +01:00
|
|
|
|
2021-08-20 13:44:03 +02:00
|
|
|
if link.k1 != k1:
|
2022-11-19 21:12:33 +01:00
|
|
|
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="k1 is wrong.")
|
2021-11-04 13:46:22 +01:00
|
|
|
|
2021-08-20 13:44:03 +02:00
|
|
|
if now < link.open_time:
|
2022-11-19 21:12:33 +01:00
|
|
|
raise HTTPException(
|
|
|
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
|
|
detail=f"wait link open_time {link.open_time - now} seconds.",
|
|
|
|
)
|
2021-11-04 13:46:22 +01:00
|
|
|
|
2023-01-04 16:29:47 +01:00
|
|
|
if id_unique_hash:
|
|
|
|
if check_unique_link(link, id_unique_hash):
|
2023-01-04 18:27:39 +01:00
|
|
|
await remove_unique_withdraw_link(link, id_unique_hash)
|
2023-01-04 16:29:47 +01:00
|
|
|
else:
|
2022-10-28 11:26:02 +02:00
|
|
|
raise HTTPException(
|
2022-11-19 21:12:33 +01:00
|
|
|
status_code=HTTPStatus.NOT_FOUND, detail="withdraw not found."
|
2022-10-28 11:26:02 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
try:
|
2022-06-01 15:24:17 +02:00
|
|
|
payment_hash = await pay_invoice(
|
2021-08-20 13:44:03 +02:00
|
|
|
wallet_id=link.wallet,
|
2023-01-04 16:29:47 +01:00
|
|
|
payment_request=pr,
|
2021-08-20 13:44:03 +02:00
|
|
|
max_sat=link.max_withdrawable,
|
|
|
|
extra={"tag": "withdraw"},
|
|
|
|
)
|
2023-01-04 16:29:47 +01:00
|
|
|
await increment_withdraw_link(link)
|
2022-06-01 15:24:17 +02:00
|
|
|
if link.webhook_url:
|
2023-01-04 16:29:47 +01:00
|
|
|
await dispatch_webhook(link, payment_hash, pr)
|
2021-11-04 13:41:31 +01:00
|
|
|
return {"status": "OK"}
|
2021-08-20 13:44:03 +02:00
|
|
|
except Exception as e:
|
2022-11-19 21:12:33 +01:00
|
|
|
raise HTTPException(
|
|
|
|
status_code=HTTPStatus.BAD_REQUEST, detail=f"withdraw not working. {str(e)}"
|
|
|
|
)
|
2021-11-17 22:20:48 +01:00
|
|
|
|
|
|
|
|
2023-01-04 16:29:47 +01:00
|
|
|
def check_unique_link(link: WithdrawLink, unique_hash: str) -> bool:
|
2023-01-04 18:27:39 +01:00
|
|
|
return any(
|
|
|
|
unique_hash == shortuuid.uuid(name=link.id + link.unique_hash + x.strip())
|
|
|
|
for x in link.usescsv.split(",")
|
|
|
|
)
|
2023-01-04 16:29:47 +01:00
|
|
|
|
|
|
|
|
|
|
|
async def dispatch_webhook(
|
|
|
|
link: WithdrawLink, payment_hash: str, payment_request: str
|
|
|
|
) -> None:
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
|
|
try:
|
|
|
|
r: httpx.Response = await client.post(
|
|
|
|
link.webhook_url,
|
|
|
|
json={
|
|
|
|
"payment_hash": payment_hash,
|
|
|
|
"payment_request": payment_request,
|
|
|
|
"lnurlw": link.id,
|
|
|
|
"body": json.loads(link.webhook_body) if link.webhook_body else "",
|
|
|
|
},
|
|
|
|
headers=json.loads(link.webhook_headers)
|
|
|
|
if link.webhook_headers
|
|
|
|
else None,
|
|
|
|
timeout=40,
|
|
|
|
)
|
|
|
|
await update_payment_extra(
|
|
|
|
payment_hash=payment_hash,
|
|
|
|
extra={
|
|
|
|
"wh_success": r.is_success,
|
|
|
|
"wh_message": r.reason_phrase,
|
|
|
|
"wh_response": r.text,
|
|
|
|
},
|
|
|
|
outgoing=True,
|
|
|
|
)
|
|
|
|
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: " + str(exc))
|
|
|
|
await update_payment_extra(
|
|
|
|
payment_hash=payment_hash,
|
|
|
|
extra={"wh_success": False, "wh_message": str(exc)},
|
|
|
|
outgoing=True,
|
|
|
|
)
|
2021-11-17 22:20:48 +01:00
|
|
|
|
|
|
|
|
2023-01-04 16:29:47 +01:00
|
|
|
# FOR LNURLs WHICH ARE UNIQUE
|
2021-11-17 22:20:48 +01:00
|
|
|
@withdraw_ext.get(
|
|
|
|
"/api/v1/lnurl/{unique_hash}/{id_unique_hash}",
|
2023-01-04 18:27:39 +01:00
|
|
|
response_class=Response,
|
2021-11-17 22:20:48 +01:00
|
|
|
name="withdraw.api_lnurl_multi_response",
|
|
|
|
)
|
|
|
|
async def api_lnurl_multi_response(request: Request, unique_hash, id_unique_hash):
|
|
|
|
link = await get_withdraw_link_by_hash(unique_hash)
|
|
|
|
|
|
|
|
if not link:
|
|
|
|
raise HTTPException(
|
2022-05-18 12:08:52 +02:00
|
|
|
status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found."
|
2021-11-17 22:20:48 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
if link.is_spent:
|
2022-06-01 14:53:05 +02:00
|
|
|
raise HTTPException(
|
|
|
|
status_code=HTTPStatus.NOT_FOUND, detail="Withdraw is spent."
|
|
|
|
)
|
2021-11-17 22:20:48 +01:00
|
|
|
|
2023-01-04 18:27:39 +01:00
|
|
|
if not check_unique_link(link, id_unique_hash):
|
2021-11-17 22:20:48 +01:00
|
|
|
raise HTTPException(
|
2022-05-18 12:08:52 +02:00
|
|
|
status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found."
|
2021-11-17 22:20:48 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
url = request.url_for("withdraw.api_lnurl_callback", unique_hash=link.unique_hash)
|
|
|
|
withdrawResponse = {
|
|
|
|
"tag": "withdrawRequest",
|
2022-05-18 12:08:52 +02:00
|
|
|
"callback": url + "?id_unique_hash=" + id_unique_hash,
|
2021-11-17 22:20:48 +01:00
|
|
|
"k1": link.k1,
|
|
|
|
"minWithdrawable": link.min_withdrawable * 1000,
|
|
|
|
"maxWithdrawable": link.max_withdrawable * 1000,
|
|
|
|
"defaultDescription": link.title,
|
|
|
|
}
|
|
|
|
return json.dumps(withdrawResponse)
|