mirror of
https://github.com/lnbits/lnbits.git
synced 2026-08-15 13:00:14 +02:00
fix: payout hardening/remove ambiguity
This commit is contained in:
parent
9aedfdcc9b
commit
aa62b44cdd
24 changed files with 645 additions and 256 deletions
|
|
@ -4,6 +4,8 @@ import json
|
|||
from collections.abc import AsyncGenerator
|
||||
|
||||
import httpx
|
||||
from bolt11 import Bolt11Exception
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.helpers import normalize_endpoint
|
||||
|
|
@ -124,6 +126,11 @@ class AlbyWallet(Wallet):
|
|||
)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
try:
|
||||
checking_id = bolt11_decode(bolt11).payment_hash
|
||||
except Bolt11Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
try:
|
||||
# https://api.getalby.com/payments/bolt11
|
||||
r = await self.client.post(
|
||||
|
|
@ -136,9 +143,12 @@ class AlbyWallet(Wallet):
|
|||
|
||||
if r.is_error:
|
||||
error_message = data["message"] if "message" in data else r.text
|
||||
return PaymentResponse(error_message=error_message)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
|
||||
checking_id = data["payment_hash"]
|
||||
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"]
|
||||
|
|
@ -149,18 +159,21 @@ class AlbyWallet(Wallet):
|
|||
except KeyError as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
error_message="Server error: 'missing required fields'"
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'missing required fields'",
|
||||
)
|
||||
except json.JSONDecodeError as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
error_message="Server error: 'invalid json response'"
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'invalid json response'",
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.info(f"Failed to pay invoice {bolt11}")
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
error_message=f"Unable to connect to {self.endpoint}."
|
||||
checking_id=checking_id,
|
||||
error_message=f"Unable to connect to {self.endpoint}.",
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
|
|
|
|||
|
|
@ -167,6 +167,10 @@ class BlinkWallet(Wallet):
|
|||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
# https://dev.blink.sv/api/btc-ln-send
|
||||
# Future: add check fee estimate is < fee_limit_msat before paying invoice
|
||||
try:
|
||||
checking_id = bolt11_lib.decode(bolt11).payment_hash
|
||||
except Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
payment_variables = {
|
||||
"input": {
|
||||
|
|
@ -178,29 +182,38 @@ class BlinkWallet(Wallet):
|
|||
data = {"query": q.payment_query, "variables": payment_variables}
|
||||
try:
|
||||
response = await self._graphql_query(data)
|
||||
|
||||
errors = (
|
||||
response.get("data", {})
|
||||
.get("lnInvoicePaymentSend", {})
|
||||
.get("errors", {})
|
||||
)
|
||||
if len(errors) > 0:
|
||||
error_message = errors[0].get("message")
|
||||
return PaymentResponse(ok=False, error_message=error_message)
|
||||
|
||||
checking_id = bolt11_lib.decode(bolt11).payment_hash
|
||||
payment_data = response.get("data", {}).get("lnInvoicePaymentSend", {})
|
||||
errors = payment_data.get("errors") or []
|
||||
status = payment_data.get("status")
|
||||
if status == "FAILURE":
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=errors[0].get("message") if errors else None,
|
||||
)
|
||||
|
||||
payment_status = await self.get_payment_status(checking_id)
|
||||
fee_msat = payment_status.fee_msat
|
||||
preimage = payment_status.preimage
|
||||
if status == "SUCCESS" or payment_status.success:
|
||||
ok = True
|
||||
elif payment_status.failed:
|
||||
ok = False
|
||||
else:
|
||||
ok = None
|
||||
return PaymentResponse(
|
||||
ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage
|
||||
ok=ok,
|
||||
checking_id=checking_id,
|
||||
fee_msat=fee_msat,
|
||||
preimage=preimage,
|
||||
error_message=errors[0].get("message") if errors else None,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.info(f"Failed to pay invoice {bolt11}")
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
error_message=f"Unable to connect to {self.endpoint}."
|
||||
checking_id=checking_id,
|
||||
error_message=f"Unable to connect to {self.endpoint}.",
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
|
|
|
|||
|
|
@ -123,8 +123,14 @@ class BoltzWallet(Wallet):
|
|||
fee_msat=fee_msat,
|
||||
)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
|
||||
async def pay_invoice( # noqa: C901
|
||||
self, bolt11: str, fee_limit_msat: int
|
||||
) -> PaymentResponse:
|
||||
try:
|
||||
invoice = decode(bolt11)
|
||||
except Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
checking_id = invoice.payment_hash
|
||||
pair = boltzrpc_pb2.Pair(**{"from": boltzrpc_pb2.LBTC})
|
||||
try:
|
||||
pair_info: boltzrpc_pb2.PairInfo
|
||||
|
|
@ -132,18 +138,32 @@ class BoltzWallet(Wallet):
|
|||
type=boltzrpc_pb2.SUBMARINE, pair=pair
|
||||
)
|
||||
pair_info = await self.rpc.GetPairInfo(pair_request, metadata=self.metadata)
|
||||
invoice = decode(bolt11)
|
||||
except AioRpcError as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=exc.details(),
|
||||
)
|
||||
|
||||
if not invoice.amount_msat:
|
||||
raise ValueError("amountless invoice")
|
||||
if not invoice.amount_msat:
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message="amountless invoice",
|
||||
)
|
||||
|
||||
service_fee: float = invoice.amount_msat * pair_info.fees.percentage / 100
|
||||
estimate = int(service_fee + pair_info.fees.miner_fees * 1000)
|
||||
if estimate > fee_limit_msat:
|
||||
error = f"fee of {estimate} msat exceeds limit of {fee_limit_msat} msat"
|
||||
|
||||
return PaymentResponse(ok=False, error_message=error)
|
||||
service_fee: float = invoice.amount_msat * pair_info.fees.percentage / 100
|
||||
estimate = int(service_fee + pair_info.fees.miner_fees * 1000)
|
||||
if estimate > fee_limit_msat:
|
||||
error = f"fee of {estimate} msat exceeds limit of {fee_limit_msat} msat"
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=error,
|
||||
)
|
||||
|
||||
try:
|
||||
request = boltzrpc_pb2.CreateSwapRequest(
|
||||
invoice=bolt11,
|
||||
pair=pair,
|
||||
|
|
@ -163,10 +183,13 @@ class BoltzWallet(Wallet):
|
|||
logger.warning(
|
||||
"Boltz invoice paid directly on liquid network using magic routing"
|
||||
)
|
||||
return PaymentResponse(ok=True, checking_id=invoice.payment_hash)
|
||||
return PaymentResponse(ok=True, checking_id=checking_id)
|
||||
except AioRpcError as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(ok=False, error_message=exc.details())
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=exc.details(),
|
||||
)
|
||||
|
||||
try:
|
||||
info_request = boltzrpc_pb2.GetSwapInfoRequest(id=response.id)
|
||||
|
|
@ -182,18 +205,26 @@ class BoltzWallet(Wallet):
|
|||
)
|
||||
return PaymentResponse(
|
||||
ok=True,
|
||||
checking_id=invoice.payment_hash,
|
||||
checking_id=checking_id,
|
||||
fee_msat=fee_msat,
|
||||
preimage=info.swap.preimage,
|
||||
)
|
||||
elif info.swap.error != "":
|
||||
return PaymentResponse(ok=False, error_message=info.swap.error)
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=info.swap.error,
|
||||
)
|
||||
return PaymentResponse(
|
||||
ok=False, error_message="stream stopped unexpectedly"
|
||||
checking_id=checking_id,
|
||||
error_message="stream stopped unexpectedly",
|
||||
)
|
||||
except AioRpcError as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(ok=False, error_message=exc.details())
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=exc.details(),
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -238,16 +238,29 @@ else:
|
|||
self.sdk_services.report_issue(payment_error) # type: ignore[arg-type]
|
||||
except Exception as ex:
|
||||
logger.info(ex)
|
||||
return PaymentResponse(error_message=f"exception while payment {exc!s}")
|
||||
|
||||
if payment.status != BreezPaymentStatus.COMPLETE:
|
||||
return PaymentResponse(ok=None, error_message="payment is pending")
|
||||
return PaymentResponse(
|
||||
checking_id=invoice.payment_hash,
|
||||
error_message=f"exception while payment {exc!s}",
|
||||
)
|
||||
|
||||
# let's use the payment_hash as the checking_id
|
||||
checking_id = invoice.payment_hash
|
||||
if payment.status == BreezPaymentStatus.FAILED:
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message="payment failed",
|
||||
)
|
||||
if payment.status != BreezPaymentStatus.COMPLETE:
|
||||
return PaymentResponse(
|
||||
ok=None,
|
||||
checking_id=checking_id,
|
||||
error_message="payment is pending",
|
||||
)
|
||||
|
||||
if not isinstance(payment.details, PaymentDetails.LN):
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message="Breez SDK returned a non-LN payment details object",
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -173,7 +173,11 @@ else:
|
|||
async def pay_invoice(
|
||||
self, bolt11: str, fee_limit_msat: int
|
||||
) -> PaymentResponse:
|
||||
invoice_data = bolt11_decode(bolt11)
|
||||
try:
|
||||
invoice_data = bolt11_decode(bolt11)
|
||||
except Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
checking_id = invoice_data.payment_hash
|
||||
|
||||
try:
|
||||
prepare_req = PrepareSendRequest(destination=bolt11)
|
||||
|
|
@ -186,23 +190,33 @@ else:
|
|||
if req.fees_sat and req.fees_sat > fee_limit_sat:
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=(
|
||||
f"fee of {req.fees_sat} sat exceeds limit of "
|
||||
f"{fee_limit_sat} sat"
|
||||
),
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=f"Exception while preparing payment: {exc}",
|
||||
)
|
||||
|
||||
try:
|
||||
send_response = self.sdk_services.send_payment(
|
||||
SendPaymentRequest(prepare_response=req)
|
||||
)
|
||||
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(error_message=f"Exception while payment: {exc}")
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=f"Exception while payment: {exc}",
|
||||
)
|
||||
|
||||
payment: Payment = send_response.payment
|
||||
logger.debug(f"pay invoice res: {payment}")
|
||||
checking_id = invoice_data.payment_hash
|
||||
|
||||
fees = req.fees_sat * 1000 if req.fees_sat and req.fees_sat > 0 else 0
|
||||
|
||||
|
|
@ -211,7 +225,8 @@ else:
|
|||
|
||||
if not isinstance(payment.details, PaymentDetails.LIGHTNING):
|
||||
return PaymentResponse(
|
||||
error_message="lightning payment details are not available"
|
||||
checking_id=checking_id,
|
||||
error_message="lightning payment details are not available",
|
||||
)
|
||||
|
||||
return PaymentResponse(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import hashlib
|
|||
import json
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
from bolt11 import Bolt11Exception
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from loguru import logger
|
||||
from websocket import create_connection
|
||||
|
||||
|
|
@ -104,37 +106,37 @@ class ClicheWallet(Wallet):
|
|||
)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
try:
|
||||
checking_id = bolt11_decode(bolt11).payment_hash
|
||||
except Bolt11Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
ws = create_connection(self.endpoint)
|
||||
ws.send(f"pay-invoice --invoice {bolt11}")
|
||||
checking_id, fee_msat, preimage, payment_ok = (
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
fee_msat, preimage, payment_ok = None, None, None
|
||||
for _ in range(2):
|
||||
r = ws.recv()
|
||||
data = json.loads(r)
|
||||
checking_id, fee_msat, preimage, payment_ok = (
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
||||
if data.get("error") is not None:
|
||||
error_message = data["error"].get("message")
|
||||
return PaymentResponse(ok=False, error_message=error_message)
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
|
||||
if data.get("method") == "payment_succeeded":
|
||||
payment_ok = True
|
||||
checking_id = data["params"]["payment_hash"]
|
||||
fee_msat = data["params"]["fee_msatoshi"]
|
||||
preimage = data["params"]["preimage"]
|
||||
continue
|
||||
|
||||
if data.get("result") is None:
|
||||
return PaymentResponse(error_message="result is None")
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message="result is None",
|
||||
)
|
||||
|
||||
return PaymentResponse(
|
||||
ok=payment_ok, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage
|
||||
|
|
|
|||
|
|
@ -255,15 +255,19 @@ class CLNRestWallet(Wallet):
|
|||
invoice = decode(bolt11)
|
||||
except Bolt11Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
checking_id = invoice.payment_hash
|
||||
|
||||
if not invoice.amount_msat or invoice.amount_msat <= 0:
|
||||
return PaymentResponse(
|
||||
ok=False, error_message="0 amount invoices are not allowed"
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message="0 amount invoices are not allowed",
|
||||
)
|
||||
|
||||
if not settings.clnrest_pay_rune and not settings.clnrest_renepay_rune:
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message="Unable to pay invoice without a pay or renepay rune",
|
||||
)
|
||||
|
||||
|
|
@ -296,11 +300,16 @@ class CLNRestWallet(Wallet):
|
|||
if "payment_preimage" not in data:
|
||||
error_message = data.get("error", "No payment preimage in response")
|
||||
logger.warning(error_message)
|
||||
return PaymentResponse(error_message=error_message)
|
||||
failed = self.statuses.get(data.get("status")) is False
|
||||
return PaymentResponse(
|
||||
ok=False if failed else None,
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
|
||||
return PaymentResponse(
|
||||
ok=self.statuses.get(data["status"]),
|
||||
checking_id=data["payment_hash"],
|
||||
checking_id=checking_id,
|
||||
fee_msat=data["amount_sent_msat"] - data["amount_msat"],
|
||||
preimage=data["payment_preimage"],
|
||||
)
|
||||
|
|
@ -311,18 +320,31 @@ class CLNRestWallet(Wallet):
|
|||
error_code = int(error.get("code", 0))
|
||||
error_message = error.get("message", "Unknown error")
|
||||
if error_code in self.pay_failure_error_codes:
|
||||
return PaymentResponse(ok=False, error_message=error_message)
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
else:
|
||||
return PaymentResponse(error_message=error_message)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
except Exception:
|
||||
error_message = f"Error parsing response from {self.url}: {exc!s}"
|
||||
logger.warning(error_message)
|
||||
return PaymentResponse(error_message=error_message)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.info(f"Failed to pay invoice {bolt11}")
|
||||
logger.warning(exc)
|
||||
error_message = f"Unable to connect to {self.url}."
|
||||
return PaymentResponse(error_message=error_message)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
data: dict = {"payment_hash": checking_id}
|
||||
|
|
|
|||
|
|
@ -152,15 +152,14 @@ class CoreLightningWallet(Wallet):
|
|||
invoice = bolt11_decode(bolt11)
|
||||
except Bolt11Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
checking_id = invoice.payment_hash
|
||||
|
||||
try:
|
||||
previous_payment = await self.get_payment_status(invoice.payment_hash)
|
||||
if previous_payment.paid:
|
||||
return PaymentResponse(ok=False, error_message="invoice already paid")
|
||||
|
||||
if not invoice.amount_msat or invoice.amount_msat <= 0:
|
||||
return PaymentResponse(
|
||||
ok=False, error_message="CLN 0 amount invoice not supported"
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message="CLN 0 amount invoice not supported",
|
||||
)
|
||||
|
||||
# maxfee overrides both maxfeepercent and exemptfee defaults (and
|
||||
|
|
@ -178,7 +177,7 @@ class CoreLightningWallet(Wallet):
|
|||
|
||||
fee_msat = -int(r["amount_sent_msat"] - r["amount_msat"])
|
||||
return PaymentResponse(
|
||||
True, r["payment_hash"], fee_msat, r["payment_preimage"], None
|
||||
True, checking_id, fee_msat, r["payment_preimage"], None
|
||||
)
|
||||
except RpcError as exc:
|
||||
logger.warning(exc)
|
||||
|
|
@ -187,23 +186,35 @@ class CoreLightningWallet(Wallet):
|
|||
if error_code in self.pay_failure_error_codes:
|
||||
error_message = exc.error.get("message", error_code) # type: ignore
|
||||
return PaymentResponse(
|
||||
ok=False, error_message=f"Payment failed: {error_message}"
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=f"Payment failed: {error_message}",
|
||||
)
|
||||
else:
|
||||
error_message = f"Payment failed: {exc.error}"
|
||||
return PaymentResponse(error_message=error_message)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
except Exception:
|
||||
error_message = f"RPC '{exc.method}' failed with '{exc.error}'."
|
||||
return PaymentResponse(error_message=error_message)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
except KeyError as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
error_message="Server error: 'missing required fields'"
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'missing required fields'",
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.info(f"Failed to pay invoice {bolt11}")
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(error_message=f"Payment failed: '{exc}'.")
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=f"Payment failed: '{exc}'.",
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
try:
|
||||
|
|
@ -239,7 +250,7 @@ class CoreLightningWallet(Wallet):
|
|||
return PaymentPendingStatus()
|
||||
if not r["pays"]:
|
||||
# no payment with this payment_hash is found
|
||||
return PaymentFailedStatus()
|
||||
return PaymentPendingStatus()
|
||||
|
||||
payment_resp = r["pays"][-1]
|
||||
|
||||
|
|
|
|||
|
|
@ -181,10 +181,15 @@ class CoreLightningRestWallet(Wallet):
|
|||
invoice = decode(bolt11)
|
||||
except Bolt11Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
checking_id = invoice.payment_hash
|
||||
|
||||
if not invoice.amount_msat or invoice.amount_msat <= 0:
|
||||
error_message = "0 amount invoices are not allowed"
|
||||
return PaymentResponse(ok=False, error_message=error_message)
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
try:
|
||||
r = await self.client.post(
|
||||
f"{self.url}/v1/pay",
|
||||
|
|
@ -201,10 +206,12 @@ class CoreLightningRestWallet(Wallet):
|
|||
status = self.statuses.get(data["status"])
|
||||
if "payment_preimage" not in data:
|
||||
return PaymentResponse(
|
||||
ok=status, error_message=data.get("error") or "unknown error"
|
||||
ok=False if status is False else None,
|
||||
checking_id=checking_id,
|
||||
error_message=data.get("error") or "unknown error",
|
||||
)
|
||||
|
||||
checking_id = data["payment_hash"]
|
||||
data["payment_hash"]
|
||||
preimage = data["payment_preimage"]
|
||||
fee_msat = data["msatoshi_sent"] - data["msatoshi"]
|
||||
|
||||
|
|
@ -218,26 +225,41 @@ class CoreLightningRestWallet(Wallet):
|
|||
error_code = int(data["error"]["code"])
|
||||
if error_code in self.pay_failure_error_codes:
|
||||
error_message = data["error"]["message"]
|
||||
return PaymentResponse(ok=False, error_message=error_message)
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
error_message = f"REST failed with {data['error']['message']}."
|
||||
return PaymentResponse(error_message=error_message)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
except Exception as exc:
|
||||
error_message = f"Unable to connect to {self.url}."
|
||||
return PaymentResponse(error_message=error_message)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return PaymentResponse(
|
||||
error_message="Server error: 'invalid json response'"
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'invalid json response'",
|
||||
)
|
||||
except KeyError as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
error_message="Server error: 'missing required fields'"
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'missing required fields'",
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.info(f"Failed to pay invoice {bolt11}")
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(error_message=f"Unable to connect to {self.url}.")
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=f"Unable to connect to {self.url}.",
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
r = await self.client.get(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ from decimal import Decimal
|
|||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from bolt11 import Bolt11Exception
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from loguru import logger
|
||||
from websockets import connect
|
||||
|
||||
|
|
@ -145,6 +147,11 @@ class EclairWallet(Wallet):
|
|||
)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
try:
|
||||
checking_id = bolt11_decode(bolt11).payment_hash
|
||||
except Bolt11Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
try:
|
||||
r = await self.client.post(
|
||||
"/payinvoice",
|
||||
|
|
@ -155,28 +162,39 @@ class EclairWallet(Wallet):
|
|||
data = r.json()
|
||||
|
||||
if "error" in data:
|
||||
return PaymentResponse(error_message=data["error"])
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id, error_message=data["error"]
|
||||
)
|
||||
if r.is_error:
|
||||
return PaymentResponse(error_message=r.text)
|
||||
return PaymentResponse(checking_id=checking_id, error_message=r.text)
|
||||
|
||||
if data["type"] == "payment-failed":
|
||||
return PaymentResponse(ok=False, error_message="payment failed")
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message="payment failed",
|
||||
)
|
||||
|
||||
checking_id = data["paymentHash"]
|
||||
data["paymentHash"]
|
||||
preimage = data["paymentPreimage"]
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return PaymentResponse(
|
||||
error_message="Server error: 'invalid json response'"
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'invalid json response'",
|
||||
)
|
||||
except KeyError:
|
||||
return PaymentResponse(
|
||||
error_message="Server error: 'missing required fields'"
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'missing required fields'",
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.info(f"Failed to pay invoice {bolt11}")
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(error_message=f"Unable to connect to {self.url}.")
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
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
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import json
|
|||
from collections.abc import AsyncGenerator
|
||||
|
||||
import httpx
|
||||
from bolt11 import Bolt11Exception
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from loguru import logger
|
||||
from websockets import connect
|
||||
|
||||
|
|
@ -118,6 +120,11 @@ class LNbitsWallet(Wallet):
|
|||
)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
try:
|
||||
checking_id = bolt11_decode(bolt11).payment_hash
|
||||
except Bolt11Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
try:
|
||||
r = await self.client.post(
|
||||
url="/api/v1/payments",
|
||||
|
|
@ -128,7 +135,7 @@ class LNbitsWallet(Wallet):
|
|||
r.raise_for_status()
|
||||
data = r.json()
|
||||
|
||||
checking_id = data["payment_hash"]
|
||||
data["payment_hash"]
|
||||
|
||||
# we do this to get the fee and preimage
|
||||
payment: PaymentStatus = await self.get_payment_status(checking_id)
|
||||
|
|
@ -147,25 +154,38 @@ class LNbitsWallet(Wallet):
|
|||
data = exc.response.json()
|
||||
error_message = f"Payment {data['status']}: {data['detail']}."
|
||||
if data["status"] == "failed":
|
||||
return PaymentResponse(ok=False, error_message=error_message)
|
||||
return PaymentResponse(error_message=error_message)
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
except Exception as exc:
|
||||
error_message = f"Unable to connect to {self.endpoint}."
|
||||
return PaymentResponse(error_message=error_message)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return PaymentResponse(
|
||||
error_message="Server error: 'invalid json response'"
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'invalid json response'",
|
||||
)
|
||||
except KeyError:
|
||||
return PaymentResponse(
|
||||
error_message="Server error: 'missing required fields'"
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'missing required fields'",
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.info(f"Failed to pay invoice {bolt11}")
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
error_message=f"Unable to connect to {self.endpoint}."
|
||||
checking_id=checking_id,
|
||||
error_message=f"Unable to connect to {self.endpoint}.",
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ from hashlib import sha256
|
|||
from os import environ
|
||||
|
||||
import grpc
|
||||
from bolt11 import Bolt11Exception
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.helpers import normalize_endpoint
|
||||
|
|
@ -186,6 +188,11 @@ class LndWallet(Wallet):
|
|||
)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
try:
|
||||
checking_id = bolt11_decode(bolt11).payment_hash
|
||||
except Bolt11Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
# fee_limit_fixed = ln.FeeLimit(fixed=fee_limit_msat // 1000)
|
||||
req = SendPaymentRequest(
|
||||
payment_request=bolt11,
|
||||
|
|
@ -200,30 +207,32 @@ class LndWallet(Wallet):
|
|||
res: Payment = await self.router_rpc.SendPaymentV2(req).read()
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(error_message=str(exc))
|
||||
return PaymentResponse(checking_id=checking_id, error_message=str(exc))
|
||||
|
||||
if res.status == Payment.PaymentStatus.SUCCEEDED:
|
||||
return PaymentResponse(
|
||||
ok=True,
|
||||
checking_id=res.payment_hash,
|
||||
checking_id=checking_id,
|
||||
fee_msat=abs(res.fee_msat),
|
||||
preimage=res.payment_preimage,
|
||||
)
|
||||
elif res.status == Payment.PaymentStatus.FAILED:
|
||||
error_message = PaymentFailureReason.Name(res.failure_reason)
|
||||
return PaymentResponse(
|
||||
ok=False, error_message=f"Payment failed: {error_message}"
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=f"Payment failed: {error_message}",
|
||||
)
|
||||
elif res.status == Payment.PaymentStatus.IN_FLIGHT:
|
||||
return PaymentResponse(
|
||||
ok=None,
|
||||
checking_id=res.payment_hash,
|
||||
checking_id=checking_id,
|
||||
error_message="Payment is IN_FLIGHT.",
|
||||
)
|
||||
else:
|
||||
return PaymentResponse(
|
||||
ok=None,
|
||||
checking_id=res.payment_hash,
|
||||
checking_id=checking_id,
|
||||
error_message="Payment is non-existant.",
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ from collections.abc import AsyncGenerator
|
|||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from bolt11 import Bolt11Exception
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.helpers import normalize_endpoint
|
||||
|
|
@ -145,6 +147,11 @@ class LndRestWallet(Wallet):
|
|||
)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
try:
|
||||
checking_id = bolt11_decode(bolt11).payment_hash
|
||||
except Bolt11Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
req = {
|
||||
"payment_request": bolt11,
|
||||
"fee_limit_msat": fee_limit_msat,
|
||||
|
|
@ -164,29 +171,36 @@ class LndRestWallet(Wallet):
|
|||
data = r.json()
|
||||
except json.JSONDecodeError:
|
||||
return PaymentResponse(
|
||||
error_message="Server error: 'invalid json response'"
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'invalid json response'",
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(f"LndRestWallet pay_invoice POST error: {exc}.")
|
||||
return PaymentResponse(
|
||||
error_message=f"Unable to connect to {self.endpoint}."
|
||||
checking_id=checking_id,
|
||||
error_message=f"Unable to connect to {self.endpoint}.",
|
||||
)
|
||||
|
||||
payment_error = data.get("payment_error")
|
||||
if payment_error:
|
||||
logger.warning(f"LndRestWallet payment_error: {payment_error}.")
|
||||
return PaymentResponse(ok=False, error_message=payment_error)
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=payment_error,
|
||||
)
|
||||
|
||||
try:
|
||||
payment = data["result"]
|
||||
status = payment["status"]
|
||||
checking_id = payment["payment_hash"]
|
||||
payment["payment_hash"]
|
||||
preimage = payment["payment_preimage"]
|
||||
fee_msat = abs(int(payment["fee_msat"]))
|
||||
except KeyError as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
error_message="Server error: 'missing required fields'"
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'missing required fields'",
|
||||
)
|
||||
|
||||
if status == "SUCCEEDED":
|
||||
|
|
@ -201,7 +215,7 @@ class LndRestWallet(Wallet):
|
|||
elif status == "IN_FLIGHT":
|
||||
return PaymentResponse(ok=None, checking_id=checking_id)
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
ok=None,
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'unknown payment status returned'",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import hashlib
|
|||
from collections.abc import AsyncGenerator
|
||||
|
||||
import httpx
|
||||
from bolt11 import Bolt11Exception
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.helpers import normalize_endpoint
|
||||
|
|
@ -38,6 +40,7 @@ class LNPayWallet(Wallet):
|
|||
)
|
||||
self.wallet_key = wallet_key
|
||||
self.endpoint = normalize_endpoint(settings.lnpay_api_endpoint)
|
||||
self.payment_ids: dict[str, str] = {}
|
||||
|
||||
headers = {
|
||||
"X-Api-Key": settings.lnpay_api_key,
|
||||
|
|
@ -105,33 +108,61 @@ class LNPayWallet(Wallet):
|
|||
)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
r = await self.client.post(
|
||||
f"/wallet/{self.wallet_key}/withdraw",
|
||||
json={"payment_request": bolt11},
|
||||
timeout=None,
|
||||
)
|
||||
try:
|
||||
checking_id = bolt11_decode(bolt11).payment_hash
|
||||
except Bolt11Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
try:
|
||||
r = await self.client.post(
|
||||
f"/wallet/{self.wallet_key}/withdraw",
|
||||
json={"payment_request": bolt11},
|
||||
timeout=None,
|
||||
)
|
||||
data = r.json()
|
||||
except Exception:
|
||||
return PaymentResponse(ok=False, error_message="Got invalid JSON.")
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message="Unable to determine payment status.",
|
||||
)
|
||||
|
||||
if r.is_error:
|
||||
return PaymentResponse(ok=False, error_message=data["message"])
|
||||
error_message = data.get("message", r.text)
|
||||
return PaymentResponse(
|
||||
ok=False if r.is_client_error else None,
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
|
||||
checking_id = data["lnTx"]["id"]
|
||||
fee_msat = 0
|
||||
preimage = data["lnTx"]["payment_preimage"]
|
||||
try:
|
||||
payment_data = data["lnTx"]
|
||||
provider_id = payment_data["id"]
|
||||
except (KeyError, TypeError) as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'missing required fields'",
|
||||
)
|
||||
|
||||
self.payment_ids[checking_id] = provider_id
|
||||
preimage = payment_data.get("payment_preimage")
|
||||
if not preimage:
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message="Payment status is pending.",
|
||||
)
|
||||
return PaymentResponse(
|
||||
ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage
|
||||
ok=True, checking_id=checking_id, fee_msat=0, preimage=preimage
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
return await self.get_payment_status(checking_id)
|
||||
|
||||
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
provider_id = self.payment_ids.get(checking_id, checking_id)
|
||||
r = await self.client.get(
|
||||
url=f"/lntx/{checking_id}",
|
||||
url=f"/lntx/{provider_id}",
|
||||
)
|
||||
|
||||
if r.is_error:
|
||||
|
|
@ -141,7 +172,10 @@ class LNPayWallet(Wallet):
|
|||
preimage = data["payment_preimage"]
|
||||
fee_msat = data["fee_msat"]
|
||||
statuses = {0: None, 1: True, -1: False}
|
||||
return PaymentStatus(statuses[data["settled"]], fee_msat, preimage)
|
||||
status = PaymentStatus(statuses[data["settled"]], fee_msat, preimage)
|
||||
if status.success or status.failed:
|
||||
self.payment_ids.pop(checking_id, None)
|
||||
return status
|
||||
|
||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
self.queue: asyncio.Queue = asyncio.Queue(0)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import time
|
|||
from collections.abc import AsyncGenerator
|
||||
|
||||
import httpx
|
||||
from bolt11 import Bolt11Exception
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.helpers import normalize_endpoint
|
||||
|
|
@ -103,26 +105,52 @@ class LnTipsWallet(Wallet):
|
|||
)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
r = await self.client.post(
|
||||
"/api/v1/payinvoice",
|
||||
json={"pay_req": bolt11},
|
||||
timeout=None,
|
||||
)
|
||||
try:
|
||||
checking_id = bolt11_decode(bolt11).payment_hash
|
||||
except Bolt11Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
try:
|
||||
r = await self.client.post(
|
||||
"/api/v1/payinvoice",
|
||||
json={"pay_req": bolt11},
|
||||
timeout=None,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=f"Unable to connect to {self.endpoint}.",
|
||||
)
|
||||
|
||||
if r.is_error:
|
||||
return PaymentResponse(ok=False, error_message=r.text)
|
||||
return PaymentResponse(
|
||||
ok=False if r.is_client_error else None,
|
||||
checking_id=checking_id,
|
||||
error_message=r.text,
|
||||
)
|
||||
|
||||
if "error" in r.json():
|
||||
try:
|
||||
data = r.json()
|
||||
try:
|
||||
data = r.json()
|
||||
if "error" in data:
|
||||
error_message = data["error"]
|
||||
except Exception:
|
||||
error_message = r.text
|
||||
return PaymentResponse(ok=False, error_message=error_message)
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
|
||||
details = data["details"]
|
||||
details["payment_hash"]
|
||||
fee_msat = -details["fee"]
|
||||
preimage = details["preimage"]
|
||||
except (json.JSONDecodeError, KeyError, TypeError) as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'invalid payment response'",
|
||||
)
|
||||
|
||||
data = r.json()["details"]
|
||||
checking_id = data["payment_hash"]
|
||||
fee_msat = -data["fee"]
|
||||
preimage = data["preimage"]
|
||||
return PaymentResponse(
|
||||
ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage
|
||||
)
|
||||
|
|
|
|||
|
|
@ -579,11 +579,14 @@ class NWCWallet(Wallet):
|
|||
return StatusResponse(str(e), 0)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
try:
|
||||
payment_hash = bolt11_decode(bolt11).payment_hash
|
||||
except Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
try:
|
||||
resp = await self.conn.call("pay_invoice", {"invoice": bolt11})
|
||||
preimage = resp.get("preimage", None)
|
||||
invoice_data = bolt11_decode(bolt11)
|
||||
payment_hash = invoice_data.payment_hash
|
||||
# pay_invoice doesn't return payment data, so we need
|
||||
# to call lookup_invoice too (if supported)
|
||||
await self.conn.get_info()
|
||||
|
|
@ -625,20 +628,19 @@ class NWCWallet(Wallet):
|
|||
"QUOTA_EXCEEDED",
|
||||
"RESTRICTED",
|
||||
"UNAUTHORIZED",
|
||||
"INTERNAL",
|
||||
"OTHER",
|
||||
"PAYMENT_FAILED",
|
||||
]
|
||||
failed = e.code in failure_codes
|
||||
return PaymentResponse(
|
||||
ok=None if not failed else False,
|
||||
error_message=e.message if failed else None,
|
||||
checking_id=payment_hash,
|
||||
error_message=e.message,
|
||||
)
|
||||
except Exception as e:
|
||||
msg = "Error paying invoice: " + str(e)
|
||||
logger.error(msg)
|
||||
# assume pending
|
||||
return PaymentResponse(error_message=msg)
|
||||
return PaymentResponse(checking_id=payment_hash, error_message=msg)
|
||||
|
||||
async def _get_status_via_transactions(
|
||||
self, checking_id: str, unpaid_filters: list[bool]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import asyncio
|
|||
from collections.abc import AsyncGenerator
|
||||
|
||||
import httpx
|
||||
from bolt11 import Bolt11Exception
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.exceptions import UnsupportedError
|
||||
|
|
@ -40,6 +42,7 @@ class OpenNodeWallet(Wallet):
|
|||
self.key = key
|
||||
|
||||
self.endpoint = normalize_endpoint(settings.opennode_api_endpoint)
|
||||
self.payment_ids: dict[str, str] = {}
|
||||
|
||||
headers = {
|
||||
"Authorization": self.key,
|
||||
|
|
@ -100,24 +103,66 @@ class OpenNodeWallet(Wallet):
|
|||
)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
r = await self.client.post(
|
||||
"/v2/withdrawals",
|
||||
json={"type": "ln", "address": bolt11},
|
||||
timeout=None,
|
||||
)
|
||||
try:
|
||||
checking_id = bolt11_decode(bolt11).payment_hash
|
||||
except Bolt11Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
try:
|
||||
r = await self.client.post(
|
||||
"/v2/withdrawals",
|
||||
json={"type": "ln", "address": bolt11},
|
||||
timeout=None,
|
||||
)
|
||||
data = r.json()
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message="Unable to determine payment status.",
|
||||
)
|
||||
|
||||
if r.is_error:
|
||||
error_message = r.json()["message"]
|
||||
error_message = data.get("message", r.text)
|
||||
logger.warning(error_message)
|
||||
return PaymentResponse(ok=None, error_message=error_message)
|
||||
return PaymentResponse(
|
||||
ok=False if r.is_client_error else None,
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
|
||||
data = r.json()["data"]
|
||||
checking_id = data["id"]
|
||||
fee_msat = -data["fee"] * 1000
|
||||
# pending
|
||||
if data["status"] != "paid":
|
||||
return PaymentResponse(ok=None, checking_id=checking_id, fee_msat=fee_msat)
|
||||
return PaymentResponse(ok=True, checking_id=checking_id, fee_msat=fee_msat)
|
||||
try:
|
||||
payment_data = data["data"]
|
||||
provider_id = payment_data["id"]
|
||||
except (KeyError, TypeError) as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'missing required fields'",
|
||||
)
|
||||
|
||||
self.payment_ids[checking_id] = provider_id
|
||||
try:
|
||||
fee_msat = -payment_data["fee"] * 1000
|
||||
status = payment_data["status"]
|
||||
except (KeyError, TypeError) as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'missing required fields'",
|
||||
)
|
||||
|
||||
if status in {"paid", "confirmed"}:
|
||||
return PaymentResponse(ok=True, checking_id=checking_id, fee_msat=fee_msat)
|
||||
if status in {"error", "failed"}:
|
||||
self.payment_ids.pop(checking_id, None)
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
fee_msat=fee_msat,
|
||||
error_message=payment_data.get("error") or "Payment failed.",
|
||||
)
|
||||
return PaymentResponse(ok=None, 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}")
|
||||
|
|
@ -128,21 +173,29 @@ class OpenNodeWallet(Wallet):
|
|||
return PaymentStatus(statuses[data.get("status")])
|
||||
|
||||
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
r = await self.client.get(f"/v1/withdrawal/{checking_id}")
|
||||
provider_id = self.payment_ids.get(checking_id, checking_id)
|
||||
r = await self.client.get(f"/v1/withdrawal/{provider_id}")
|
||||
|
||||
if r.is_error:
|
||||
return PaymentPendingStatus()
|
||||
|
||||
data = r.json()["data"]
|
||||
statuses = {
|
||||
"initial": None,
|
||||
"pending": None,
|
||||
"confirmed": True,
|
||||
"error": None,
|
||||
"failed": False,
|
||||
}
|
||||
fee_msat = -data.get("fee") * 1000
|
||||
return PaymentStatus(statuses[data.get("status")], fee_msat)
|
||||
try:
|
||||
data = r.json()["data"]
|
||||
statuses = {
|
||||
"initial": None,
|
||||
"pending": None,
|
||||
"confirmed": True,
|
||||
"error": False,
|
||||
"failed": False,
|
||||
}
|
||||
fee_msat = -data["fee"] * 1000
|
||||
status = PaymentStatus(statuses[data["status"]], fee_msat)
|
||||
if status.success or status.failed:
|
||||
self.payment_ids.pop(checking_id, None)
|
||||
return status
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentPendingStatus()
|
||||
|
||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
self.queue: asyncio.Queue = asyncio.Queue(0)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ from pathlib import Path
|
|||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from bolt11 import Bolt11Exception
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from embit.bip39 import mnemonic_is_valid
|
||||
from httpx import RequestError, TimeoutException
|
||||
from loguru import logger
|
||||
|
|
@ -191,6 +193,11 @@ class PhoenixdWallet(Wallet):
|
|||
)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
try:
|
||||
checking_id = bolt11_decode(bolt11).payment_hash
|
||||
except Bolt11Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
try:
|
||||
r = await self.client.post(
|
||||
"/payinvoice",
|
||||
|
|
@ -206,17 +213,26 @@ class PhoenixdWallet(Wallet):
|
|||
# be safe and return pending on timeouts
|
||||
msg = f"Timeout connecting to {self.endpoint}. keep pending..."
|
||||
logger.warning(msg)
|
||||
return PaymentResponse(ok=None, error_message=msg)
|
||||
return PaymentResponse(
|
||||
ok=None,
|
||||
checking_id=checking_id,
|
||||
error_message=msg,
|
||||
)
|
||||
except RequestError as exc:
|
||||
# RequestError is raised when the request never hit the destination server
|
||||
msg = f"Unable to connect to {self.endpoint}."
|
||||
logger.warning(msg)
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(ok=False, error_message=msg)
|
||||
return PaymentResponse(
|
||||
ok=None,
|
||||
checking_id=checking_id,
|
||||
error_message=msg,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
ok=None, error_message=f"Unable to connect to {self.endpoint}."
|
||||
ok=None,
|
||||
checking_id=checking_id,
|
||||
error_message=f"Unable to connect to {self.endpoint}.",
|
||||
)
|
||||
|
||||
try:
|
||||
|
|
@ -224,9 +240,12 @@ class PhoenixdWallet(Wallet):
|
|||
|
||||
if "routingFeeSat" not in data and ("reason" in data or "message" in data):
|
||||
error_message = data.get("reason", data.get("message", "Unknown error"))
|
||||
return PaymentResponse(error_message=error_message)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
|
||||
checking_id = data["paymentHash"]
|
||||
data["paymentHash"]
|
||||
fee_msat = -int(data["routingFeeSat"]) * 1000
|
||||
preimage = data["paymentPreimage"]
|
||||
return PaymentResponse(
|
||||
|
|
@ -238,17 +257,20 @@ class PhoenixdWallet(Wallet):
|
|||
|
||||
except json.JSONDecodeError:
|
||||
return PaymentResponse(
|
||||
error_message="Server error: 'invalid json response'"
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'invalid json response'",
|
||||
)
|
||||
except KeyError:
|
||||
return PaymentResponse(
|
||||
error_message="Server error: 'missing required fields'"
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'missing required fields'",
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.info(f"Failed to pay invoice {bolt11}")
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
error_message=f"Unable to connect to {self.endpoint}."
|
||||
checking_id=checking_id,
|
||||
error_message=f"Unable to connect to {self.endpoint}.",
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from collections.abc import AsyncGenerator
|
|||
from secrets import token_urlsafe
|
||||
|
||||
import httpx
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.helpers import normalize_endpoint
|
||||
|
|
@ -147,6 +148,11 @@ class SparkWallet(Wallet):
|
|||
return InvoiceResponse(ok=False, error_message=str(e))
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
try:
|
||||
checking_id = bolt11_decode(bolt11).payment_hash
|
||||
except Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
try:
|
||||
r = await self.pay(
|
||||
bolt11=bolt11,
|
||||
|
|
@ -156,7 +162,7 @@ class SparkWallet(Wallet):
|
|||
preimage = r["payment_preimage"]
|
||||
return PaymentResponse(
|
||||
ok=True,
|
||||
checking_id=r["payment_hash"],
|
||||
checking_id=checking_id,
|
||||
fee_msat=fee_msat,
|
||||
preimage=preimage,
|
||||
)
|
||||
|
|
@ -164,27 +170,36 @@ class SparkWallet(Wallet):
|
|||
except (SparkError, UnknownError) as exc:
|
||||
listpays = await self.listpays(bolt11)
|
||||
if not listpays:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=str(exc),
|
||||
)
|
||||
|
||||
pays = listpays["pays"]
|
||||
|
||||
if len(pays) == 0:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=str(exc),
|
||||
)
|
||||
|
||||
pay = pays[0]
|
||||
payment_hash = pay["payment_hash"]
|
||||
|
||||
if len(pays) > 1:
|
||||
raise SparkError(
|
||||
f"listpays({payment_hash}) returned an unexpected response:"
|
||||
f"listpays({checking_id}) returned an unexpected response:"
|
||||
f" {listpays}"
|
||||
) from exc
|
||||
|
||||
if pay["status"] == "failed":
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=checking_id,
|
||||
error_message=str(exc),
|
||||
)
|
||||
|
||||
if pay["status"] == "pending":
|
||||
return PaymentResponse(ok=None, checking_id=payment_hash)
|
||||
return PaymentResponse(ok=None, checking_id=checking_id)
|
||||
|
||||
if pay["status"] == "complete":
|
||||
r = pay
|
||||
|
|
@ -198,12 +213,15 @@ class SparkWallet(Wallet):
|
|||
preimage = r["payment_preimage"]
|
||||
return PaymentResponse(
|
||||
ok=True,
|
||||
checking_id=r["payment_hash"],
|
||||
checking_id=checking_id,
|
||||
fee_msat=fee_msat,
|
||||
preimage=preimage,
|
||||
)
|
||||
else:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=str(exc),
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
try:
|
||||
|
|
@ -235,7 +253,7 @@ class SparkWallet(Wallet):
|
|||
return PaymentPendingStatus()
|
||||
|
||||
if not r["pays"]:
|
||||
return PaymentFailedStatus()
|
||||
return PaymentPendingStatus()
|
||||
if r["pays"][0]["payment_hash"] == checking_id:
|
||||
status = r["pays"][0]["status"]
|
||||
if status == "complete":
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ class SparkL2Wallet(Wallet):
|
|||
self._sidecar_path = Path(settings.lnbits_data_folder, "light_spark")
|
||||
|
||||
self.pending_invoices: list[str] = []
|
||||
self.payment_ids: dict[str, str] = {}
|
||||
|
||||
self.endpoint = "http://127.0.0.1:8765"
|
||||
self._api_key = uuid.uuid4().hex
|
||||
|
|
@ -141,30 +142,30 @@ class SparkL2Wallet(Wallet):
|
|||
return InvoiceResponse(ok=False, error_message=str(e))
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
try:
|
||||
payment_hash = bolt11_decode(bolt11).payment_hash
|
||||
except Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
try:
|
||||
max_fee_sats = (int(fee_limit_msat) + 999) // 1000
|
||||
logger.info(
|
||||
f"Paying invoice via Spark sidecar with max fee {max_fee_sats} sats."
|
||||
)
|
||||
|
||||
payment_hash = None
|
||||
try:
|
||||
payment_hash = bolt11_decode(bolt11).payment_hash
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
payment_hash = None
|
||||
payload = {
|
||||
"bolt11": bolt11,
|
||||
"max_fee_sats": max_fee_sats,
|
||||
"payment_hash": payment_hash,
|
||||
}
|
||||
res = await self._request("POST", "/v1/payments", payload)
|
||||
checking_id = res.get("checking_id")
|
||||
if not checking_id:
|
||||
provider_id = res.get("payment_hash") or res.get("checking_id")
|
||||
if not provider_id:
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=payment_hash,
|
||||
error_message="Spark sidecar payment response missing checking_id.",
|
||||
)
|
||||
self.payment_ids[payment_hash] = provider_id
|
||||
status = res.get("status")
|
||||
fee_msat = res.get("fee_msat")
|
||||
ok = None
|
||||
|
|
@ -172,13 +173,16 @@ class SparkL2Wallet(Wallet):
|
|||
ok = self._map_payment_ok(status)
|
||||
return PaymentResponse(
|
||||
ok=ok,
|
||||
checking_id=checking_id,
|
||||
checking_id=payment_hash,
|
||||
fee_msat=int(fee_msat) if fee_msat is not None else None,
|
||||
preimage=res.get("preimage"),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return PaymentResponse(ok=False, error_message=str(e))
|
||||
return PaymentResponse(
|
||||
checking_id=payment_hash,
|
||||
error_message=str(e),
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
try:
|
||||
|
|
@ -193,7 +197,8 @@ class SparkL2Wallet(Wallet):
|
|||
|
||||
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
try:
|
||||
res = await self._request("GET", f"/v1/payments/{checking_id}")
|
||||
provider_id = self.payment_ids.get(checking_id, checking_id)
|
||||
res = await self._request("GET", f"/v1/payments/{provider_id}")
|
||||
status = res.get("status")
|
||||
fee_msat = res.get("fee_msat")
|
||||
preimage = res.get("preimage")
|
||||
|
|
@ -201,11 +206,13 @@ class SparkL2Wallet(Wallet):
|
|||
return PaymentPendingStatus()
|
||||
mapped = self._map_payment_status(status)
|
||||
if mapped.success:
|
||||
self.payment_ids.pop(checking_id, None)
|
||||
return PaymentSuccessStatus(
|
||||
fee_msat=int(fee_msat) if fee_msat is not None else None,
|
||||
preimage=preimage,
|
||||
)
|
||||
if mapped.failed:
|
||||
self.payment_ids.pop(checking_id, None)
|
||||
return PaymentFailedStatus()
|
||||
return PaymentPendingStatus()
|
||||
except Exception as exc:
|
||||
|
|
|
|||
|
|
@ -241,12 +241,20 @@ class StrikeWallet(Wallet):
|
|||
# 1) Create a payment quote
|
||||
quote_id, error = await self._create_payment_quote(bolt11)
|
||||
if error or not quote_id:
|
||||
return PaymentResponse(ok=False, error_message=error or "Unknown error")
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=payment_hash,
|
||||
error_message=error or "Unknown error",
|
||||
)
|
||||
|
||||
# 2) Execute the payment quote
|
||||
self.pending_payments[payment_hash] = quote_id
|
||||
data, error = await self._execute_payment_quote(quote_id)
|
||||
if error or not data:
|
||||
return PaymentResponse(ok=False, error_message=error or "Unknown error")
|
||||
return PaymentResponse(
|
||||
checking_id=payment_hash,
|
||||
error_message=error or "Unknown error",
|
||||
)
|
||||
|
||||
state = data.get("state", "").upper()
|
||||
payment_id = data.get("paymentId")
|
||||
|
|
@ -256,6 +264,7 @@ class StrikeWallet(Wallet):
|
|||
|
||||
# Handle successful payment
|
||||
if state in {"SUCCEEDED", "COMPLETED"}:
|
||||
self.pending_payments.pop(payment_hash, None)
|
||||
preimage = self._extract_preimage(data)
|
||||
return PaymentResponse(
|
||||
ok=True,
|
||||
|
|
@ -267,6 +276,7 @@ class StrikeWallet(Wallet):
|
|||
# Handle failed payment
|
||||
failed_states = {"CANCELED", "FAILED", "TIMED_OUT"}
|
||||
if state in failed_states:
|
||||
self.pending_payments.pop(payment_hash, None)
|
||||
logger.warning(
|
||||
f"Strike payment {payment_id} failed with state: {state}"
|
||||
)
|
||||
|
|
@ -276,9 +286,6 @@ class StrikeWallet(Wallet):
|
|||
error_message=f"Payment {state.lower()}",
|
||||
)
|
||||
|
||||
# Store mapping for later polling
|
||||
self.pending_payments[payment_hash] = quote_id
|
||||
|
||||
# Treat all other states as pending
|
||||
return PaymentResponse(ok=None, checking_id=payment_hash)
|
||||
|
||||
|
|
@ -289,12 +296,17 @@ class StrikeWallet(Wallet):
|
|||
f"body: {http_exc.response.text}"
|
||||
)
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
ok=None,
|
||||
checking_id=payment_hash,
|
||||
error_message=f"Strike API error: {http_exc.response.status_code}",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Strike payment exception: {e}", exc_info=True)
|
||||
return PaymentResponse(ok=None, error_message=f"Error: {e!s}")
|
||||
return PaymentResponse(
|
||||
ok=None,
|
||||
checking_id=payment_hash,
|
||||
error_message=f"Error: {e!s}",
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
try:
|
||||
|
|
@ -341,25 +353,14 @@ class StrikeWallet(Wallet):
|
|||
|
||||
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
quote_id = self.pending_payments.get(checking_id)
|
||||
|
||||
if not quote_id:
|
||||
return PaymentPendingStatus()
|
||||
try:
|
||||
# Attempt 1: Use quote_id if available (from in-memory store)
|
||||
if quote_id:
|
||||
status = await self._get_payment_status_by_quote_id(
|
||||
checking_id, quote_id
|
||||
)
|
||||
if status:
|
||||
return status
|
||||
status = await self._get_payment_status_by_quote_id(checking_id, quote_id)
|
||||
return status or PaymentPendingStatus()
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
logger.debug(f"Error while fetching payment by quote id {checking_id}.")
|
||||
|
||||
try:
|
||||
# Attempt 2: Fallback - Use paymentId (checking_id) directly.
|
||||
return await self._get_payment_status_by_checking_id(checking_id)
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
logger.debug(f"Error while fetching payment {checking_id}.")
|
||||
return PaymentPendingStatus()
|
||||
|
||||
async def get_invoices(
|
||||
|
|
@ -625,7 +626,7 @@ class StrikeWallet(Wallet):
|
|||
if state in {"SUCCEEDED", "COMPLETED"}:
|
||||
self.pending_payments.pop(checking_id, None)
|
||||
return PaymentSuccessStatus(fee_msat=fee_msat, preimage=preimage)
|
||||
if state == "FAILED":
|
||||
if state in {"CANCELED", "FAILED", "TIMED_OUT"}:
|
||||
self.pending_payments.pop(checking_id, None)
|
||||
return PaymentFailedStatus()
|
||||
|
||||
|
|
|
|||
|
|
@ -105,27 +105,48 @@ class ZBDWallet(Wallet):
|
|||
|
||||
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,
|
||||
"description": "",
|
||||
"amount": "",
|
||||
"internalId": "",
|
||||
"callbackUrl": "",
|
||||
},
|
||||
timeout=40,
|
||||
)
|
||||
try:
|
||||
checking_id = bolt11_decode(bolt11).payment_hash
|
||||
except Exception as exc:
|
||||
return PaymentResponse(ok=False, error_message=str(exc))
|
||||
|
||||
try:
|
||||
r = await self.client.post(
|
||||
"payments",
|
||||
json={
|
||||
"invoice": bolt11,
|
||||
"description": "",
|
||||
"amount": "",
|
||||
"internalId": "",
|
||||
"callbackUrl": "",
|
||||
},
|
||||
timeout=40,
|
||||
)
|
||||
data = r.json()
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message=f"Unable to connect to {self.endpoint}.",
|
||||
)
|
||||
|
||||
if r.is_error:
|
||||
error_message = r.json()["message"]
|
||||
return PaymentResponse(ok=False, error_message=error_message)
|
||||
error_message = data.get("message", r.text)
|
||||
return PaymentResponse(
|
||||
ok=False if r.is_client_error else None,
|
||||
checking_id=checking_id,
|
||||
error_message=error_message,
|
||||
)
|
||||
|
||||
data = r.json()
|
||||
|
||||
checking_id = bolt11_decode(bolt11).payment_hash
|
||||
fee_msat = -int(data["data"]["fee"])
|
||||
preimage = data["data"]["preimage"]
|
||||
try:
|
||||
fee_msat = -int(data["data"]["fee"])
|
||||
preimage = data["data"]["preimage"]
|
||||
except (KeyError, TypeError, ValueError) as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentResponse(
|
||||
checking_id=checking_id,
|
||||
error_message="Server error: 'missing required fields'",
|
||||
)
|
||||
|
||||
return PaymentResponse(
|
||||
ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage
|
||||
|
|
|
|||
|
|
@ -1322,7 +1322,7 @@
|
|||
"success": false,
|
||||
"pending": false,
|
||||
"failed": true,
|
||||
"checking_id": null,
|
||||
"checking_id": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||
"fee_msat": null,
|
||||
"preimage": null
|
||||
},
|
||||
|
|
@ -1518,7 +1518,7 @@
|
|||
"success": false,
|
||||
"pending": true,
|
||||
"failed": false,
|
||||
"checking_id": null,
|
||||
"checking_id": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||
"fee_msat": null,
|
||||
"preimage": null,
|
||||
"error_message": "Test Error"
|
||||
|
|
@ -1612,7 +1612,7 @@
|
|||
"success": false,
|
||||
"pending": true,
|
||||
"failed": false,
|
||||
"checking_id": null,
|
||||
"checking_id": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||
"fee_msat": null,
|
||||
"preimage": null
|
||||
},
|
||||
|
|
@ -1709,7 +1709,7 @@
|
|||
"success": false,
|
||||
"pending": true,
|
||||
"failed": false,
|
||||
"checking_id": null,
|
||||
"checking_id": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||
"fee_msat": null,
|
||||
"preimage": null
|
||||
},
|
||||
|
|
@ -1827,7 +1827,7 @@
|
|||
"success": false,
|
||||
"pending": true,
|
||||
"failed": false,
|
||||
"checking_id": null,
|
||||
"checking_id": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||
"fee_msat": null,
|
||||
"preimage": null
|
||||
},
|
||||
|
|
@ -1957,7 +1957,7 @@
|
|||
"success": false,
|
||||
"pending": true,
|
||||
"failed": false,
|
||||
"checking_id": null,
|
||||
"checking_id": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||
"fee_msat": null,
|
||||
"preimage": null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -702,7 +702,7 @@
|
|||
"expect": {
|
||||
"error_message": null,
|
||||
"success": true,
|
||||
"checking_id": "c386d8e8d07342f2e39e189c8e6c57bb205bb373fe4e3a6f69404a8bb767b417",
|
||||
"checking_id": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||
"fee_msat": 50,
|
||||
"preimage": "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
|
|
@ -843,7 +843,7 @@
|
|||
"success": false,
|
||||
"pending": false,
|
||||
"failed": true,
|
||||
"checking_id": null,
|
||||
"checking_id": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||
"fee_msat": null,
|
||||
"preimage": null
|
||||
},
|
||||
|
|
@ -1024,7 +1024,7 @@
|
|||
"success": false,
|
||||
"pending": true,
|
||||
"failed": false,
|
||||
"checking_id": null,
|
||||
"checking_id": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||
"fee_msat": null,
|
||||
"preimage": null
|
||||
},
|
||||
|
|
@ -1112,7 +1112,7 @@
|
|||
"success": false,
|
||||
"pending": true,
|
||||
"failed": false,
|
||||
"checking_id": null,
|
||||
"checking_id": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||
"fee_msat": null,
|
||||
"preimage": null
|
||||
},
|
||||
|
|
@ -1161,7 +1161,7 @@
|
|||
"success": false,
|
||||
"pending": true,
|
||||
"failed": false,
|
||||
"checking_id": null,
|
||||
"checking_id": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||
"fee_msat": null,
|
||||
"preimage": null
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue