refactor: fundingsource Invoice-, PaymentResponses (#3089)

This commit is contained in:
dni ⚡ 2025-04-25 11:52:54 +02:00 committed by GitHub
parent 94d5f37723
commit ffecd03c4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 287 additions and 215 deletions

View file

@ -1,7 +1,7 @@
import asyncio
import hashlib
import json
from typing import AsyncGenerator, Dict, Optional
from typing import AsyncGenerator, Optional
import httpx
from loguru import logger
@ -72,10 +72,10 @@ class AlbyWallet(Wallet):
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
**kwargs,
**_,
) -> InvoiceResponse:
# https://api.getalby.com/invoices
data: Dict = {"amount": f"{amount}"}
data: dict = {"amount": f"{amount}"}
if description_hash:
data["description_hash"] = description_hash.hex()
elif unhashed_description:
@ -95,25 +95,29 @@ class AlbyWallet(Wallet):
if r.is_error:
error_message = data["message"] if "message" in data else r.text
return InvoiceResponse(False, None, None, error_message)
return InvoiceResponse(ok=False, error_message=error_message)
checking_id = data["payment_hash"]
payment_request = data["payment_request"]
return InvoiceResponse(True, checking_id, payment_request, None)
return InvoiceResponse(
ok=True,
checking_id=checking_id,
payment_request=payment_request,
)
except KeyError as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, "Server error: 'missing required fields'"
ok=False, error_message="Server error: 'missing required fields'"
)
except json.JSONDecodeError as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, "Server error: 'invalid json response'"
ok=False, error_message="Server error: 'invalid json response'"
)
except Exception as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, f"Unable to connect to {self.endpoint}."
ok=False, error_message=f"Unable to connect to {self.endpoint}."
)
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
@ -129,30 +133,31 @@ class AlbyWallet(Wallet):
if r.is_error:
error_message = data["message"] if "message" in data else r.text
return PaymentResponse(None, None, None, None, error_message)
return PaymentResponse(error_message=error_message)
checking_id = data["payment_hash"]
# todo: confirm with bitkarrot that having the minus is fine
# other funding sources return a positive fee value
fee_msat = -data["fee"]
preimage = data["payment_preimage"]
return PaymentResponse(True, checking_id, fee_msat, preimage, None)
return PaymentResponse(
ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage
)
except KeyError as exc:
logger.warning(exc)
return PaymentResponse(
None, None, None, None, "Server error: 'missing required fields'"
error_message="Server error: 'missing required fields'"
)
except json.JSONDecodeError as exc:
logger.warning(exc)
return PaymentResponse(
None, None, None, None, "Server error: 'invalid json response'"
error_message="Server error: 'invalid json response'"
)
except Exception as exc:
logger.info(f"Failed to pay invoice {bolt11}")
logger.warning(exc)
return PaymentResponse(
None, None, None, None, f"Unable to connect to {self.endpoint}."
error_message=f"Unable to connect to {self.endpoint}."
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:

View file

@ -135,7 +135,7 @@ class BlinkWallet(Wallet):
)
if len(errors) > 0:
error_message = errors[0].get("message")
return InvoiceResponse(False, None, None, error_message)
return InvoiceResponse(ok=False, error_message=error_message)
payment_request = (
response.get("data", {})
@ -150,15 +150,18 @@ class BlinkWallet(Wallet):
.get("paymentHash", None)
)
return InvoiceResponse(True, checking_id, payment_request, None)
# TODO: add preimage to response
return InvoiceResponse(
ok=True, checking_id=checking_id, payment_request=payment_request
)
except json.JSONDecodeError:
return InvoiceResponse(
False, None, None, "Server error: 'invalid json response'"
ok=False, error_message="Server error: 'invalid json response'"
)
except Exception as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, f"Unable to connect to {self.endpoint}."
ok=False, error_message=f"Unable to connect to {self.endpoint}."
)
async def pay_invoice(
@ -185,19 +188,21 @@ class BlinkWallet(Wallet):
)
if len(errors) > 0:
error_message = errors[0].get("message")
return PaymentResponse(False, None, None, None, error_message)
return PaymentResponse(ok=False, error_message=error_message)
checking_id = bolt11.decode(bolt11_invoice).payment_hash
payment_status = await self.get_payment_status(checking_id)
fee_msat = payment_status.fee_msat
preimage = payment_status.preimage
return PaymentResponse(True, checking_id, fee_msat, preimage, None)
return PaymentResponse(
ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage
)
except Exception as exc:
logger.info(f"Failed to pay invoice {bolt11_invoice}")
logger.warning(exc)
return PaymentResponse(
None, None, None, None, f"Unable to connect to {self.endpoint}."
error_message=f"Unable to connect to {self.endpoint}."
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:

View file

@ -182,15 +182,16 @@ else:
)
)
# TODO: add preimage
return InvoiceResponse(
True,
breez_invoice.ln_invoice.payment_hash,
breez_invoice.ln_invoice.bolt11,
None,
ok=True,
checking_id=breez_invoice.ln_invoice.payment_hash,
payment_request=breez_invoice.ln_invoice.bolt11,
# preimage=breez_invoice.ln_invoice.payment_preimage,
)
except Exception as e:
logger.warning(e)
return InvoiceResponse(False, None, None, str(e))
return InvoiceResponse(ok=False, error_message=str(e))
async def pay_invoice(
self, bolt11: str, fee_limit_msat: int
@ -217,22 +218,19 @@ else:
except Exception as ex:
logger.info(ex)
# assume that payment failed?
return PaymentResponse(
False, None, None, None, f"payment failed: {exc}"
)
return PaymentResponse(ok=False, error_message=f"payment failed: {exc}")
if payment.status != breez_sdk.PaymentStatus.COMPLETE:
return PaymentResponse(False, None, None, None, "payment is pending")
return PaymentResponse(ok=False, error_message="payment is pending")
# let's use the payment_hash as the checking_id
checking_id = invoice.payment_hash
return PaymentResponse(
True,
checking_id,
payment.fee_msat,
payment.details.data.payment_preimage,
None,
ok=True,
checking_id=checking_id,
fee_msat=payment.fee_msat,
preimage=payment.details.data.payment_preimage,
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:

View file

@ -54,7 +54,7 @@ class ClicheWallet(Wallet):
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
**kwargs,
**_,
) -> InvoiceResponse:
if unhashed_description or description_hash:
description_hash_str = (
@ -79,12 +79,13 @@ class ClicheWallet(Wallet):
data = json.loads(r)
checking_id = None
payment_request = None
error_message = None
if data.get("error") is not None and data["error"].get("message"):
logger.error(data["error"]["message"])
error_message = data["error"]["message"]
return InvoiceResponse(False, checking_id, payment_request, error_message)
return InvoiceResponse(
ok=False, checking_id=checking_id, error_message=error_message
)
if data.get("result") is not None:
checking_id, payment_request = (
@ -92,15 +93,18 @@ class ClicheWallet(Wallet):
data["result"]["invoice"],
)
else:
return InvoiceResponse(False, None, None, "Could not get payment hash")
return InvoiceResponse(ok=False, error_message="Could not get payment hash")
return InvoiceResponse(True, checking_id, payment_request, error_message)
return InvoiceResponse(
ok=True,
checking_id=checking_id,
payment_request=payment_request,
)
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
ws = create_connection(self.endpoint)
ws.send(f"pay-invoice --invoice {bolt11}")
checking_id, fee_msat, preimage, error_message, payment_ok = (
None,
checking_id, fee_msat, preimage, payment_ok = (
None,
None,
None,
@ -109,8 +113,7 @@ class ClicheWallet(Wallet):
for _ in range(2):
r = ws.recv()
data = json.loads(r)
checking_id, fee_msat, preimage, error_message, payment_ok = (
None,
checking_id, fee_msat, preimage, payment_ok = (
None,
None,
None,
@ -119,7 +122,7 @@ class ClicheWallet(Wallet):
if data.get("error") is not None:
error_message = data["error"].get("message")
return PaymentResponse(False, None, None, None, error_message)
return PaymentResponse(ok=False, error_message=error_message)
if data.get("method") == "payment_succeeded":
payment_ok = True
@ -129,10 +132,10 @@ class ClicheWallet(Wallet):
continue
if data.get("result") is None:
return PaymentResponse(None)
return PaymentResponse(error_message="result is None")
return PaymentResponse(
payment_ok, checking_id, fee_msat, preimage, error_message
ok=payment_ok, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:

View file

@ -115,35 +115,38 @@ class CoreLightningWallet(Wallet):
if r.get("code") and r.get("code") < 0: # type: ignore
raise Exception(r.get("message"))
return InvoiceResponse(True, r["payment_hash"], r["bolt11"], None)
return InvoiceResponse(
ok=True,
checking_id=r["payment_hash"],
payment_request=r["bolt11"],
)
except RpcError as exc:
logger.warning(exc)
error_message = f"RPC '{exc.method}' failed with '{exc.error}'."
return InvoiceResponse(False, None, None, error_message)
return InvoiceResponse(ok=False, error_message=error_message)
except KeyError as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, "Server error: 'missing required fields'"
ok=False, error_message="Server error: 'missing required fields'"
)
except Exception as e:
logger.warning(e)
return InvoiceResponse(False, None, None, str(e))
return InvoiceResponse(ok=False, error_message=str(e))
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
try:
invoice = bolt11_decode(bolt11)
except Bolt11Exception as exc:
return PaymentResponse(False, None, None, None, str(exc))
return PaymentResponse(ok=False, error_message=str(exc))
try:
previous_payment = await self.get_payment_status(invoice.payment_hash)
if previous_payment.paid:
return PaymentResponse(False, None, None, None, "invoice already paid")
return PaymentResponse(ok=False, error_message="invoice already paid")
if not invoice.amount_msat or invoice.amount_msat <= 0:
return PaymentResponse(
False, None, None, None, "CLN 0 amount invoice not supported"
ok=False, error_message="CLN 0 amount invoice not supported"
)
# maxfee overrides both maxfeepercent and exemptfee defaults (and
@ -170,23 +173,23 @@ class CoreLightningWallet(Wallet):
if error_code in self.pay_failure_error_codes:
error_message = exc.error.get("message", error_code) # type: ignore
return PaymentResponse(
False, None, None, None, f"Payment failed: {error_message}"
ok=False, error_message=f"Payment failed: {error_message}"
)
else:
error_message = f"Payment failed: {exc.error}"
return PaymentResponse(None, None, None, None, error_message)
return PaymentResponse(error_message=error_message)
except Exception:
error_message = f"RPC '{exc.method}' failed with '{exc.error}'."
return PaymentResponse(None, None, None, None, error_message)
return PaymentResponse(error_message=error_message)
except KeyError as exc:
logger.warning(exc)
return PaymentResponse(
None, None, None, None, "Server error: 'missing required fields'"
error_message="Server error: 'missing required fields'"
)
except Exception as exc:
logger.info(f"Failed to pay invoice {bolt11}")
logger.warning(exc)
return PaymentResponse(None, None, None, None, f"Payment failed: '{exc}'.")
return PaymentResponse(error_message=f"Payment failed: '{exc}'.")
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
try:

View file

@ -1,7 +1,7 @@
import asyncio
import json
import random
from typing import AsyncGenerator, Dict, Optional
from typing import AsyncGenerator, Optional
import httpx
from bolt11 import Bolt11Exception
@ -109,7 +109,7 @@ class CoreLightningRestWallet(Wallet):
**kwargs,
) -> InvoiceResponse:
label = kwargs.get("label", f"lbl{random.random()}")
data: Dict = {
data: dict = {
"amount": amount * 1000,
"description": memo,
"label": label,
@ -139,41 +139,47 @@ class CoreLightningRestWallet(Wallet):
data = r.json()
if len(data) == 0:
return InvoiceResponse(False, None, None, "no data")
return InvoiceResponse(ok=False, error_message="no data")
if "error" in data:
return InvoiceResponse(
False, None, None, f"""Server error: '{data["error"]}'"""
ok=False, error_message=f"""Server error: '{data["error"]}'"""
)
if r.is_error:
return InvoiceResponse(False, None, None, f"Server error: '{r.text}'")
return InvoiceResponse(
ok=False, error_message=f"Server error: '{r.text}'"
)
if "payment_hash" not in data or "bolt11" not in data:
return InvoiceResponse(
False, None, None, "Server error: 'missing required fields'"
ok=False, error_message="Server error: 'missing required fields'"
)
return InvoiceResponse(True, data["payment_hash"], data["bolt11"], None)
return InvoiceResponse(
ok=True,
checking_id=data["payment_hash"],
payment_request=data["bolt11"],
)
except json.JSONDecodeError:
return InvoiceResponse(
False, None, None, "Server error: 'invalid json response'"
ok=False, error_message="Server error: 'invalid json response'"
)
except Exception as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, f"Unable to connect to {self.url}."
ok=False, error_message=f"Unable to connect to {self.url}."
)
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
try:
invoice = decode(bolt11)
except Bolt11Exception as exc:
return PaymentResponse(False, None, None, None, str(exc))
return PaymentResponse(ok=False, error_message=str(exc))
if not invoice.amount_msat or invoice.amount_msat <= 0:
error_message = "0 amount invoices are not allowed"
return PaymentResponse(False, None, None, None, error_message)
return PaymentResponse(ok=False, error_message=error_message)
try:
r = await self.client.post(
f"{self.url}/v1/pay",
@ -190,18 +196,16 @@ class CoreLightningRestWallet(Wallet):
status = self.statuses.get(data["status"])
if "payment_preimage" not in data:
return PaymentResponse(
status,
None,
None,
None,
data.get("error"),
ok=status, error_message=data.get("error") or "unknown error"
)
checking_id = data["payment_hash"]
preimage = data["payment_preimage"]
fee_msat = data["msatoshi_sent"] - data["msatoshi"]
return PaymentResponse(status, checking_id, fee_msat, preimage, None)
return PaymentResponse(
ok=status, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage
)
except httpx.HTTPStatusError as exc:
try:
logger.debug(exc)
@ -209,28 +213,26 @@ class CoreLightningRestWallet(Wallet):
error_code = int(data["error"]["code"])
if error_code in self.pay_failure_error_codes:
error_message = f"Payment failed: {data['error']['message']}"
return PaymentResponse(False, None, None, None, error_message)
return PaymentResponse(ok=False, error_message=error_message)
error_message = f"REST failed with {data['error']['message']}."
return PaymentResponse(None, None, None, None, error_message)
return PaymentResponse(error_message=error_message)
except Exception as exc:
error_message = f"Unable to connect to {self.url}."
return PaymentResponse(None, None, None, None, error_message)
return PaymentResponse(error_message=error_message)
except json.JSONDecodeError:
return PaymentResponse(
None, None, None, None, "Server error: 'invalid json response'"
error_message="Server error: 'invalid json response'"
)
except KeyError as exc:
logger.warning(exc)
return PaymentResponse(
None, None, None, None, "Server error: 'missing required fields'"
error_message="Server error: 'missing required fields'"
)
except Exception as exc:
logger.info(f"Failed to pay invoice {bolt11}")
logger.warning(exc)
return PaymentResponse(
None, None, None, None, f"Unable to connect to {self.url}."
)
return PaymentResponse(error_message=f"Unable to connect to {self.url}.")
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
r = await self.client.get(

View file

@ -3,7 +3,7 @@ import base64
import hashlib
import json
import urllib.parse
from typing import Any, AsyncGenerator, Dict, Optional
from typing import Any, AsyncGenerator, Optional
import httpx
from loguru import logger
@ -85,7 +85,7 @@ class EclairWallet(Wallet):
unhashed_description: Optional[bytes] = None,
**kwargs,
) -> InvoiceResponse:
data: Dict[str, Any] = {
data: dict[str, Any] = {
"amountMsat": amount * 1000,
}
if kwargs.get("expiry"):
@ -105,30 +105,35 @@ class EclairWallet(Wallet):
data = r.json()
if len(data) == 0:
return InvoiceResponse(False, None, None, "no data")
return InvoiceResponse(ok=False, error_message="no data")
if "error" in data:
return InvoiceResponse(
False, None, None, f"""Server error: '{data["error"]}'"""
ok=False, error_message=f"""Server error: '{data["error"]}'"""
)
if r.is_error:
return InvoiceResponse(False, None, None, f"Server error: '{r.text}'")
return InvoiceResponse(True, data["paymentHash"], data["serialized"], None)
return InvoiceResponse(
ok=False, error_message=f"Server error: '{r.text}'"
)
return InvoiceResponse(
ok=True,
checking_id=data["paymentHash"],
payment_request=data["serialized"],
)
except json.JSONDecodeError:
return InvoiceResponse(
False, None, None, "Server error: 'invalid json response'"
ok=False, error_message="Server error: 'invalid json response'"
)
except KeyError as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, "Server error: 'missing required fields'"
ok=False, error_message="Server error: 'missing required fields'"
)
except Exception as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, f"Unable to connect to {self.url}."
ok=False, error_message=f"Unable to connect to {self.url}."
)
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
@ -142,35 +147,36 @@ class EclairWallet(Wallet):
data = r.json()
if "error" in data:
return PaymentResponse(None, None, None, None, data["error"])
return PaymentResponse(error_message=data["error"])
if r.is_error:
return PaymentResponse(None, None, None, None, r.text)
return PaymentResponse(error_message=r.text)
if data["type"] == "payment-failed":
return PaymentResponse(False, None, None, None, "payment failed")
return PaymentResponse(ok=False, error_message="payment failed")
checking_id = data["paymentHash"]
preimage = data["paymentPreimage"]
except json.JSONDecodeError:
return PaymentResponse(
None, None, None, None, "Server error: 'invalid json response'"
error_message="Server error: 'invalid json response'"
)
except KeyError:
return PaymentResponse(
None, None, None, None, "Server error: 'missing required fields'"
error_message="Server error: 'missing required fields'"
)
except Exception as exc:
logger.info(f"Failed to pay invoice {bolt11}")
logger.warning(exc)
return PaymentResponse(
None, None, None, None, f"Unable to connect to {self.url}."
)
return PaymentResponse(error_message=f"Unable to connect to {self.url}.")
payment_status: PaymentStatus = await self.get_payment_status(checking_id)
success = True if payment_status.success else None
return PaymentResponse(
success, checking_id, payment_status.fee_msat, preimage, None
ok=success,
checking_id=checking_id,
fee_msat=payment_status.fee_msat,
preimage=preimage,
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:

View file

@ -92,23 +92,25 @@ class LNbitsWallet(Wallet):
if r.is_error or not payment_str:
error_message = data["detail"] if "detail" in data else r.text
return InvoiceResponse(
False, None, None, f"Server error: '{error_message}'"
ok=False, error_message=f"Server error: '{error_message}'"
)
return InvoiceResponse(True, data["checking_id"], payment_str, None)
return InvoiceResponse(
ok=True, checking_id=data["checking_id"], payment_request=payment_str
)
except json.JSONDecodeError:
return InvoiceResponse(
False, None, None, "Server error: 'invalid json response'"
ok=False, error_message="Server error: 'invalid json response'"
)
except KeyError as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, "Server error: 'missing required fields'"
ok=False, error_message="Server error: 'missing required fields'"
)
except Exception as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, f"Unable to connect to {self.endpoint}."
ok=False, error_message=f"Unable to connect to {self.endpoint}."
)
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
@ -129,7 +131,10 @@ class LNbitsWallet(Wallet):
success = True if payment.success else None
return PaymentResponse(
success, checking_id, payment.fee_msat, payment.preimage
ok=success,
checking_id=checking_id,
fee_msat=payment.fee_msat,
preimage=payment.preimage,
)
except httpx.HTTPStatusError as exc:
@ -138,25 +143,25 @@ class LNbitsWallet(Wallet):
data = exc.response.json()
error_message = f"Payment {data['status']}: {data['detail']}."
if data["status"] == "failed":
return PaymentResponse(False, None, None, None, error_message)
return PaymentResponse(None, None, None, None, error_message)
return PaymentResponse(ok=False, error_message=error_message)
return PaymentResponse(error_message=error_message)
except Exception as exc:
error_message = f"Unable to connect to {self.endpoint}."
return PaymentResponse(None, None, None, None, error_message)
return PaymentResponse(error_message=error_message)
except json.JSONDecodeError:
return PaymentResponse(
None, None, None, None, "Server error: 'invalid json response'"
error_message="Server error: 'invalid json response'"
)
except KeyError:
return PaymentResponse(
None, None, None, None, "Server error: 'missing required fields'"
error_message="Server error: 'missing required fields'"
)
except Exception as exc:
logger.info(f"Failed to pay invoice {bolt11}")
logger.warning(exc)
return PaymentResponse(
None, None, None, None, f"Unable to connect to {self.endpoint}."
error_message=f"Unable to connect to {self.endpoint}."
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:

View file

@ -149,11 +149,13 @@ class LndWallet(Wallet):
except Exception as exc:
logger.warning(exc)
error_message = str(exc)
return InvoiceResponse(False, None, None, error_message)
return InvoiceResponse(ok=False, error_message=error_message)
checking_id = bytes_to_hex(resp.r_hash)
payment_request = str(resp.payment_request)
return InvoiceResponse(True, checking_id, payment_request, None)
return InvoiceResponse(
ok=True, checking_id=checking_id, payment_request=payment_request
)
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
# fee_limit_fixed = ln.FeeLimit(fixed=fee_limit_msat // 1000)
@ -167,7 +169,7 @@ class LndWallet(Wallet):
resp = await self.routerpc.SendPaymentV2(req).read()
except Exception as exc:
logger.warning(exc)
return PaymentResponse(None, None, None, None, str(exc))
return PaymentResponse(error_message=str(exc))
# PaymentStatus from https://github.com/lightningnetwork/lnd/blob/master/channeldb/payments.go#L178
statuses = {
@ -199,7 +201,11 @@ class LndWallet(Wallet):
error_message = failure_reasons[resp.failure_reason]
return PaymentResponse(
statuses[resp.status], checking_id, fee_msat, preimage, error_message
ok=statuses[resp.status],
checking_id=checking_id,
fee_msat=fee_msat,
preimage=preimage,
error_message=error_message,
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:

View file

@ -132,34 +132,40 @@ class LndRestWallet(Wallet):
data = r.json()
if len(data) == 0:
return InvoiceResponse(False, None, None, "no data")
return InvoiceResponse(ok=False, error_message="no data")
if "error" in data:
return InvoiceResponse(
False, None, None, f"""Server error: '{data["error"]}'"""
ok=False, error_message=f"""Server error: '{data["error"]}'"""
)
if r.is_error:
return InvoiceResponse(False, None, None, f"Server error: '{r.text}'")
return InvoiceResponse(
ok=False, error_message=f"Server error: '{r.text}'"
)
if "payment_request" not in data or "r_hash" not in data:
return InvoiceResponse(
False, None, None, "Server error: 'missing required fields'"
ok=False, error_message="Server error: 'missing required fields'"
)
payment_request = data["payment_request"]
payment_hash = base64.b64decode(data["r_hash"]).hex()
checking_id = payment_hash
return InvoiceResponse(
ok=True,
checking_id=checking_id,
payment_request=payment_request,
)
return InvoiceResponse(True, checking_id, payment_request, None)
except json.JSONDecodeError:
return InvoiceResponse(
False, None, None, "Server error: 'invalid json response'"
ok=False, error_message="Server error: 'invalid json response'"
)
except Exception as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, f"Unable to connect to {self.endpoint}."
ok=False, error_message=f"Unable to connect to {self.endpoint}."
)
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
@ -185,25 +191,27 @@ class LndRestWallet(Wallet):
payment_error = data.get("payment_error")
if payment_error:
logger.warning(f"LndRestWallet payment_error: {payment_error}.")
return PaymentResponse(False, None, None, None, payment_error)
return PaymentResponse(ok=False, error_message=payment_error)
checking_id = base64.b64decode(data["payment_hash"]).hex()
fee_msat = int(data["payment_route"]["total_fees_msat"])
preimage = base64.b64decode(data["payment_preimage"]).hex()
return PaymentResponse(True, checking_id, fee_msat, preimage, None)
return PaymentResponse(
ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage
)
except KeyError as exc:
logger.warning(exc)
return PaymentResponse(
None, None, None, None, "Server error: 'missing required fields'"
error_message="Server error: 'missing required fields'"
)
except json.JSONDecodeError:
return PaymentResponse(
None, None, None, None, "Server error: 'invalid json response'"
error_message="Server error: 'invalid json response'"
)
except Exception as exc:
logger.warning(f"LndRestWallet pay_invoice POST error: {exc}.")
return PaymentResponse(
None, None, None, None, f"Unable to connect to {self.endpoint}."
error_message=f"Unable to connect to {self.endpoint}."
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:

View file

@ -91,20 +91,17 @@ class LNPayWallet(Wallet):
json=data,
timeout=60,
)
ok, checking_id, payment_request, error_message = (
r.status_code == 201,
None,
None,
r.text,
)
if ok:
if r.status_code == 201:
data = r.json()
checking_id, payment_request = data["id"], data["payment_request"]
self.pending_invoices.append(checking_id)
return InvoiceResponse(ok, checking_id, payment_request, error_message)
self.pending_invoices.append(data["id"])
return InvoiceResponse(
ok=True,
payment_request=data["payment_request"],
)
return InvoiceResponse(
ok=False,
error_message=r.text,
)
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
r = await self.client.post(
@ -116,17 +113,17 @@ class LNPayWallet(Wallet):
try:
data = r.json()
except Exception:
return PaymentResponse(
False, None, 0, None, f"Got invalid JSON: {r.text[:200]}"
)
return PaymentResponse(ok=False, error_message="Got invalid JSON.")
if r.is_error:
return PaymentResponse(False, None, None, None, data["message"])
return PaymentResponse(ok=False, error_message=data["message"])
checking_id = data["lnTx"]["id"]
fee_msat = 0
preimage = data["lnTx"]["payment_preimage"]
return PaymentResponse(True, checking_id, fee_msat, preimage, None)
return PaymentResponse(
ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
return await self.get_payment_status(checking_id)

View file

@ -70,7 +70,7 @@ class LnTipsWallet(Wallet):
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
**kwargs,
**_,
) -> InvoiceResponse:
data: Dict = {"amount": amount, "description_hash": "", "memo": memo or ""}
if description_hash:
@ -91,11 +91,13 @@ class LnTipsWallet(Wallet):
except Exception:
error_message = r.text
return InvoiceResponse(False, None, None, error_message)
return InvoiceResponse(ok=False, error_message=error_message)
data = r.json()
return InvoiceResponse(
True, data["payment_hash"], data["payment_request"], None
ok=True,
checking_id=data["payment_hash"],
payment_request=data["payment_request"],
)
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
@ -105,7 +107,7 @@ class LnTipsWallet(Wallet):
timeout=None,
)
if r.is_error:
return PaymentResponse(False, None, 0, None, r.text)
return PaymentResponse(ok=False, error_message=r.text)
if "error" in r.json():
try:
@ -113,13 +115,15 @@ class LnTipsWallet(Wallet):
error_message = data["error"]
except Exception:
error_message = r.text
return PaymentResponse(False, None, 0, None, error_message)
return PaymentResponse(ok=False, error_message=error_message)
data = r.json()["details"]
checking_id = data["payment_hash"]
fee_msat = -data["fee"]
preimage = data["preimage"]
return PaymentResponse(True, checking_id, fee_msat, preimage, None)
return PaymentResponse(
ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
try:

View file

@ -132,7 +132,7 @@ class NWCWallet(Wallet):
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
**kwargs,
**_,
) -> InvoiceResponse:
desc = ""
desc_hash = None
@ -148,10 +148,8 @@ class NWCWallet(Wallet):
info = await self.conn.get_info()
if "make_invoice" not in info["supported_methods"]:
return InvoiceResponse(
False,
None,
None,
"make_invoice is not supported by this NWC service.",
ok=False,
error_message="make_invoice is not supported by this NWC service.",
)
resp = await self.conn.call(
"make_invoice",
@ -175,7 +173,9 @@ class NWCWallet(Wallet):
"expired": False,
}
)
return InvoiceResponse(True, checking_id, payment_request, None)
return InvoiceResponse(
ok=True, checking_id=checking_id, payment_request=payment_request
)
except Exception as e:
return InvoiceResponse(ok=False, error_message=str(e))
@ -203,7 +203,9 @@ class NWCWallet(Wallet):
if "lookup_invoice" not in info["supported_methods"]:
# if not supported, we assume it succeeded
return PaymentResponse(True, payment_hash, None, preimage, None)
return PaymentResponse(
ok=True, checking_id=payment_hash, preimage=preimage, fee_msat=0
)
try:
payment_data = await self.conn.call(
@ -213,15 +215,20 @@ class NWCWallet(Wallet):
"preimage", None
)
if not settled:
return PaymentResponse(None, payment_hash, None, None, None)
return PaymentResponse(checking_id=payment_hash)
else:
fee_msat = payment_data.get("fees_paid", None)
return PaymentResponse(True, payment_hash, fee_msat, preimage, None)
return PaymentResponse(
ok=True,
checking_id=payment_hash,
fee_msat=fee_msat,
preimage=preimage,
)
except Exception:
# Workaround: some nwc service providers might not store the invoice
# right away, so this call may raise an exception.
# We will assume the payment is pending anyway
return PaymentResponse(None, payment_hash, None, None, None)
return PaymentResponse(checking_id=payment_hash)
except NWCError as e:
logger.error("Error paying invoice: " + str(e))
failure_codes = [
@ -237,13 +244,14 @@ class NWCWallet(Wallet):
]
failed = e.code in failure_codes
return PaymentResponse(
None if not failed else False,
ok=None if not failed else False,
error_message=e.message if failed else None,
)
except Exception as e:
logger.error("Error paying invoice: " + str(e))
msg = "Error paying invoice: " + str(e)
logger.error(msg)
# assume pending
return PaymentResponse(None)
return PaymentResponse(error_message=msg)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
return await self.get_payment_status(checking_id)
@ -300,6 +308,7 @@ class NWCConnection:
self.account_private_key = secp256k1.PrivateKey(bytes.fromhex(secret))
self.account_private_key_hex = secret
self.account_public_key = self.account_private_key.pubkey
assert self.account_public_key
self.account_public_key_hex = self.account_public_key.serialize().hex()[2:]
# Extract service key (used for encryption to identify the nwc service provider)

View file

@ -72,7 +72,7 @@ class OpenNodeWallet(Wallet):
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
**kwargs,
**_,
) -> InvoiceResponse:
if description_hash or unhashed_description:
raise UnsupportedError("description_hash")
@ -88,13 +88,15 @@ class OpenNodeWallet(Wallet):
if r.is_error:
error_message = r.json()["message"]
return InvoiceResponse(False, None, None, error_message)
return InvoiceResponse(ok=False, error_message=error_message)
data = r.json()["data"]
checking_id = data["id"]
payment_request = data["lightning_invoice"]["payreq"]
self.pending_invoices.append(checking_id)
return InvoiceResponse(True, checking_id, payment_request, None)
return InvoiceResponse(
ok=True, checking_id=checking_id, payment_request=payment_request
)
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
r = await self.client.post(
@ -105,16 +107,15 @@ class OpenNodeWallet(Wallet):
if r.is_error:
error_message = r.json()["message"]
return PaymentResponse(False, None, None, None, error_message)
return PaymentResponse(ok=False, error_message=error_message)
data = r.json()["data"]
checking_id = data["id"]
fee_msat = -data["fee"] * 1000
# pending
if data["status"] != "paid":
return PaymentResponse(None, checking_id, fee_msat, None, "payment failed")
return PaymentResponse(True, checking_id, fee_msat, None, None)
return PaymentResponse(ok=None, checking_id=checking_id, fee_msat=fee_msat)
return PaymentResponse(ok=True, checking_id=checking_id, fee_msat=fee_msat)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
r = await self.client.get(f"/v1/charge/{checking_id}")

View file

@ -133,25 +133,29 @@ class PhoenixdWallet(Wallet):
if r.is_error or "paymentHash" not in data:
error_message = data["message"]
return InvoiceResponse(
False, None, None, f"Server error: '{error_message}'"
ok=False, error_message=f"Server error: '{error_message}'"
)
checking_id = data["paymentHash"]
payment_request = data["serialized"]
return InvoiceResponse(True, checking_id, payment_request, None)
return InvoiceResponse(
ok=True,
checking_id=checking_id,
payment_request=payment_request,
)
except json.JSONDecodeError:
return InvoiceResponse(
False, None, None, "Server error: 'invalid json response'"
ok=False, error_message="Server error: 'invalid json response'"
)
except KeyError as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, "Server error: 'missing required fields'"
ok=False, error_message="Server error: 'missing required fields'"
)
except Exception as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, f"Unable to connect to {self.endpoint}."
ok=False, error_message=f"Unable to connect to {self.endpoint}."
)
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
@ -168,31 +172,35 @@ class PhoenixdWallet(Wallet):
data = r.json()
if "routingFeeSat" not in data and "reason" in data:
return PaymentResponse(None, None, None, None, data["reason"])
return PaymentResponse(error_message=data["reason"])
if r.is_error or "paymentHash" not in data:
error_message = data["message"] if "message" in data else r.text
return PaymentResponse(None, None, None, None, error_message)
return PaymentResponse(error_message=error_message)
checking_id = data["paymentHash"]
fee_msat = -int(data["routingFeeSat"]) * 1000
preimage = data["paymentPreimage"]
return PaymentResponse(True, checking_id, fee_msat, preimage, None)
return PaymentResponse(
ok=True,
checking_id=checking_id,
fee_msat=fee_msat,
preimage=preimage,
)
except json.JSONDecodeError:
return PaymentResponse(
None, None, None, None, "Server error: 'invalid json response'"
error_message="Server error: 'invalid json response'"
)
except KeyError:
return PaymentResponse(
None, None, None, None, "Server error: 'missing required fields'"
error_message="Server error: 'missing required fields'"
)
except Exception as exc:
logger.info(f"Failed to pay invoice {bolt11}")
logger.warning(exc)
return PaymentResponse(
None, None, None, None, f"Unable to connect to {self.endpoint}."
error_message=f"Unable to connect to {self.endpoint}."
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:

View file

@ -115,8 +115,6 @@ class SparkWallet(Wallet):
**kwargs,
) -> InvoiceResponse:
label = f"lbs{random.random()}"
checking_id = label
try:
if description_hash:
r = await self.invoicewithdescriptionhash(
@ -138,11 +136,13 @@ class SparkWallet(Wallet):
exposeprivatechannels=True,
expiry=kwargs.get("expiry"),
)
ok, payment_request, error_message = True, r["bolt11"], ""
return InvoiceResponse(
ok=True,
payment_request=r["bolt11"],
checking_id=label,
)
except (SparkError, UnknownError) as e:
ok, payment_request, error_message = False, None, str(e)
return InvoiceResponse(ok, checking_id, payment_request, error_message)
return InvoiceResponse(ok=False, error_message=str(e))
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
try:
@ -152,17 +152,22 @@ class SparkWallet(Wallet):
)
fee_msat = -int(r["msatoshi_sent"] - r["msatoshi"])
preimage = r["payment_preimage"]
return PaymentResponse(True, r["payment_hash"], fee_msat, preimage, None)
return PaymentResponse(
ok=True,
checking_id=r["payment_hash"],
fee_msat=fee_msat,
preimage=preimage,
)
except (SparkError, UnknownError) as exc:
listpays = await self.listpays(bolt11)
if not listpays:
return PaymentResponse(False, None, None, None, str(exc))
return PaymentResponse(ok=False, error_message=str(exc))
pays = listpays["pays"]
if len(pays) == 0:
return PaymentResponse(False, None, None, None, str(exc))
return PaymentResponse(ok=False, error_message=str(exc))
pay = pays[0]
payment_hash = pay["payment_hash"]
@ -174,10 +179,10 @@ class SparkWallet(Wallet):
) from exc
if pay["status"] == "failed":
return PaymentResponse(False, None, None, None, str(exc))
return PaymentResponse(ok=False, error_message=str(exc))
if pay["status"] == "pending":
return PaymentResponse(None, payment_hash, None, None, None)
return PaymentResponse(ok=None, checking_id=payment_hash)
if pay["status"] == "complete":
r = pay
@ -190,10 +195,13 @@ class SparkWallet(Wallet):
fee_msat = -int(r["msatoshi_sent"] - r["msatoshi"])
preimage = r["payment_preimage"]
return PaymentResponse(
True, r["payment_hash"], fee_msat, preimage, None
ok=True,
checking_id=r["payment_hash"],
fee_msat=fee_msat,
preimage=preimage,
)
else:
return PaymentResponse(False, None, None, None, str(exc))
return PaymentResponse(ok=False, error_message=str(exc))
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
try:

View file

@ -3,9 +3,9 @@ import hashlib
from typing import AsyncGenerator, Dict, Optional
import httpx
from bolt11 import decode as bolt11_decode
from loguru import logger
from lnbits import bolt11
from lnbits.settings import settings
from .base import (
@ -61,7 +61,7 @@ class ZBDWallet(Wallet):
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
**kwargs,
**_,
) -> InvoiceResponse:
# https://api.zebedee.io/v0/charges
@ -89,21 +89,23 @@ class ZBDWallet(Wallet):
if r.is_error:
error_message = r.json()["message"]
return InvoiceResponse(False, None, None, error_message)
return InvoiceResponse(ok=False, error_message=error_message)
data = r.json()["data"]
checking_id = data["id"] # this is a zbd id
payment_request = data["invoice"]["request"]
return InvoiceResponse(True, checking_id, payment_request, None)
return InvoiceResponse(
ok=True,
checking_id=checking_id,
payment_request=payment_request,
)
async def pay_invoice(
self, bolt11_invoice: str, fee_limit_msat: int
) -> PaymentResponse:
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
# https://api.zebedee.io/v0/payments
r = await self.client.post(
"payments",
json={
"invoice": bolt11_invoice,
"invoice": bolt11,
"description": "",
"amount": "",
"internalId": "",
@ -114,15 +116,17 @@ class ZBDWallet(Wallet):
if r.is_error:
error_message = r.json()["message"]
return PaymentResponse(False, None, None, None, error_message)
return PaymentResponse(ok=False, error_message=error_message)
data = r.json()
checking_id = bolt11.decode(bolt11_invoice).payment_hash
checking_id = bolt11_decode(bolt11).payment_hash
fee_msat = -int(data["data"]["fee"])
preimage = data["data"]["preimage"]
return PaymentResponse(True, checking_id, fee_msat, preimage, None)
return PaymentResponse(
ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
r = await self.client.get(f"charges/{checking_id}")
@ -137,7 +141,7 @@ class ZBDWallet(Wallet):
"expired": False,
"completed": True,
}
return PaymentStatus(statuses[data.get("status")])
return PaymentStatus(paid=statuses[data.get("status")])
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
r = await self.client.get(f"payments/{checking_id}")
@ -155,7 +159,7 @@ class ZBDWallet(Wallet):
"failed": False,
}
return PaymentStatus(statuses[data.get("status")], fee_msat=None, preimage=None)
return PaymentStatus(paid=statuses[data.get("status")])
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
self.queue: asyncio.Queue = asyncio.Queue(0)