Browse Source

async invoice listeners through webhooks: lnpay and opennode.

atmext
fiatjaf 4 years ago
parent
commit
2c92205703
  1. 2
      lnbits/app.py
  2. 6
      lnbits/core/tasks.py
  3. 21
      lnbits/wallets/lnpay.py
  4. 44
      lnbits/wallets/opennode.py

2
lnbits/app.py

@ -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()

6
lnbits/core/tasks.py

@ -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):

21
lnbits/wallets/lnpay.py

@ -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:
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

44
lnbits/wallets/opennode.py

@ -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

Loading…
Cancel
Save