async invoice listeners through webhooks: lnpay and opennode.

This commit is contained in:
fiatjaf 2020-09-29 00:52:27 -03:00
parent 74117ffc57
commit 2c92205703
4 changed files with 64 additions and 15 deletions

View file

@ -93,7 +93,7 @@ def register_request_hooks(app: Quart):
def register_async_tasks(app):
from lnbits.core.tasks import invoice_listener, webhook_handler
@app.route("/wallet/webhook")
@app.route("/wallet/webhook", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
async def webhook_listener():
return await webhook_handler()

View file

@ -1,5 +1,6 @@
import asyncio
from typing import Optional, List, Awaitable, Tuple, Callable
from http import HTTPStatus
from typing import Optional, Tuple, List, Callable, Awaitable
from quart import Quart, Request, g
from werkzeug.datastructures import Headers
@ -52,7 +53,8 @@ def register_invoice_listener(ext_name: str, cb: Callable[[Payment], Awaitable[N
async def webhook_handler():
handler = getattr(WALLET, "webhook_listener", None)
if handler:
await handler()
return await handler()
return "", HTTPStatus.NO_CONTENT
async def invoice_listener(app):

View file

@ -1,6 +1,8 @@
import json
import asyncio
import aiohttp
from os import getenv
from http import HTTPStatus
from typing import Optional, Dict, AsyncGenerator
from requests import get, post
from quart import request
@ -18,7 +20,6 @@ class LNPayWallet(Wallet):
self.auth_invoice = getenv("LNPAY_INVOICE_KEY")
self.auth_read = getenv("LNPAY_READ_KEY")
self.auth_api = {"X-Api-Key": getenv("LNPAY_API_KEY")}
self.queue = asyncio.Queue()
def create_invoice(
self,
@ -79,18 +80,26 @@ class LNPayWallet(Wallet):
return PaymentStatus(statuses[r.json()["settled"]])
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
self.queue: asyncio.Queue = asyncio.Queue()
while True:
yield await self.queue.get()
item = await self.queue.get()
yield item
self.queue.task_done()
async def webhook_listener(self):
data = await request.get_json()
if "event" not in data or data["event"].get("name") != "wallet_receive":
return ""
text: str = await request.get_data()
data = json.loads(text)
if type(data) is not dict or "event" not in data or data["event"].get("name") != "wallet_receive":
return "", HTTPStatus.NO_CONTENT
lntx_id = data["data"]["wtx"]["lnTx"]["id"]
async with aiohttp.ClientSession() as session:
async with session.get(f"{self.endpoint}/user/lntx/{lntx_id}?fields=settled") as resp:
data = await resp.json()
if data["settled"]:
self.queue.put_nowait(lntx_id)
resp = await session.get(
f"{self.endpoint}/user/lntx/{lntx_id}?fields=settled",
headers=self.auth_api,
)
data = await resp.json()
if data["settled"]:
self.queue.put_nowait(lntx_id)
return "", HTTPStatus.NO_CONTENT

View file

@ -1,6 +1,11 @@
import json
import asyncio
import hmac
from http import HTTPStatus
from os import getenv
from typing import Optional
from typing import Optional, AsyncGenerator
from requests import get, post
from quart import request, url_for
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet, Unsupported
@ -23,13 +28,18 @@ class OpenNodeWallet(Wallet):
r = post(
url=f"{self.endpoint}/v1/charges",
headers=self.auth_invoice,
json={"amount": f"{amount}", "description": memo}, # , "private": True},
json={
"amount": amount,
"description": memo or "",
"callback_url": url_for("webhook_listener", _external=True),
},
)
ok, checking_id, payment_request, error_message = r.ok, None, None, None
if r.ok:
data = r.json()["data"]
checking_id, payment_request = data["id"], data["lightning_invoice"]["payreq"]
checking_id = data["id"]
payment_request = data["lightning_invoice"]["payreq"]
else:
error_message = r.json()["message"]
@ -64,3 +74,31 @@ class OpenNodeWallet(Wallet):
statuses = {"initial": None, "pending": None, "confirmed": True, "error": False, "failed": False}
return PaymentStatus(statuses[r.json()["data"]["status"]])
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
self.queue: asyncio.Queue = asyncio.Queue()
while True:
item = await self.queue.get()
yield item
self.queue.task_done()
async def webhook_listener(self):
print("a request!")
text: str = await request.get_data()
print("text", text)
data = json.loads(text)
if type(data) is not dict or "event" not in data or data["event"].get("name") != "wallet_receive":
return "", HTTPStatus.NO_CONTENT
charge_id = data["id"]
if data["status"] != "paid":
return "", HTTPStatus.NO_CONTENT
x = hmac.new(self.auth_invoice["Authorization"], digestmod="sha256")
x.update(charge_id)
if x.hexdigest() != data["hashed_order"]:
print("invalid webhook, not from opennode")
return "", HTTPStatus.NO_CONTENT
self.queue.put_nowait(charge_id)
return "", HTTPStatus.NO_CONTENT