invoice listeners support on lnd and other fixes around wallets/
This commit is contained in:
parent
90c640b659
commit
b3c69ad49c
|
@ -35,24 +35,21 @@ LNBITS_ADMIN_MACAROON=LNBITS_ADMIN_MACAROON
|
|||
# LndWallet
|
||||
LND_GRPC_ENDPOINT=127.0.0.1
|
||||
LND_GRPC_PORT=11009
|
||||
LND_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
|
||||
LND_GRPC_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
|
||||
LND_ADMIN_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon"
|
||||
LND_INVOICE_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/invoice.macaroon"
|
||||
LND_READ_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/read.macaroon"
|
||||
|
||||
# LndRestWallet
|
||||
LND_REST_ENDPOINT=https://localhost:8080/
|
||||
LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
|
||||
LND_REST_ADMIN_MACAROON="HEXSTRING"
|
||||
LND_REST_INVOICE_MACAROON="HEXSTRING"
|
||||
LND_REST_READ_MACAROON="HEXSTRING"
|
||||
|
||||
# LNPayWallet
|
||||
LNPAY_API_ENDPOINT=https://lnpay.co/v1/
|
||||
LNPAY_API_KEY=LNPAY_API_KEY
|
||||
LNPAY_ADMIN_KEY=LNPAY_ADMIN_KEY
|
||||
LNPAY_INVOICE_KEY=LNPAY_INVOICE_KEY
|
||||
LNPAY_READ_KEY=LNPAY_READ_KEY
|
||||
|
||||
# LntxbotWallet
|
||||
LNTXBOT_API_ENDPOINT=https://lntxbot.bigsun.xyz/
|
||||
|
|
|
@ -6,6 +6,7 @@ from .crud import get_pay_link_by_invoice, mark_webhook_sent
|
|||
|
||||
|
||||
async def on_invoice_paid(payment: Payment) -> None:
|
||||
print(payment)
|
||||
islnurlp = "lnurlp" == payment.extra.get("tag")
|
||||
if islnurlp:
|
||||
pay_link = get_pay_link_by_invoice(payment.payment_hash)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import NamedTuple, Optional
|
||||
from typing import NamedTuple, Optional, AsyncGenerator
|
||||
|
||||
|
||||
class InvoiceResponse(NamedTuple):
|
||||
|
@ -43,6 +43,15 @@ class Wallet(ABC):
|
|||
def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
"""
|
||||
this is an async function, but here it is noted without the 'async'
|
||||
prefix because mypy has a bug identifying the signature of abstract
|
||||
methods.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Unsupported(Exception):
|
||||
pass
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
try:
|
||||
from lightning import LightningRpc # type: ignore
|
||||
from lightning import LightningRpc, RpcError # type: ignore
|
||||
except ImportError: # pragma: nocover
|
||||
LightningRpc = None
|
||||
|
||||
import random
|
||||
|
||||
from os import getenv
|
||||
from typing import Optional
|
||||
from typing import Optional, AsyncGenerator
|
||||
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet, Unsupported
|
||||
|
||||
|
||||
|
@ -15,26 +15,52 @@ class CLightningWallet(Wallet):
|
|||
if LightningRpc is None: # pragma: nocover
|
||||
raise ImportError("The `pylightning` library must be installed to use `CLightningWallet`.")
|
||||
|
||||
self.l1 = LightningRpc(getenv("CLIGHTNING_RPC"))
|
||||
self.ln = LightningRpc(getenv("CLIGHTNING_RPC"))
|
||||
|
||||
# check description_hash support (could be provided by a plugin)
|
||||
self.supports_description_hash = False
|
||||
try:
|
||||
answer = self.ln.help("invoicewithdescriptionhash")
|
||||
if answer["help"][0]["command"].startswith(
|
||||
"invoicewithdescriptionhash msatoshi label description_hash",
|
||||
):
|
||||
self.supports_description_hash = True
|
||||
except:
|
||||
pass
|
||||
|
||||
# check last payindex so we can listen from that point on
|
||||
self.last_pay_index = 0
|
||||
invoices = self.ln.listinvoices()
|
||||
if len(invoices["invoices"]):
|
||||
self.last_pay_index = invoices["invoices"][-1]["pay_index"]
|
||||
|
||||
def create_invoice(
|
||||
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
|
||||
) -> InvoiceResponse:
|
||||
if description_hash:
|
||||
raise Unsupported("description_hash")
|
||||
|
||||
label = "lbl{}".format(random.random())
|
||||
r = self.l1.invoice(amount * 1000, label, memo, exposeprivatechannels=True)
|
||||
ok, checking_id, payment_request, error_message = True, r["payment_hash"], r["bolt11"], None
|
||||
return InvoiceResponse(ok, checking_id, payment_request, error_message)
|
||||
msat = amount * 1000
|
||||
|
||||
try:
|
||||
if description_hash:
|
||||
if not self.supports_description_hash:
|
||||
raise Unsupported("description_hash")
|
||||
|
||||
r = self.ln.call("invoicewithdescriptionhash", [msat, label, memo])
|
||||
return InvoiceResponse(True, label, r["bolt11"], "")
|
||||
else:
|
||||
r = self.ln.invoice(msat, label, memo, exposeprivatechannels=True)
|
||||
return InvoiceResponse(True, label, r["bolt11"], "")
|
||||
except RpcError as exc:
|
||||
error_message = f"lightningd '{exc.method}' failed with '{exc.error}'."
|
||||
return InvoiceResponse(False, label, None, error_message)
|
||||
|
||||
def pay_invoice(self, bolt11: str) -> PaymentResponse:
|
||||
r = self.l1.pay(bolt11)
|
||||
r = self.ln.pay(bolt11)
|
||||
ok, checking_id, fee_msat, error_message = True, r["payment_hash"], r["msatoshi_sent"] - r["msatoshi"], None
|
||||
return PaymentResponse(ok, checking_id, fee_msat, error_message)
|
||||
|
||||
def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
r = self.l1.listinvoices(checking_id)
|
||||
r = self.ln.listinvoices(checking_id)
|
||||
if not r["invoices"]:
|
||||
return PaymentStatus(False)
|
||||
if r["invoices"][0]["label"] == checking_id:
|
||||
|
@ -42,7 +68,7 @@ class CLightningWallet(Wallet):
|
|||
raise KeyError("supplied an invalid checking_id")
|
||||
|
||||
def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
r = self.l1.listpays(payment_hash=checking_id)
|
||||
r = self.ln.listpays(payment_hash=checking_id)
|
||||
if not r["pays"]:
|
||||
return PaymentStatus(False)
|
||||
if r["pays"][0]["payment_hash"] == checking_id:
|
||||
|
@ -53,3 +79,9 @@ class CLightningWallet(Wallet):
|
|||
return PaymentStatus(False)
|
||||
return PaymentStatus(None)
|
||||
raise KeyError("supplied an invalid checking_id")
|
||||
|
||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
while True:
|
||||
paid = self.ln.waitanyinvoice(self.last_pay_index)
|
||||
self.last_pay_index = paid["pay_index"]
|
||||
yield paid["label"]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from os import getenv
|
||||
from typing import Optional, Dict
|
||||
from typing import Optional, Dict, AsyncGenerator
|
||||
from requests import get, post
|
||||
|
||||
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
|
||||
|
@ -64,3 +64,7 @@ class LNbitsWallet(Wallet):
|
|||
return PaymentStatus(None)
|
||||
|
||||
return PaymentStatus(r.json()["paid"])
|
||||
|
||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
print("lnbits does not support paid invoices stream yet")
|
||||
yield ""
|
||||
|
|
|
@ -5,88 +5,94 @@ except ImportError: # pragma: nocover
|
|||
|
||||
import base64
|
||||
from os import getenv
|
||||
from typing import Optional, Dict
|
||||
from typing import Optional, Dict, AsyncGenerator
|
||||
|
||||
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
|
||||
|
||||
|
||||
def parse_checking_id(checking_id: str) -> bytes:
|
||||
return base64.b64decode(
|
||||
checking_id.replace("_", "/"),
|
||||
)
|
||||
|
||||
|
||||
def stringify_checking_id(r_hash: bytes) -> str:
|
||||
return (
|
||||
base64.b64encode(
|
||||
r_hash,
|
||||
)
|
||||
.decode("utf-8")
|
||||
.replace("/", "_")
|
||||
)
|
||||
|
||||
|
||||
class LndWallet(Wallet):
|
||||
def __init__(self):
|
||||
if lnd_grpc is None: # pragma: nocover
|
||||
raise ImportError("The `lnd-grpc` library must be installed to use `LndWallet`.")
|
||||
|
||||
endpoint = getenv("LND_GRPC_ENDPOINT")
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
self.port = getenv("LND_GRPC_PORT")
|
||||
self.auth_admin = getenv("LND_ADMIN_MACAROON")
|
||||
self.auth_invoice = getenv("LND_INVOICE_MACAROON")
|
||||
self.auth_read = getenv("LND_READ_MACAROON")
|
||||
self.auth_cert = getenv("LND_CERT")
|
||||
endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
port = getenv("LND_GRPC_PORT")
|
||||
cert = getenv("LND_GRPC_CERT") or getenv("LND_CERT")
|
||||
auth_admin = getenv("LND_ADMIN_MACAROON")
|
||||
auth_invoices = getenv("LND_INVOICE_MACAROON")
|
||||
network = getenv("LND_GRPC_NETWORK", "mainnet")
|
||||
|
||||
self.admin_rpc = lnd_grpc.Client(
|
||||
lnd_dir=None,
|
||||
macaroon_path=auth_admin,
|
||||
tls_cert_path=cert,
|
||||
network=network,
|
||||
grpc_host=endpoint,
|
||||
grpc_port=port,
|
||||
)
|
||||
|
||||
self.invoices_rpc = lnd_grpc.Client(
|
||||
lnd_dir=None,
|
||||
macaroon_path=auth_invoices,
|
||||
tls_cert_path=cert,
|
||||
network=network,
|
||||
grpc_host=endpoint,
|
||||
grpc_port=port,
|
||||
)
|
||||
|
||||
def create_invoice(
|
||||
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
|
||||
) -> InvoiceResponse:
|
||||
lnd_rpc = lnd_grpc.Client(
|
||||
lnd_dir=None,
|
||||
macaroon_path=self.auth_invoice,
|
||||
tls_cert_path=self.auth_cert,
|
||||
network="mainnet",
|
||||
grpc_host=self.endpoint,
|
||||
grpc_port=self.port,
|
||||
)
|
||||
|
||||
params: Dict = {"value": amount, "expiry": 600, "private": True}
|
||||
if description_hash:
|
||||
params["description_hash"] = description_hash # as bytes directly
|
||||
else:
|
||||
params["memo"] = memo or ""
|
||||
lndResponse = lnd_rpc.add_invoice(**params)
|
||||
decoded_hash = base64.b64encode(lndResponse.r_hash).decode("utf-8").replace("/", "_")
|
||||
ok, checking_id, payment_request, error_message = True, decoded_hash, str(lndResponse.payment_request), None
|
||||
return InvoiceResponse(ok, checking_id, payment_request, error_message)
|
||||
resp = self.invoices_rpc.add_invoice(**params)
|
||||
|
||||
checking_id = stringify_checking_id(resp.r_hash)
|
||||
payment_request = str(resp.payment_request)
|
||||
return InvoiceResponse(True, checking_id, payment_request, None)
|
||||
|
||||
def pay_invoice(self, bolt11: str) -> PaymentResponse:
|
||||
lnd_rpc = lnd_grpc.Client(
|
||||
lnd_dir=None,
|
||||
macaroon_path=self.auth_admin,
|
||||
tls_cert_path=self.auth_cert,
|
||||
network="mainnet",
|
||||
grpc_host=self.endpoint,
|
||||
grpc_port=self.port,
|
||||
)
|
||||
resp = self.admin_rpc.pay_invoice(payment_request=bolt11)
|
||||
|
||||
payinvoice = lnd_rpc.pay_invoice(
|
||||
payment_request=bolt11,
|
||||
)
|
||||
if resp.payment_error:
|
||||
return PaymentResponse(False, "", 0, resp.payment_error)
|
||||
|
||||
ok, checking_id, fee_msat, error_message = True, None, 0, None
|
||||
|
||||
if payinvoice.payment_error:
|
||||
ok, error_message = False, payinvoice.payment_error
|
||||
else:
|
||||
checking_id = base64.b64encode(payinvoice.payment_hash).decode("utf-8").replace("/", "_")
|
||||
|
||||
return PaymentResponse(ok, checking_id, fee_msat, error_message)
|
||||
checking_id = stringify_checking_id(resp.payment_hash)
|
||||
return PaymentResponse(True, checking_id, 0, None)
|
||||
|
||||
def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
|
||||
check_id = base64.b64decode(checking_id.replace("_", "/"))
|
||||
print(check_id)
|
||||
lnd_rpc = lnd_grpc.Client(
|
||||
lnd_dir=None,
|
||||
macaroon_path=self.auth_invoice,
|
||||
tls_cert_path=self.auth_cert,
|
||||
network="mainnet",
|
||||
grpc_host=self.endpoint,
|
||||
grpc_port=self.port,
|
||||
)
|
||||
|
||||
for _response in lnd_rpc.subscribe_single_invoice(check_id):
|
||||
r_hash = parse_checking_id(checking_id)
|
||||
for _response in self.invoices_rpc.subscribe_single_invoice(r_hash):
|
||||
if _response.state == 1:
|
||||
return PaymentStatus(True)
|
||||
|
||||
return PaymentStatus(None)
|
||||
|
||||
def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
|
||||
return PaymentStatus(True)
|
||||
|
||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
for paid in self.invoices_rpc.SubscribeInvoices():
|
||||
print("PAID", paid)
|
||||
checking_id = stringify_checking_id(paid.r_hash)
|
||||
yield checking_id
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
from os import getenv
|
||||
from typing import Optional, Dict
|
||||
import httpx
|
||||
import json
|
||||
import base64
|
||||
from requests import get, post
|
||||
from os import getenv
|
||||
from typing import Optional, Dict, AsyncGenerator
|
||||
|
||||
from lnbits import bolt11
|
||||
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
|
||||
|
||||
|
||||
|
@ -12,10 +15,8 @@ class LndRestWallet(Wallet):
|
|||
|
||||
endpoint = getenv("LND_REST_ENDPOINT")
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
print(self.endpoint)
|
||||
self.auth_admin = {"Grpc-Metadata-macaroon": getenv("LND_REST_ADMIN_MACAROON")}
|
||||
self.auth_invoice = {"Grpc-Metadata-macaroon": getenv("LND_REST_INVOICE_MACAROON")}
|
||||
self.auth_read = {"Grpc-Metadata-macaroon": getenv("LND_REST_READ_MACAROON")}
|
||||
self.auth_cert = getenv("LND_REST_CERT")
|
||||
|
||||
def create_invoice(
|
||||
|
@ -30,84 +31,97 @@ class LndRestWallet(Wallet):
|
|||
else:
|
||||
data["memo"] = memo or ""
|
||||
|
||||
r = post(
|
||||
r = httpx.post(
|
||||
url=f"{self.endpoint}/v1/invoices",
|
||||
headers=self.auth_invoice,
|
||||
verify=self.auth_cert,
|
||||
json=data,
|
||||
)
|
||||
|
||||
ok, checking_id, payment_request, error_message = r.ok, None, None, None
|
||||
if r.is_error:
|
||||
error_message = r.text
|
||||
try:
|
||||
error_message = r.json()["error"]
|
||||
except Exception:
|
||||
pass
|
||||
return InvoiceResponse(False, None, None, error_message)
|
||||
|
||||
if r.ok:
|
||||
data = r.json()
|
||||
payment_request = data["payment_request"]
|
||||
data = r.json()
|
||||
payment_request = data["payment_request"]
|
||||
payment_hash = base64.b64decode(data["r_hash"]).hex()
|
||||
checking_id = payment_hash
|
||||
|
||||
r = get(
|
||||
url=f"{self.endpoint}/v1/payreq/{payment_request}",
|
||||
headers=self.auth_read,
|
||||
verify=self.auth_cert,
|
||||
)
|
||||
print(r)
|
||||
if r.ok:
|
||||
checking_id = r.json()["payment_hash"].replace("/", "_")
|
||||
print(checking_id)
|
||||
error_message = None
|
||||
ok = True
|
||||
|
||||
return InvoiceResponse(ok, checking_id, payment_request, error_message)
|
||||
return InvoiceResponse(True, checking_id, payment_request, None)
|
||||
|
||||
def pay_invoice(self, bolt11: str) -> PaymentResponse:
|
||||
r = post(
|
||||
r = httpx.post(
|
||||
url=f"{self.endpoint}/v1/channels/transactions",
|
||||
headers=self.auth_admin,
|
||||
verify=self.auth_cert,
|
||||
json={"payment_request": bolt11},
|
||||
)
|
||||
ok, checking_id, fee_msat, error_message = r.ok, None, 0, None
|
||||
r = get(
|
||||
url=f"{self.endpoint}/v1/payreq/{bolt11}",
|
||||
headers=self.auth_admin,
|
||||
verify=self.auth_cert,
|
||||
)
|
||||
|
||||
if r.ok:
|
||||
checking_id = r.json()["payment_hash"]
|
||||
else:
|
||||
error_message = r.json()["error"]
|
||||
if r.is_error:
|
||||
error_message = r.text
|
||||
try:
|
||||
error_message = r.json()["error"]
|
||||
except:
|
||||
pass
|
||||
return PaymentResponse(False, None, 0, error_message)
|
||||
|
||||
return PaymentResponse(ok, checking_id, fee_msat, error_message)
|
||||
payment_hash = r.json()["payment_hash"]
|
||||
checking_id = payment_hash
|
||||
|
||||
return PaymentResponse(True, checking_id, 0, None)
|
||||
|
||||
def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
checking_id = checking_id.replace("_", "/")
|
||||
print(checking_id)
|
||||
r = get(
|
||||
r = httpx.get(
|
||||
url=f"{self.endpoint}/v1/invoice/{checking_id}",
|
||||
headers=self.auth_invoice,
|
||||
verify=self.auth_cert,
|
||||
)
|
||||
print(r.json()["settled"])
|
||||
if not r or r.json()["settled"] == False:
|
||||
return PaymentStatus(None)
|
||||
|
||||
return PaymentStatus(r.json()["settled"])
|
||||
|
||||
def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
r = get(
|
||||
r = httpx.get(
|
||||
url=f"{self.endpoint}/v1/payments",
|
||||
headers=self.auth_admin,
|
||||
verify=self.auth_cert,
|
||||
params={"include_incomplete": "True", "max_payments": "20"},
|
||||
)
|
||||
|
||||
if not r.ok:
|
||||
if r.is_error:
|
||||
return PaymentStatus(None)
|
||||
|
||||
payments = [p for p in r.json()["payments"] if p["payment_hash"] == checking_id]
|
||||
print(checking_id)
|
||||
payment = payments[0] if payments else None
|
||||
|
||||
# check payment.status: https://api.lightning.community/rest/index.html?python#peersynctype
|
||||
# check payment.status:
|
||||
# https://api.lightning.community/rest/index.html?python#peersynctype
|
||||
statuses = {"UNKNOWN": None, "IN_FLIGHT": None, "SUCCEEDED": True, "FAILED": False}
|
||||
|
||||
return PaymentStatus(statuses[payment["status"]])
|
||||
|
||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
url = self.endpoint + "/v1/invoices/subscribe"
|
||||
|
||||
async with httpx.AsyncClient(timeout=None, headers=self.auth_admin, verify=self.auth_cert) as client:
|
||||
async with client.stream("GET", url) as r:
|
||||
print("ok")
|
||||
print(r)
|
||||
print(r.is_error)
|
||||
print("ok")
|
||||
async for line in r.aiter_lines():
|
||||
print("line", line)
|
||||
try:
|
||||
event = json.loads(line)["result"]
|
||||
print(event)
|
||||
except:
|
||||
continue
|
||||
|
||||
payment_hash = bolt11.decode(event["payment_request"]).payment_hash
|
||||
yield payment_hash
|
||||
|
|
|
@ -4,7 +4,6 @@ import httpx
|
|||
from os import getenv
|
||||
from http import HTTPStatus
|
||||
from typing import Optional, Dict, AsyncGenerator
|
||||
from requests import get, post
|
||||
from quart import request
|
||||
|
||||
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
|
||||
|
@ -14,11 +13,9 @@ class LNPayWallet(Wallet):
|
|||
"""https://docs.lnpay.co/"""
|
||||
|
||||
def __init__(self):
|
||||
endpoint = getenv("LNPAY_API_ENDPOINT")
|
||||
endpoint = getenv("LNPAY_API_ENDPOINT", "https://lnpay.co/v1")
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
self.auth_admin = getenv("LNPAY_ADMIN_KEY")
|
||||
self.auth_invoice = getenv("LNPAY_INVOICE_KEY")
|
||||
self.auth_read = getenv("LNPAY_READ_KEY")
|
||||
self.auth_api = {"X-Api-Key": getenv("LNPAY_API_KEY")}
|
||||
|
||||
def create_invoice(
|
||||
|
@ -33,8 +30,8 @@ class LNPayWallet(Wallet):
|
|||
else:
|
||||
data["memo"] = memo or ""
|
||||
|
||||
r = post(
|
||||
url=f"{self.endpoint}/user/wallet/{self.auth_invoice}/invoice",
|
||||
r = httpx.post(
|
||||
url=f"{self.endpoint}/user/wallet/{self.auth_admin}/invoice",
|
||||
headers=self.auth_api,
|
||||
json=data,
|
||||
)
|
||||
|
@ -52,7 +49,7 @@ class LNPayWallet(Wallet):
|
|||
return InvoiceResponse(ok, checking_id, payment_request, error_message)
|
||||
|
||||
def pay_invoice(self, bolt11: str) -> PaymentResponse:
|
||||
r = post(
|
||||
r = httpx.post(
|
||||
url=f"{self.endpoint}/user/wallet/{self.auth_admin}/withdraw",
|
||||
headers=self.auth_api,
|
||||
json={"payment_request": bolt11},
|
||||
|
@ -68,12 +65,12 @@ class LNPayWallet(Wallet):
|
|||
return self.get_payment_status(checking_id)
|
||||
|
||||
def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
r = get(
|
||||
r = httpx.get(
|
||||
url=f"{self.endpoint}/user/lntx/{checking_id}?fields=settled",
|
||||
headers=self.auth_api,
|
||||
)
|
||||
|
||||
if not r.ok:
|
||||
if r.is_error:
|
||||
return PaymentStatus(None)
|
||||
|
||||
statuses = {0: None, 1: True, -1: False}
|
||||
|
@ -82,8 +79,7 @@ class LNPayWallet(Wallet):
|
|||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
self.queue: asyncio.Queue = asyncio.Queue()
|
||||
while True:
|
||||
item = await self.queue.get()
|
||||
yield item
|
||||
yield await self.queue.get()
|
||||
self.queue.task_done()
|
||||
|
||||
async def webhook_listener(self):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from os import getenv
|
||||
from typing import Optional, Dict
|
||||
from typing import Optional, Dict, AsyncGenerator
|
||||
from requests import post
|
||||
|
||||
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
|
||||
|
@ -75,3 +75,7 @@ class LntxbotWallet(Wallet):
|
|||
|
||||
statuses = {"complete": True, "failed": False, "pending": None, "unknown": None}
|
||||
return PaymentStatus(statuses[r.json().get("status", "unknown")])
|
||||
|
||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
print("lntxbot does not support paid invoices stream yet")
|
||||
yield ""
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import json
|
||||
import asyncio
|
||||
import hmac
|
||||
import httpx
|
||||
from http import HTTPStatus
|
||||
from os import getenv
|
||||
from typing import Optional, AsyncGenerator
|
||||
from requests import get, post
|
||||
from quart import request, url_for
|
||||
|
||||
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet, Unsupported
|
||||
|
@ -25,8 +25,8 @@ class OpenNodeWallet(Wallet):
|
|||
if description_hash:
|
||||
raise Unsupported("description_hash")
|
||||
|
||||
r = post(
|
||||
url=f"{self.endpoint}/v1/charges",
|
||||
r = httpx.post(
|
||||
f"{self.endpoint}/v1/charges",
|
||||
headers=self.auth_invoice,
|
||||
json={
|
||||
"amount": amount,
|
||||
|
@ -34,42 +34,43 @@ class OpenNodeWallet(Wallet):
|
|||
"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 = data["id"]
|
||||
payment_request = data["lightning_invoice"]["payreq"]
|
||||
else:
|
||||
if r.is_error:
|
||||
error_message = r.json()["message"]
|
||||
return InvoiceResponse(False, None, None, error_message)
|
||||
|
||||
return InvoiceResponse(ok, checking_id, payment_request, error_message)
|
||||
data = r.json()["data"]
|
||||
checking_id = data["id"]
|
||||
payment_request = data["lightning_invoice"]["payreq"]
|
||||
return InvoiceResponse(True, checking_id, payment_request, error_message)
|
||||
|
||||
def pay_invoice(self, bolt11: str) -> PaymentResponse:
|
||||
r = post(url=f"{self.endpoint}/v2/withdrawals", headers=self.auth_admin, json={"type": "ln", "address": bolt11})
|
||||
ok, checking_id, fee_msat, error_message = r.ok, None, 0, None
|
||||
r = httpx.post(
|
||||
f"{self.endpoint}/v2/withdrawals", headers=self.auth_admin, json={"type": "ln", "address": bolt11}
|
||||
)
|
||||
|
||||
if r.ok:
|
||||
data = r.json()["data"]
|
||||
checking_id, fee_msat = data["id"], data["fee"] * 1000
|
||||
else:
|
||||
if r.is_error:
|
||||
error_message = r.json()["message"]
|
||||
return PaymentResponse(False, None, 0, error_message)
|
||||
|
||||
return PaymentResponse(ok, checking_id, fee_msat, error_message)
|
||||
data = r.json()["data"]
|
||||
checking_id, fee_msat = data["id"]
|
||||
fee_msat = data["fee"] * 1000
|
||||
return PaymentResponse(True, checking_id, fee_msat, error_message)
|
||||
|
||||
def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
r = get(url=f"{self.endpoint}/v1/charge/{checking_id}", headers=self.auth_invoice)
|
||||
r = httpx.get(f"{self.endpoint}/v1/charge/{checking_id}", headers=self.auth_invoice)
|
||||
|
||||
if not r.ok:
|
||||
if r.is_error:
|
||||
return PaymentStatus(None)
|
||||
|
||||
statuses = {"processing": None, "paid": True, "unpaid": False}
|
||||
return PaymentStatus(statuses[r.json()["data"]["status"]])
|
||||
|
||||
def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
r = get(url=f"{self.endpoint}/v1/withdrawal/{checking_id}", headers=self.auth_admin)
|
||||
r = httpx.get(f"{self.endpoint}/v1/withdrawal/{checking_id}", headers=self.auth_admin)
|
||||
|
||||
if not r.ok:
|
||||
if r.is_error:
|
||||
return PaymentStatus(None)
|
||||
|
||||
statuses = {"initial": None, "pending": None, "confirmed": True, "error": False, "failed": False}
|
||||
|
@ -78,8 +79,7 @@ class OpenNodeWallet(Wallet):
|
|||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
self.queue: asyncio.Queue = asyncio.Queue()
|
||||
while True:
|
||||
item = await self.queue.get()
|
||||
yield item
|
||||
yield await self.queue.get()
|
||||
self.queue.task_done()
|
||||
|
||||
async def webhook_listener(self):
|
||||
|
|
|
@ -34,7 +34,7 @@ class SparkWallet(Wallet):
|
|||
data = r.json()
|
||||
except:
|
||||
raise UnknownError(r.text)
|
||||
if not r.ok:
|
||||
if r.is_error:
|
||||
raise SparkError(data["message"])
|
||||
return data
|
||||
|
||||
|
@ -96,7 +96,7 @@ class SparkWallet(Wallet):
|
|||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
url = self.url + "/stream?access-key=" + self.token
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(timeout=None) as client:
|
||||
async with client.stream("GET", url) as r:
|
||||
async for line in r.aiter_lines():
|
||||
if line.startswith("data:"):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Optional
|
||||
from typing import Optional, AsyncGenerator
|
||||
|
||||
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet, Unsupported
|
||||
|
||||
|
@ -17,3 +17,6 @@ class VoidWallet(Wallet):
|
|||
|
||||
def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
raise Unsupported("")
|
||||
|
||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
yield ""
|
||||
|
|
Loading…
Reference in New Issue
Block a user