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

View file

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

View file

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

View file

@ -54,7 +54,7 @@ class ClicheWallet(Wallet):
memo: Optional[str] = None, memo: Optional[str] = None,
description_hash: Optional[bytes] = None, description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None, unhashed_description: Optional[bytes] = None,
**kwargs, **_,
) -> InvoiceResponse: ) -> InvoiceResponse:
if unhashed_description or description_hash: if unhashed_description or description_hash:
description_hash_str = ( description_hash_str = (
@ -79,12 +79,13 @@ class ClicheWallet(Wallet):
data = json.loads(r) data = json.loads(r)
checking_id = None checking_id = None
payment_request = None payment_request = None
error_message = None
if data.get("error") is not None and data["error"].get("message"): if data.get("error") is not None and data["error"].get("message"):
logger.error(data["error"]["message"]) logger.error(data["error"]["message"])
error_message = 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: if data.get("result") is not None:
checking_id, payment_request = ( checking_id, payment_request = (
@ -92,15 +93,18 @@ class ClicheWallet(Wallet):
data["result"]["invoice"], data["result"]["invoice"],
) )
else: 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: async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
ws = create_connection(self.endpoint) ws = create_connection(self.endpoint)
ws.send(f"pay-invoice --invoice {bolt11}") ws.send(f"pay-invoice --invoice {bolt11}")
checking_id, fee_msat, preimage, error_message, payment_ok = ( checking_id, fee_msat, preimage, payment_ok = (
None,
None, None,
None, None,
None, None,
@ -109,8 +113,7 @@ class ClicheWallet(Wallet):
for _ in range(2): for _ in range(2):
r = ws.recv() r = ws.recv()
data = json.loads(r) data = json.loads(r)
checking_id, fee_msat, preimage, error_message, payment_ok = ( checking_id, fee_msat, preimage, payment_ok = (
None,
None, None,
None, None,
None, None,
@ -119,7 +122,7 @@ class ClicheWallet(Wallet):
if data.get("error") is not None: if data.get("error") is not None:
error_message = data["error"].get("message") 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": if data.get("method") == "payment_succeeded":
payment_ok = True payment_ok = True
@ -129,10 +132,10 @@ class ClicheWallet(Wallet):
continue continue
if data.get("result") is None: if data.get("result") is None:
return PaymentResponse(None) return PaymentResponse(error_message="result is None")
return PaymentResponse( 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: 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 if r.get("code") and r.get("code") < 0: # type: ignore
raise Exception(r.get("message")) raise Exception(r.get("message"))
return InvoiceResponse(
return InvoiceResponse(True, r["payment_hash"], r["bolt11"], None) ok=True,
checking_id=r["payment_hash"],
payment_request=r["bolt11"],
)
except RpcError as exc: except RpcError as exc:
logger.warning(exc) logger.warning(exc)
error_message = f"RPC '{exc.method}' failed with '{exc.error}'." 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: except KeyError as exc:
logger.warning(exc) logger.warning(exc)
return InvoiceResponse( return InvoiceResponse(
False, None, None, "Server error: 'missing required fields'" ok=False, error_message="Server error: 'missing required fields'"
) )
except Exception as e: except Exception as e:
logger.warning(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: async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
try: try:
invoice = bolt11_decode(bolt11) invoice = bolt11_decode(bolt11)
except Bolt11Exception as exc: except Bolt11Exception as exc:
return PaymentResponse(False, None, None, None, str(exc)) return PaymentResponse(ok=False, error_message=str(exc))
try: try:
previous_payment = await self.get_payment_status(invoice.payment_hash) previous_payment = await self.get_payment_status(invoice.payment_hash)
if previous_payment.paid: 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: if not invoice.amount_msat or invoice.amount_msat <= 0:
return PaymentResponse( 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 # maxfee overrides both maxfeepercent and exemptfee defaults (and
@ -170,23 +173,23 @@ class CoreLightningWallet(Wallet):
if error_code in self.pay_failure_error_codes: if error_code in self.pay_failure_error_codes:
error_message = exc.error.get("message", error_code) # type: ignore error_message = exc.error.get("message", error_code) # type: ignore
return PaymentResponse( return PaymentResponse(
False, None, None, None, f"Payment failed: {error_message}" ok=False, error_message=f"Payment failed: {error_message}"
) )
else: else:
error_message = f"Payment failed: {exc.error}" error_message = f"Payment failed: {exc.error}"
return PaymentResponse(None, None, None, None, error_message) return PaymentResponse(error_message=error_message)
except Exception: except Exception:
error_message = f"RPC '{exc.method}' failed with '{exc.error}'." 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: except KeyError as exc:
logger.warning(exc) logger.warning(exc)
return PaymentResponse( return PaymentResponse(
None, None, None, None, "Server error: 'missing required fields'" error_message="Server error: 'missing required fields'"
) )
except Exception as exc: except Exception as exc:
logger.info(f"Failed to pay invoice {bolt11}") logger.info(f"Failed to pay invoice {bolt11}")
logger.warning(exc) 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: async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
try: try:

View file

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

View file

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

View file

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

View file

@ -132,34 +132,40 @@ class LndRestWallet(Wallet):
data = r.json() data = r.json()
if len(data) == 0: if len(data) == 0:
return InvoiceResponse(False, None, None, "no data") return InvoiceResponse(ok=False, error_message="no data")
if "error" in data: if "error" in data:
return InvoiceResponse( return InvoiceResponse(
False, None, None, f"""Server error: '{data["error"]}'""" ok=False, error_message=f"""Server error: '{data["error"]}'"""
) )
if r.is_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: if "payment_request" not in data or "r_hash" not in data:
return InvoiceResponse( 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_request = data["payment_request"]
payment_hash = base64.b64decode(data["r_hash"]).hex() payment_hash = base64.b64decode(data["r_hash"]).hex()
checking_id = payment_hash 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: except json.JSONDecodeError:
return InvoiceResponse( return InvoiceResponse(
False, None, None, "Server error: 'invalid json response'" ok=False, error_message="Server error: 'invalid json response'"
) )
except Exception as exc: except Exception as exc:
logger.warning(exc) logger.warning(exc)
return InvoiceResponse( 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: 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") payment_error = data.get("payment_error")
if payment_error: if payment_error:
logger.warning(f"LndRestWallet payment_error: {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() checking_id = base64.b64decode(data["payment_hash"]).hex()
fee_msat = int(data["payment_route"]["total_fees_msat"]) fee_msat = int(data["payment_route"]["total_fees_msat"])
preimage = base64.b64decode(data["payment_preimage"]).hex() 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: except KeyError as exc:
logger.warning(exc) logger.warning(exc)
return PaymentResponse( return PaymentResponse(
None, None, None, None, "Server error: 'missing required fields'" error_message="Server error: 'missing required fields'"
) )
except json.JSONDecodeError: except json.JSONDecodeError:
return PaymentResponse( return PaymentResponse(
None, None, None, None, "Server error: 'invalid json response'" error_message="Server error: 'invalid json response'"
) )
except Exception as exc: except Exception as exc:
logger.warning(f"LndRestWallet pay_invoice POST error: {exc}.") logger.warning(f"LndRestWallet pay_invoice POST error: {exc}.")
return PaymentResponse( 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: async def get_invoice_status(self, checking_id: str) -> PaymentStatus:

View file

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

View file

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

View file

@ -132,7 +132,7 @@ class NWCWallet(Wallet):
memo: Optional[str] = None, memo: Optional[str] = None,
description_hash: Optional[bytes] = None, description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None, unhashed_description: Optional[bytes] = None,
**kwargs, **_,
) -> InvoiceResponse: ) -> InvoiceResponse:
desc = "" desc = ""
desc_hash = None desc_hash = None
@ -148,10 +148,8 @@ class NWCWallet(Wallet):
info = await self.conn.get_info() info = await self.conn.get_info()
if "make_invoice" not in info["supported_methods"]: if "make_invoice" not in info["supported_methods"]:
return InvoiceResponse( return InvoiceResponse(
False, ok=False,
None, error_message="make_invoice is not supported by this NWC service.",
None,
"make_invoice is not supported by this NWC service.",
) )
resp = await self.conn.call( resp = await self.conn.call(
"make_invoice", "make_invoice",
@ -175,7 +173,9 @@ class NWCWallet(Wallet):
"expired": False, "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: except Exception as e:
return InvoiceResponse(ok=False, error_message=str(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 "lookup_invoice" not in info["supported_methods"]:
# if not supported, we assume it succeeded # 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: try:
payment_data = await self.conn.call( payment_data = await self.conn.call(
@ -213,15 +215,20 @@ class NWCWallet(Wallet):
"preimage", None "preimage", None
) )
if not settled: if not settled:
return PaymentResponse(None, payment_hash, None, None, None) return PaymentResponse(checking_id=payment_hash)
else: else:
fee_msat = payment_data.get("fees_paid", None) 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: except Exception:
# Workaround: some nwc service providers might not store the invoice # Workaround: some nwc service providers might not store the invoice
# right away, so this call may raise an exception. # right away, so this call may raise an exception.
# We will assume the payment is pending anyway # 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: except NWCError as e:
logger.error("Error paying invoice: " + str(e)) logger.error("Error paying invoice: " + str(e))
failure_codes = [ failure_codes = [
@ -237,13 +244,14 @@ class NWCWallet(Wallet):
] ]
failed = e.code in failure_codes failed = e.code in failure_codes
return PaymentResponse( return PaymentResponse(
None if not failed else False, ok=None if not failed else False,
error_message=e.message if failed else None, error_message=e.message if failed else None,
) )
except Exception as e: except Exception as e:
logger.error("Error paying invoice: " + str(e)) msg = "Error paying invoice: " + str(e)
logger.error(msg)
# assume pending # assume pending
return PaymentResponse(None) return PaymentResponse(error_message=msg)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus: async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
return await self.get_payment_status(checking_id) 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 = secp256k1.PrivateKey(bytes.fromhex(secret))
self.account_private_key_hex = secret self.account_private_key_hex = secret
self.account_public_key = self.account_private_key.pubkey 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:] self.account_public_key_hex = self.account_public_key.serialize().hex()[2:]
# Extract service key (used for encryption to identify the nwc service provider) # 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, memo: Optional[str] = None,
description_hash: Optional[bytes] = None, description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None, unhashed_description: Optional[bytes] = None,
**kwargs, **_,
) -> InvoiceResponse: ) -> InvoiceResponse:
if description_hash or unhashed_description: if description_hash or unhashed_description:
raise UnsupportedError("description_hash") raise UnsupportedError("description_hash")
@ -88,13 +88,15 @@ class OpenNodeWallet(Wallet):
if r.is_error: if r.is_error:
error_message = r.json()["message"] error_message = r.json()["message"]
return InvoiceResponse(False, None, None, error_message) return InvoiceResponse(ok=False, error_message=error_message)
data = r.json()["data"] data = r.json()["data"]
checking_id = data["id"] checking_id = data["id"]
payment_request = data["lightning_invoice"]["payreq"] payment_request = data["lightning_invoice"]["payreq"]
self.pending_invoices.append(checking_id) 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: async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
r = await self.client.post( r = await self.client.post(
@ -105,16 +107,15 @@ class OpenNodeWallet(Wallet):
if r.is_error: if r.is_error:
error_message = r.json()["message"] 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"] data = r.json()["data"]
checking_id = data["id"] checking_id = data["id"]
fee_msat = -data["fee"] * 1000 fee_msat = -data["fee"] * 1000
# pending
if data["status"] != "paid": if data["status"] != "paid":
return PaymentResponse(None, checking_id, fee_msat, None, "payment failed") return PaymentResponse(ok=None, checking_id=checking_id, fee_msat=fee_msat)
return PaymentResponse(ok=True, checking_id=checking_id, fee_msat=fee_msat)
return PaymentResponse(True, checking_id, fee_msat, None, None)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus: async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
r = await self.client.get(f"/v1/charge/{checking_id}") 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: if r.is_error or "paymentHash" not in data:
error_message = data["message"] error_message = data["message"]
return InvoiceResponse( return InvoiceResponse(
False, None, None, f"Server error: '{error_message}'" ok=False, error_message=f"Server error: '{error_message}'"
) )
checking_id = data["paymentHash"] checking_id = data["paymentHash"]
payment_request = data["serialized"] 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: except json.JSONDecodeError:
return InvoiceResponse( return InvoiceResponse(
False, None, None, "Server error: 'invalid json response'" ok=False, error_message="Server error: 'invalid json response'"
) )
except KeyError as exc: except KeyError as exc:
logger.warning(exc) logger.warning(exc)
return InvoiceResponse( return InvoiceResponse(
False, None, None, "Server error: 'missing required fields'" ok=False, error_message="Server error: 'missing required fields'"
) )
except Exception as exc: except Exception as exc:
logger.warning(exc) logger.warning(exc)
return InvoiceResponse( 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: async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
@ -168,31 +172,35 @@ class PhoenixdWallet(Wallet):
data = r.json() data = r.json()
if "routingFeeSat" not in data and "reason" in data: 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: if r.is_error or "paymentHash" not in data:
error_message = data["message"] if "message" in data else r.text 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"] checking_id = data["paymentHash"]
fee_msat = -int(data["routingFeeSat"]) * 1000 fee_msat = -int(data["routingFeeSat"]) * 1000
preimage = data["paymentPreimage"] preimage = data["paymentPreimage"]
return PaymentResponse(
return PaymentResponse(True, checking_id, fee_msat, preimage, None) ok=True,
checking_id=checking_id,
fee_msat=fee_msat,
preimage=preimage,
)
except json.JSONDecodeError: except json.JSONDecodeError:
return PaymentResponse( return PaymentResponse(
None, None, None, None, "Server error: 'invalid json response'" error_message="Server error: 'invalid json response'"
) )
except KeyError: except KeyError:
return PaymentResponse( return PaymentResponse(
None, None, None, None, "Server error: 'missing required fields'" error_message="Server error: 'missing required fields'"
) )
except Exception as exc: except Exception as exc:
logger.info(f"Failed to pay invoice {bolt11}") logger.info(f"Failed to pay invoice {bolt11}")
logger.warning(exc) logger.warning(exc)
return PaymentResponse( 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: async def get_invoice_status(self, checking_id: str) -> PaymentStatus:

View file

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

View file

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