mirror of
https://github.com/lnbits/lnbits.git
synced 2025-06-20 22:04:13 +02:00
fix: pay invoice status (#2481)
* fix: rest `pay_invoice` pending instead of failed
* fix: rpc `pay_invoice` pending instead of failed
* fix: return "failed" value for payment
* fix: handle failed status for LNbits funding source
* chore: `phoenixd` todo
* test: fix condition
* fix: wait for payment status to be updated
* fix: fail payment when explicit status provided
---------
Co-authored-by: dni ⚡ <office@dnilabs.com>
This commit is contained in:
parent
b9e62bfceb
commit
eae5002b69
20
.github/workflows/regtest.yml
vendored
20
.github/workflows/regtest.yml
vendored
@ -27,19 +27,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: docker build
|
||||||
if: ${{ inputs.backend-wallet-class == 'LNbitsWallet' }}
|
if: ${{ inputs.backend-wallet-class == 'LNbitsWallet' }}
|
||||||
uses: docker/setup-buildx-action@v3
|
run: |
|
||||||
|
docker build -t lnbits/lnbits .
|
||||||
- name: Build and push
|
|
||||||
if: ${{ inputs.backend-wallet-class == 'LNbitsWallet' }}
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: false
|
|
||||||
tags: lnbits/lnbits:latest
|
|
||||||
cache-from: type=registry,ref=lnbits/lnbits:latest
|
|
||||||
cache-to: type=inline
|
|
||||||
|
|
||||||
- name: Setup Regtest
|
- name: Setup Regtest
|
||||||
run: |
|
run: |
|
||||||
@ -89,3 +80,8 @@ jobs:
|
|||||||
file: ./coverage.xml
|
file: ./coverage.xml
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
verbose: true
|
verbose: true
|
||||||
|
|
||||||
|
- name: docker lnbits logs
|
||||||
|
if: ${{ inputs.backend-wallet-class == 'LNbitsWallet' }}
|
||||||
|
run: |
|
||||||
|
docker logs lnbits-lnbits-1
|
||||||
|
@ -63,11 +63,15 @@ from .models import Payment, UserConfig, Wallet
|
|||||||
|
|
||||||
|
|
||||||
class PaymentError(Exception):
|
class PaymentError(Exception):
|
||||||
pass
|
def __init__(self, message: str, status: str = "pending"):
|
||||||
|
self.message = message
|
||||||
|
self.status = status
|
||||||
|
|
||||||
|
|
||||||
class InvoiceError(Exception):
|
class InvoiceError(Exception):
|
||||||
pass
|
def __init__(self, message: str, status: str = "pending"):
|
||||||
|
self.message = message
|
||||||
|
self.status = status
|
||||||
|
|
||||||
|
|
||||||
async def calculate_fiat_amounts(
|
async def calculate_fiat_amounts(
|
||||||
@ -123,11 +127,11 @@ async def create_invoice(
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> Tuple[str, str]:
|
) -> Tuple[str, str]:
|
||||||
if not amount > 0:
|
if not amount > 0:
|
||||||
raise InvoiceError("Amountless invoices not supported.")
|
raise InvoiceError("Amountless invoices not supported.", status="failed")
|
||||||
|
|
||||||
user_wallet = await get_wallet(wallet_id, conn=conn)
|
user_wallet = await get_wallet(wallet_id, conn=conn)
|
||||||
if not user_wallet:
|
if not user_wallet:
|
||||||
raise InvoiceError(f"Could not fetch wallet '{wallet_id}'.")
|
raise InvoiceError(f"Could not fetch wallet '{wallet_id}'.", status="failed")
|
||||||
|
|
||||||
invoice_memo = None if description_hash else memo
|
invoice_memo = None if description_hash else memo
|
||||||
|
|
||||||
@ -143,7 +147,8 @@ async def create_invoice(
|
|||||||
):
|
):
|
||||||
raise InvoiceError(
|
raise InvoiceError(
|
||||||
f"Wallet balance cannot exceed "
|
f"Wallet balance cannot exceed "
|
||||||
f"{settings.lnbits_wallet_limit_max_balance} sats."
|
f"{settings.lnbits_wallet_limit_max_balance} sats.",
|
||||||
|
status="failed",
|
||||||
)
|
)
|
||||||
|
|
||||||
(
|
(
|
||||||
@ -159,7 +164,9 @@ async def create_invoice(
|
|||||||
expiry=expiry or settings.lightning_invoice_expiry,
|
expiry=expiry or settings.lightning_invoice_expiry,
|
||||||
)
|
)
|
||||||
if not ok or not payment_request or not checking_id:
|
if not ok or not payment_request or not checking_id:
|
||||||
raise InvoiceError(error_message or "unexpected backend error.")
|
raise InvoiceError(
|
||||||
|
error_message or "unexpected backend error.", status="pending"
|
||||||
|
)
|
||||||
|
|
||||||
invoice = bolt11_decode(payment_request)
|
invoice = bolt11_decode(payment_request)
|
||||||
|
|
||||||
@ -202,12 +209,12 @@ async def pay_invoice(
|
|||||||
try:
|
try:
|
||||||
invoice = bolt11_decode(payment_request)
|
invoice = bolt11_decode(payment_request)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise InvoiceError("Bolt11 decoding failed.") from exc
|
raise PaymentError("Bolt11 decoding failed.", status="failed") from exc
|
||||||
|
|
||||||
if not invoice.amount_msat or not invoice.amount_msat > 0:
|
if not invoice.amount_msat or not invoice.amount_msat > 0:
|
||||||
raise InvoiceError("Amountless invoices not supported.")
|
raise PaymentError("Amountless invoices not supported.", status="failed")
|
||||||
if max_sat and invoice.amount_msat > max_sat * 1000:
|
if max_sat and invoice.amount_msat > max_sat * 1000:
|
||||||
raise InvoiceError("Amount in invoice is too high.")
|
raise PaymentError("Amount in invoice is too high.", status="failed")
|
||||||
|
|
||||||
await check_wallet_limits(wallet_id, conn, invoice.amount_msat)
|
await check_wallet_limits(wallet_id, conn, invoice.amount_msat)
|
||||||
|
|
||||||
@ -242,7 +249,7 @@ async def pay_invoice(
|
|||||||
# we check if an internal invoice exists that has already been paid
|
# we check if an internal invoice exists that has already been paid
|
||||||
# (not pending anymore)
|
# (not pending anymore)
|
||||||
if not await check_internal_pending(invoice.payment_hash, conn=conn):
|
if not await check_internal_pending(invoice.payment_hash, conn=conn):
|
||||||
raise PaymentError("Internal invoice already paid.")
|
raise PaymentError("Internal invoice already paid.", status="failed")
|
||||||
|
|
||||||
# check_internal() returns the checking_id of the invoice we're waiting for
|
# check_internal() returns the checking_id of the invoice we're waiting for
|
||||||
# (pending only)
|
# (pending only)
|
||||||
@ -261,7 +268,7 @@ async def pay_invoice(
|
|||||||
internal_invoice.amount != invoice.amount_msat
|
internal_invoice.amount != invoice.amount_msat
|
||||||
or internal_invoice.bolt11 != payment_request.lower()
|
or internal_invoice.bolt11 != payment_request.lower()
|
||||||
):
|
):
|
||||||
raise PaymentError("Invalid invoice.")
|
raise PaymentError("Invalid invoice.", status="failed")
|
||||||
|
|
||||||
logger.debug(f"creating temporary internal payment with id {internal_id}")
|
logger.debug(f"creating temporary internal payment with id {internal_id}")
|
||||||
# create a new payment from this wallet
|
# create a new payment from this wallet
|
||||||
@ -289,7 +296,7 @@ async def pay_invoice(
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"could not create temporary payment: {exc}")
|
logger.error(f"could not create temporary payment: {exc}")
|
||||||
# happens if the same wallet tries to pay an invoice twice
|
# happens if the same wallet tries to pay an invoice twice
|
||||||
raise PaymentError("Could not make payment.") from exc
|
raise PaymentError("Could not make payment.", status="failed") from exc
|
||||||
|
|
||||||
# do the balance check
|
# do the balance check
|
||||||
wallet = await get_wallet(wallet_id, conn=conn)
|
wallet = await get_wallet(wallet_id, conn=conn)
|
||||||
@ -302,9 +309,10 @@ async def pay_invoice(
|
|||||||
):
|
):
|
||||||
raise PaymentError(
|
raise PaymentError(
|
||||||
f"You must reserve at least ({round(fee_reserve_total_msat/1000)}"
|
f"You must reserve at least ({round(fee_reserve_total_msat/1000)}"
|
||||||
" sat) to cover potential routing fees."
|
" sat) to cover potential routing fees.",
|
||||||
|
status="failed",
|
||||||
)
|
)
|
||||||
raise PermissionError("Insufficient balance.")
|
raise PaymentError("Insufficient balance.", status="failed")
|
||||||
|
|
||||||
if internal_checking_id:
|
if internal_checking_id:
|
||||||
service_fee_msat = service_fee(invoice.amount_msat, internal=True)
|
service_fee_msat = service_fee(invoice.amount_msat, internal=True)
|
||||||
@ -340,6 +348,7 @@ async def pay_invoice(
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(f"backend: pay_invoice finished {temp_id}")
|
logger.debug(f"backend: pay_invoice finished {temp_id}")
|
||||||
|
logger.debug(f"backend: pay_invoice response {payment}")
|
||||||
if payment.checking_id and payment.ok is not False:
|
if payment.checking_id and payment.ok is not False:
|
||||||
# payment.ok can be True (paid) or None (pending)!
|
# payment.ok can be True (paid) or None (pending)!
|
||||||
logger.debug(f"updating payment {temp_id}")
|
logger.debug(f"updating payment {temp_id}")
|
||||||
@ -370,7 +379,8 @@ async def pay_invoice(
|
|||||||
await delete_wallet_payment(temp_id, wallet_id, conn=conn)
|
await delete_wallet_payment(temp_id, wallet_id, conn=conn)
|
||||||
raise PaymentError(
|
raise PaymentError(
|
||||||
f"Payment failed: {payment.error_message}"
|
f"Payment failed: {payment.error_message}"
|
||||||
or "Payment failed, but backend didn't give us an error message."
|
or "Payment failed, but backend didn't give us an error message.",
|
||||||
|
status="failed",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@ -413,8 +423,9 @@ async def check_time_limit_between_transactions(conn, wallet_id):
|
|||||||
if len(payments) == 0:
|
if len(payments) == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
raise ValueError(
|
raise PaymentError(
|
||||||
f"The time limit of {limit} seconds between payments has been reached."
|
status="failed",
|
||||||
|
message=f"The time limit of {limit} seconds between payments has been reached.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -171,7 +171,10 @@ async def api_payments_create_invoice(data: CreateInvoice, wallet: Wallet):
|
|||||||
assert payment_db is not None, "payment not found"
|
assert payment_db is not None, "payment not found"
|
||||||
checking_id = payment_db.checking_id
|
checking_id = payment_db.checking_id
|
||||||
except InvoiceError as exc:
|
except InvoiceError as exc:
|
||||||
raise HTTPException(status_code=520, detail=str(exc)) from exc
|
return JSONResponse(
|
||||||
|
status_code=520,
|
||||||
|
content={"detail": exc.message, "status": exc.status},
|
||||||
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
@ -217,14 +220,11 @@ async def api_payments_pay_invoice(
|
|||||||
payment_hash = await pay_invoice(
|
payment_hash = await pay_invoice(
|
||||||
wallet_id=wallet.id, payment_request=bolt11, extra=extra
|
wallet_id=wallet.id, payment_request=bolt11, extra=extra
|
||||||
)
|
)
|
||||||
except ValueError as exc:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.BAD_REQUEST, detail=str(exc)
|
|
||||||
) from exc
|
|
||||||
except PermissionError as exc:
|
|
||||||
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail=str(exc)) from exc
|
|
||||||
except PaymentError as exc:
|
except PaymentError as exc:
|
||||||
raise HTTPException(status_code=520, detail=str(exc)) from exc
|
return JSONResponse(
|
||||||
|
status_code=520,
|
||||||
|
content={"detail": exc.message, "status": exc.status},
|
||||||
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
@ -434,7 +434,7 @@ async def api_payment(payment_hash, x_api_key: Optional[str] = Header(None)):
|
|||||||
return {"paid": True, "preimage": payment.preimage}
|
return {"paid": True, "preimage": payment.preimage}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await payment.check_status()
|
status = await payment.check_status()
|
||||||
except Exception:
|
except Exception:
|
||||||
if wallet and wallet.id == payment.wallet_id:
|
if wallet and wallet.id == payment.wallet_id:
|
||||||
return {"paid": False, "details": payment}
|
return {"paid": False, "details": payment}
|
||||||
@ -443,6 +443,7 @@ async def api_payment(payment_hash, x_api_key: Optional[str] = Header(None)):
|
|||||||
if wallet and wallet.id == payment.wallet_id:
|
if wallet and wallet.id == payment.wallet_id:
|
||||||
return {
|
return {
|
||||||
"paid": not payment.pending,
|
"paid": not payment.pending,
|
||||||
|
"status": f"{status!s}",
|
||||||
"preimage": payment.preimage,
|
"preimage": payment.preimage,
|
||||||
"details": payment,
|
"details": payment,
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,7 @@ 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(False, None, None, None, error_message)
|
return PaymentResponse(None, None, None, None, 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
|
||||||
@ -141,18 +141,18 @@ class AlbyWallet(Wallet):
|
|||||||
except KeyError as exc:
|
except KeyError as exc:
|
||||||
logger.warning(exc)
|
logger.warning(exc)
|
||||||
return PaymentResponse(
|
return PaymentResponse(
|
||||||
False, None, None, None, "Server error: 'missing required fields'"
|
None, None, None, None, "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(
|
||||||
False, None, None, None, "Server error: 'invalid json response'"
|
None, None, None, None, "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(
|
||||||
False, None, None, None, f"Unable to connect to {self.endpoint}."
|
None, None, None, None, 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:
|
||||||
@ -167,6 +167,7 @@ class AlbyWallet(Wallet):
|
|||||||
|
|
||||||
data = r.json()
|
data = r.json()
|
||||||
|
|
||||||
|
# TODO: how can we detect a failed payment?
|
||||||
statuses = {
|
statuses = {
|
||||||
"CREATED": None,
|
"CREATED": None,
|
||||||
"SETTLED": True,
|
"SETTLED": True,
|
||||||
|
@ -70,14 +70,11 @@ class PaymentStatus(NamedTuple):
|
|||||||
return self.paid is False
|
return self.paid is False
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
if self.paid is True:
|
if self.success:
|
||||||
return "settled"
|
return "success"
|
||||||
elif self.paid is False:
|
if self.failed:
|
||||||
return "failed"
|
return "failed"
|
||||||
elif self.paid is None:
|
return "pending"
|
||||||
return "still pending"
|
|
||||||
else:
|
|
||||||
return "unknown (should never happen)"
|
|
||||||
|
|
||||||
|
|
||||||
class PaymentSuccessStatus(PaymentStatus):
|
class PaymentSuccessStatus(PaymentStatus):
|
||||||
|
@ -46,6 +46,15 @@ class CoreLightningWallet(Wallet):
|
|||||||
command = self.ln.help("invoice")["help"][0]["command"] # type: ignore
|
command = self.ln.help("invoice")["help"][0]["command"] # type: ignore
|
||||||
self.supports_description_hash = "deschashonly" in command
|
self.supports_description_hash = "deschashonly" in command
|
||||||
|
|
||||||
|
# https://docs.corelightning.org/reference/lightning-pay
|
||||||
|
# 201: Already paid
|
||||||
|
# 203: Permanent failure at destination.
|
||||||
|
# 205: Unable to find a route.
|
||||||
|
# 206: Route too expensive.
|
||||||
|
# 207: Invoice expired.
|
||||||
|
# 210: Payment timed out without a payment in progress.
|
||||||
|
self.pay_failure_error_codes = [201, 203, 205, 206, 207, 210]
|
||||||
|
|
||||||
# check last payindex so we can listen from that point on
|
# check last payindex so we can listen from that point on
|
||||||
self.last_pay_index = 0
|
self.last_pay_index = 0
|
||||||
invoices: dict = self.ln.listinvoices() # type: ignore
|
invoices: dict = self.ln.listinvoices() # type: ignore
|
||||||
@ -155,19 +164,27 @@ class CoreLightningWallet(Wallet):
|
|||||||
except RpcError as exc:
|
except RpcError as exc:
|
||||||
logger.warning(exc)
|
logger.warning(exc)
|
||||||
try:
|
try:
|
||||||
error_message = exc.error["attempts"][-1]["fail_reason"] # type: ignore
|
error_code = exc.error.get("code")
|
||||||
|
if error_code in self.pay_failure_error_codes: # type: ignore
|
||||||
|
error_message = exc.error.get("message", error_code) # type: ignore
|
||||||
|
return PaymentResponse(
|
||||||
|
False, None, None, None, f"Payment failed: {error_message}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
error_message = f"Payment failed: {exc.error}"
|
||||||
|
return PaymentResponse(None, None, None, None, 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(False, None, None, None, error_message)
|
return PaymentResponse(None, None, None, None, error_message)
|
||||||
except KeyError as exc:
|
except KeyError as exc:
|
||||||
logger.warning(exc)
|
logger.warning(exc)
|
||||||
return PaymentResponse(
|
return PaymentResponse(
|
||||||
False, None, None, None, "Server error: 'missing required fields'"
|
None, None, None, None, "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(False, None, None, None, f"Payment failed: '{exc}'.")
|
return PaymentResponse(None, None, None, None, 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:
|
||||||
|
@ -49,6 +49,15 @@ class CoreLightningRestWallet(Wallet):
|
|||||||
"User-Agent": settings.user_agent,
|
"User-Agent": settings.user_agent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# https://docs.corelightning.org/reference/lightning-pay
|
||||||
|
# 201: Already paid
|
||||||
|
# 203: Permanent failure at destination.
|
||||||
|
# 205: Unable to find a route.
|
||||||
|
# 206: Route too expensive.
|
||||||
|
# 207: Invoice expired.
|
||||||
|
# 210: Payment timed out without a payment in progress.
|
||||||
|
self.pay_failure_error_codes = [201, 203, 205, 206, 207, 210]
|
||||||
|
|
||||||
self.cert = settings.corelightning_rest_cert or False
|
self.cert = settings.corelightning_rest_cert or False
|
||||||
self.client = httpx.AsyncClient(verify=self.cert, headers=headers)
|
self.client = httpx.AsyncClient(verify=self.cert, headers=headers)
|
||||||
self.last_pay_index = 0
|
self.last_pay_index = 0
|
||||||
@ -176,37 +185,48 @@ class CoreLightningRestWallet(Wallet):
|
|||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
data = r.json()
|
data = r.json()
|
||||||
|
|
||||||
if "error" in data:
|
status = self.statuses.get(data["status"])
|
||||||
return PaymentResponse(False, None, None, None, data["error"])
|
if "payment_preimage" not in data:
|
||||||
if r.is_error:
|
|
||||||
return PaymentResponse(False, None, None, None, r.text)
|
|
||||||
if (
|
|
||||||
"payment_hash" not in data
|
|
||||||
or "payment_preimage" not in data
|
|
||||||
or "msatoshi_sent" not in data
|
|
||||||
or "msatoshi" not in data
|
|
||||||
or "status" not in data
|
|
||||||
):
|
|
||||||
return PaymentResponse(
|
return PaymentResponse(
|
||||||
False, None, None, None, "Server error: 'missing required fields'"
|
status,
|
||||||
|
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(
|
return PaymentResponse(status, checking_id, fee_msat, preimage, None)
|
||||||
self.statuses.get(data["status"]), checking_id, fee_msat, preimage, None
|
except httpx.HTTPStatusError as exc:
|
||||||
)
|
try:
|
||||||
|
logger.debug(exc)
|
||||||
|
data = exc.response.json()
|
||||||
|
if data["error"]["code"] in self.pay_failure_error_codes: # type: ignore
|
||||||
|
error_message = f"Payment failed: {data['error']['message']}"
|
||||||
|
return PaymentResponse(False, None, None, None, error_message)
|
||||||
|
error_message = f"REST failed with {data['error']['message']}."
|
||||||
|
return PaymentResponse(None, None, None, None, error_message)
|
||||||
|
except Exception as exc:
|
||||||
|
error_message = f"Unable to connect to {self.url}."
|
||||||
|
return PaymentResponse(None, None, None, None, error_message)
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return PaymentResponse(
|
return PaymentResponse(
|
||||||
False, None, None, None, "Server error: 'invalid json response'"
|
None, None, None, None, "Server error: 'invalid json response'"
|
||||||
|
)
|
||||||
|
except KeyError as exc:
|
||||||
|
logger.warning(exc)
|
||||||
|
return PaymentResponse(
|
||||||
|
None, None, None, None, "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(
|
||||||
False, None, None, None, 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:
|
||||||
|
@ -142,9 +142,9 @@ class EclairWallet(Wallet):
|
|||||||
data = r.json()
|
data = r.json()
|
||||||
|
|
||||||
if "error" in data:
|
if "error" in data:
|
||||||
return PaymentResponse(False, None, None, None, data["error"])
|
return PaymentResponse(None, None, None, None, data["error"])
|
||||||
if r.is_error:
|
if r.is_error:
|
||||||
return PaymentResponse(False, None, None, None, r.text)
|
return PaymentResponse(None, None, None, None, r.text)
|
||||||
|
|
||||||
if data["type"] == "payment-failed":
|
if data["type"] == "payment-failed":
|
||||||
return PaymentResponse(False, None, None, None, "payment failed")
|
return PaymentResponse(False, None, None, None, "payment failed")
|
||||||
@ -154,17 +154,17 @@ class EclairWallet(Wallet):
|
|||||||
|
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return PaymentResponse(
|
return PaymentResponse(
|
||||||
False, None, None, None, "Server error: 'invalid json response'"
|
None, None, None, None, "Server error: 'invalid json response'"
|
||||||
)
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return PaymentResponse(
|
return PaymentResponse(
|
||||||
False, None, None, None, "Server error: 'missing required fields'"
|
None, None, None, None, "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(
|
||||||
False, None, None, None, 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)
|
||||||
|
@ -9,6 +9,7 @@ from lnbits.settings import settings
|
|||||||
|
|
||||||
from .base import (
|
from .base import (
|
||||||
InvoiceResponse,
|
InvoiceResponse,
|
||||||
|
PaymentFailedStatus,
|
||||||
PaymentPendingStatus,
|
PaymentPendingStatus,
|
||||||
PaymentResponse,
|
PaymentResponse,
|
||||||
PaymentStatus,
|
PaymentStatus,
|
||||||
@ -115,13 +116,10 @@ class LNbitsWallet(Wallet):
|
|||||||
json={"out": True, "bolt11": bolt11},
|
json={"out": True, "bolt11": bolt11},
|
||||||
timeout=None,
|
timeout=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
data = r.json()
|
data = r.json()
|
||||||
|
|
||||||
if r.is_error or "payment_hash" not in data:
|
|
||||||
error_message = data["detail"] if "detail" in data else r.text
|
|
||||||
return PaymentResponse(False, None, None, None, error_message)
|
|
||||||
|
|
||||||
checking_id = data["payment_hash"]
|
checking_id = data["payment_hash"]
|
||||||
|
|
||||||
# we do this to get the fee and preimage
|
# we do this to get the fee and preimage
|
||||||
@ -131,19 +129,32 @@ class LNbitsWallet(Wallet):
|
|||||||
return PaymentResponse(
|
return PaymentResponse(
|
||||||
success, checking_id, payment.fee_msat, payment.preimage
|
success, checking_id, payment.fee_msat, payment.preimage
|
||||||
)
|
)
|
||||||
|
|
||||||
|
except httpx.HTTPStatusError as exc:
|
||||||
|
try:
|
||||||
|
logger.debug(exc)
|
||||||
|
data = exc.response.json()
|
||||||
|
error_message = f"Payment {data['status']}: {data['detail']}."
|
||||||
|
if data["status"] == "failed":
|
||||||
|
return PaymentResponse(False, None, None, None, error_message)
|
||||||
|
return PaymentResponse(None, None, None, None, error_message)
|
||||||
|
except Exception as exc:
|
||||||
|
error_message = f"Unable to connect to {self.endpoint}."
|
||||||
|
return PaymentResponse(None, None, None, None, error_message)
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return PaymentResponse(
|
return PaymentResponse(
|
||||||
False, None, None, None, "Server error: 'invalid json response'"
|
None, None, None, None, "Server error: 'invalid json response'"
|
||||||
)
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return PaymentResponse(
|
return PaymentResponse(
|
||||||
False, None, None, None, "Server error: 'missing required fields'"
|
None, None, None, None, "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(
|
||||||
False, None, None, None, f"Unable to connect to {self.endpoint}."
|
None, None, None, None, 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:
|
||||||
@ -169,6 +180,9 @@ class LNbitsWallet(Wallet):
|
|||||||
return PaymentPendingStatus()
|
return PaymentPendingStatus()
|
||||||
data = r.json()
|
data = r.json()
|
||||||
|
|
||||||
|
if data.get("status") == "failed":
|
||||||
|
return PaymentFailedStatus()
|
||||||
|
|
||||||
if "paid" not in data or not data["paid"]:
|
if "paid" not in data or not data["paid"]:
|
||||||
return PaymentPendingStatus()
|
return PaymentPendingStatus()
|
||||||
|
|
||||||
|
@ -167,7 +167,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(False, None, None, None, str(exc))
|
return PaymentResponse(None, None, None, None, 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 = {
|
||||||
|
@ -174,39 +174,30 @@ class LndRestWallet(Wallet):
|
|||||||
timeout=None,
|
timeout=None,
|
||||||
)
|
)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
except Exception as exc:
|
|
||||||
logger.warning(f"LndRestWallet pay_invoice POST error: {exc}.")
|
|
||||||
return PaymentResponse(
|
|
||||||
False, None, None, None, f"Unable to connect to {self.endpoint}."
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = r.json()
|
data = r.json()
|
||||||
|
|
||||||
if data.get("payment_error"):
|
payment_error = data.get("payment_error")
|
||||||
error_message = r.json().get("payment_error") or r.text
|
if payment_error:
|
||||||
logger.warning(
|
logger.warning(f"LndRestWallet payment_error: {payment_error}.")
|
||||||
f"LndRestWallet pay_invoice payment_error: {error_message}."
|
return PaymentResponse(False, None, None, None, payment_error)
|
||||||
)
|
|
||||||
return PaymentResponse(False, None, None, None, error_message)
|
|
||||||
|
|
||||||
if (
|
|
||||||
"payment_hash" not in data
|
|
||||||
or "payment_route" not in data
|
|
||||||
or "total_fees_msat" not in data["payment_route"]
|
|
||||||
or "payment_preimage" not in data
|
|
||||||
):
|
|
||||||
return PaymentResponse(
|
|
||||||
False, None, None, None, "Server error: 'missing required fields'"
|
|
||||||
)
|
|
||||||
|
|
||||||
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(True, checking_id, fee_msat, preimage, None)
|
||||||
|
except KeyError as exc:
|
||||||
|
logger.warning(exc)
|
||||||
|
return PaymentResponse(
|
||||||
|
None, None, None, None, "Server error: 'missing required fields'"
|
||||||
|
)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return PaymentResponse(
|
return PaymentResponse(
|
||||||
False, None, None, None, "Server error: 'invalid json response'"
|
None, None, None, None, "Server error: 'invalid json response'"
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(f"LndRestWallet pay_invoice POST error: {exc}.")
|
||||||
|
return PaymentResponse(
|
||||||
|
None, None, None, None, f"Unable to connect to {self.endpoint}."
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||||
|
@ -144,11 +144,11 @@ 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(False, None, None, None, data["reason"])
|
return PaymentResponse(None, None, None, None, 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(False, None, None, None, error_message)
|
return PaymentResponse(None, None, None, None, error_message)
|
||||||
|
|
||||||
checking_id = data["paymentHash"]
|
checking_id = data["paymentHash"]
|
||||||
fee_msat = -int(data["routingFeeSat"])
|
fee_msat = -int(data["routingFeeSat"])
|
||||||
@ -158,17 +158,17 @@ class PhoenixdWallet(Wallet):
|
|||||||
|
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return PaymentResponse(
|
return PaymentResponse(
|
||||||
False, None, None, None, "Server error: 'invalid json response'"
|
None, None, None, None, "Server error: 'invalid json response'"
|
||||||
)
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return PaymentResponse(
|
return PaymentResponse(
|
||||||
False, None, None, None, "Server error: 'missing required fields'"
|
None, None, None, None, "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(
|
||||||
False, None, None, None, f"Unable to connect to {self.endpoint}."
|
None, None, None, None, 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:
|
||||||
@ -189,6 +189,7 @@ class PhoenixdWallet(Wallet):
|
|||||||
return PaymentPendingStatus()
|
return PaymentPendingStatus()
|
||||||
|
|
||||||
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||||
|
# TODO: how can we detect a failed payment?
|
||||||
try:
|
try:
|
||||||
r = await self.client.get(f"/payments/outgoing/{checking_id}")
|
r = await self.client.get(f"/payments/outgoing/{checking_id}")
|
||||||
if r.is_error:
|
if r.is_error:
|
||||||
|
@ -1310,6 +1310,58 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "failed",
|
||||||
|
"call_params": {
|
||||||
|
"bolt11": "lnbc210n1pjlgal5sp5xr3uwlfm7ltumdjyukhys0z2rw6grgm8me9k4w9vn05zt9svzzjspp5ud2jdfpaqn5c2k2vphatsjypfafyk8rcvkvwexnrhmwm94ex4jtqdqu24hxjapq23jhxapqf9h8vmmfvdjscqpjrzjqta942048v7qxh5x7pxwplhmtwfl0f25cq23jh87rhx7lgrwwvv86r90guqqnwgqqqqqqqqqqqqqqpsqyg9qxpqysgqylngsyg960lltngzy90e8n22v4j2hvjs4l4ttuy79qqefjv8q87q9ft7uhwdjakvnsgk44qyhalv6ust54x98whl3q635hkwgsyw8xgqjl7jwu",
|
||||||
|
"fee_limit_msat": 25000
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"success": false,
|
||||||
|
"pending": false,
|
||||||
|
"failed": true,
|
||||||
|
"checking_id": null,
|
||||||
|
"fee_msat": null,
|
||||||
|
"preimage": null
|
||||||
|
},
|
||||||
|
"mocks": {
|
||||||
|
"corelightningrest": {
|
||||||
|
"pay_invoice_endpoint": [
|
||||||
|
{
|
||||||
|
"request_type": "data",
|
||||||
|
"request_body": {
|
||||||
|
"invoice": "lnbc210n1pjlgal5sp5xr3uwlfm7ltumdjyukhys0z2rw6grgm8me9k4w9vn05zt9svzzjspp5ud2jdfpaqn5c2k2vphatsjypfafyk8rcvkvwexnrhmwm94ex4jtqdqu24hxjapq23jhxapqf9h8vmmfvdjscqpjrzjqta942048v7qxh5x7pxwplhmtwfl0f25cq23jh87rhx7lgrwwvv86r90guqqnwgqqqqqqqqqqqqqqpsqyg9qxpqysgqylngsyg960lltngzy90e8n22v4j2hvjs4l4ttuy79qqefjv8q87q9ft7uhwdjakvnsgk44qyhalv6ust54x98whl3q635hkwgsyw8xgqjl7jwu",
|
||||||
|
"maxfeepercent": "119.04761905",
|
||||||
|
"exemptfee": 0
|
||||||
|
},
|
||||||
|
"response_type": "json",
|
||||||
|
"response": {
|
||||||
|
"status": "failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lndrest": {
|
||||||
|
"pay_invoice_endpoint": [
|
||||||
|
{
|
||||||
|
"request_type": "json",
|
||||||
|
"request_body": {
|
||||||
|
"payment_request": "lnbc210n1pjlgal5sp5xr3uwlfm7ltumdjyukhys0z2rw6grgm8me9k4w9vn05zt9svzzjspp5ud2jdfpaqn5c2k2vphatsjypfafyk8rcvkvwexnrhmwm94ex4jtqdqu24hxjapq23jhxapqf9h8vmmfvdjscqpjrzjqta942048v7qxh5x7pxwplhmtwfl0f25cq23jh87rhx7lgrwwvv86r90guqqnwgqqqqqqqqqqqqqqpsqyg9qxpqysgqylngsyg960lltngzy90e8n22v4j2hvjs4l4ttuy79qqefjv8q87q9ft7uhwdjakvnsgk44qyhalv6ust54x98whl3q635hkwgsyw8xgqjl7jwu",
|
||||||
|
"fee_limit": 25000
|
||||||
|
},
|
||||||
|
"response_type": "json",
|
||||||
|
"response": {
|
||||||
|
"payment_error": "Test Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"alby": {},
|
||||||
|
"eclair": [],
|
||||||
|
"lnbits": [],
|
||||||
|
"phoenixd": []
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "pending, no fee",
|
"description": "pending, no fee",
|
||||||
"call_params": {
|
"call_params": {
|
||||||
@ -1462,8 +1514,8 @@
|
|||||||
},
|
},
|
||||||
"expect": {
|
"expect": {
|
||||||
"success": false,
|
"success": false,
|
||||||
"pending": false,
|
"pending": true,
|
||||||
"failed": true,
|
"failed": false,
|
||||||
"checking_id": null,
|
"checking_id": null,
|
||||||
"fee_msat": null,
|
"fee_msat": null,
|
||||||
"preimage": null,
|
"preimage": null,
|
||||||
@ -1481,25 +1533,14 @@
|
|||||||
},
|
},
|
||||||
"response_type": "json",
|
"response_type": "json",
|
||||||
"response": {
|
"response": {
|
||||||
|
"status": "pending",
|
||||||
"error": "Test Error"
|
"error": "Test Error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lndrest": {
|
"lndrest": {
|
||||||
"pay_invoice_endpoint": [
|
"pay_invoice_endpoint": []
|
||||||
{
|
|
||||||
"request_type": "json",
|
|
||||||
"request_body": {
|
|
||||||
"payment_request": "lnbc210n1pjlgal5sp5xr3uwlfm7ltumdjyukhys0z2rw6grgm8me9k4w9vn05zt9svzzjspp5ud2jdfpaqn5c2k2vphatsjypfafyk8rcvkvwexnrhmwm94ex4jtqdqu24hxjapq23jhxapqf9h8vmmfvdjscqpjrzjqta942048v7qxh5x7pxwplhmtwfl0f25cq23jh87rhx7lgrwwvv86r90guqqnwgqqqqqqqqqqqqqqpsqyg9qxpqysgqylngsyg960lltngzy90e8n22v4j2hvjs4l4ttuy79qqefjv8q87q9ft7uhwdjakvnsgk44qyhalv6ust54x98whl3q635hkwgsyw8xgqjl7jwu",
|
|
||||||
"fee_limit": 25000
|
|
||||||
},
|
|
||||||
"response_type": "json",
|
|
||||||
"response": {
|
|
||||||
"payment_error": "Test Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"alby": {
|
"alby": {
|
||||||
"pay_invoice_endpoint": []
|
"pay_invoice_endpoint": []
|
||||||
@ -1530,31 +1571,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lnbits": {
|
"lnbits": {
|
||||||
"pay_invoice_endpoint": [
|
"pay_invoice_endpoint": []
|
||||||
{
|
|
||||||
"request_type": "json",
|
|
||||||
"request_body": {
|
|
||||||
"out": true,
|
|
||||||
"blt11": "lnbc5550n1pnq9jg3sp52rvwstvjcypjsaenzdh0h30jazvzsf8aaye0julprtth9kysxtuspp5e5s3z7felv4t9zrcc6wpn7ehvjl5yzewanzl5crljdl3jgeffyhqdq2f38xy6t5wvxqzjccqpjrzjq0yzeq76ney45hmjlnlpvu0nakzy2g35hqh0dujq8ujdpr2e42pf2rrs6vqpgcsqqqqqqqqqqqqqqeqqyg9qxpqysgqwftcx89k5pp28435pgxfl2vx3ksemzxccppw2j9yjn0ngr6ed7wj8ztc0d5kmt2mvzdlcgrludhz7jncd5l5l9w820hc4clpwhtqj3gq62g66n"
|
|
||||||
},
|
|
||||||
"response_type": "json",
|
|
||||||
"response": {
|
|
||||||
"detail": "Test Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"get_payment_status_endpoint": [
|
|
||||||
{
|
|
||||||
"response_type": "json",
|
|
||||||
"response": {
|
|
||||||
"paid": true,
|
|
||||||
"preimage": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"details": {
|
|
||||||
"fee": 50
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"phoenixd": {
|
"phoenixd": {
|
||||||
"pay_invoice_endpoint": [
|
"pay_invoice_endpoint": [
|
||||||
@ -1591,8 +1608,8 @@
|
|||||||
"expect": {
|
"expect": {
|
||||||
"error_message": "Server error: 'missing required fields'",
|
"error_message": "Server error: 'missing required fields'",
|
||||||
"success": false,
|
"success": false,
|
||||||
"pending": false,
|
"pending": true,
|
||||||
"failed": true,
|
"failed": false,
|
||||||
"checking_id": null,
|
"checking_id": null,
|
||||||
"fee_msat": null,
|
"fee_msat": null,
|
||||||
"preimage": null
|
"preimage": null
|
||||||
@ -1688,8 +1705,8 @@
|
|||||||
"expect": {
|
"expect": {
|
||||||
"error_message": "Server error: 'invalid json response'",
|
"error_message": "Server error: 'invalid json response'",
|
||||||
"success": false,
|
"success": false,
|
||||||
"pending": false,
|
"pending": true,
|
||||||
"failed": true,
|
"failed": false,
|
||||||
"checking_id": null,
|
"checking_id": null,
|
||||||
"fee_msat": null,
|
"fee_msat": null,
|
||||||
"preimage": null
|
"preimage": null
|
||||||
@ -1806,8 +1823,8 @@
|
|||||||
"expect": {
|
"expect": {
|
||||||
"error_message": "Unable to connect to http://127.0.0.1:8555.",
|
"error_message": "Unable to connect to http://127.0.0.1:8555.",
|
||||||
"success": false,
|
"success": false,
|
||||||
"pending": false,
|
"pending": true,
|
||||||
"failed": true,
|
"failed": false,
|
||||||
"checking_id": null,
|
"checking_id": null,
|
||||||
"fee_msat": null,
|
"fee_msat": null,
|
||||||
"preimage": null
|
"preimage": null
|
||||||
@ -1936,8 +1953,8 @@
|
|||||||
"expect": {
|
"expect": {
|
||||||
"error_message": "Unable to connect to http://127.0.0.1:8555.",
|
"error_message": "Unable to connect to http://127.0.0.1:8555.",
|
||||||
"success": false,
|
"success": false,
|
||||||
"pending": false,
|
"pending": true,
|
||||||
"failed": true,
|
"failed": false,
|
||||||
"checking_id": null,
|
"checking_id": null,
|
||||||
"fee_msat": null,
|
"fee_msat": null,
|
||||||
"preimage": null
|
"preimage": null
|
||||||
@ -2643,7 +2660,17 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lnbits": {
|
"lnbits": {
|
||||||
"get_payment_status_endpoint": []
|
"get_payment_status_endpoint": [
|
||||||
|
{
|
||||||
|
"response_type": "json",
|
||||||
|
"response": {
|
||||||
|
"paid": false,
|
||||||
|
"status": "failed",
|
||||||
|
"preimage": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"details": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"phoenixd": {
|
"phoenixd": {
|
||||||
"description": "phoenixd.py doesn't handle the 'failed' status for `get_invoice_status`",
|
"description": "phoenixd.py doesn't handle the 'failed' status for `get_invoice_status`",
|
||||||
|
@ -818,7 +818,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "error",
|
"description": "failed",
|
||||||
"call_params": {
|
"call_params": {
|
||||||
"bolt11": "lnbc210n1pjlgal5sp5xr3uwlfm7ltumdjyukhys0z2rw6grgm8me9k4w9vn05zt9svzzjspp5ud2jdfpaqn5c2k2vphatsjypfafyk8rcvkvwexnrhmwm94ex4jtqdqu24hxjapq23jhxapqf9h8vmmfvdjscqpjrzjqta942048v7qxh5x7pxwplhmtwfl0f25cq23jh87rhx7lgrwwvv86r90guqqnwgqqqqqqqqqqqqqqpsqyg9qxpqysgqylngsyg960lltngzy90e8n22v4j2hvjs4l4ttuy79qqefjv8q87q9ft7uhwdjakvnsgk44qyhalv6ust54x98whl3q635hkwgsyw8xgqjl7jwu",
|
"bolt11": "lnbc210n1pjlgal5sp5xr3uwlfm7ltumdjyukhys0z2rw6grgm8me9k4w9vn05zt9svzzjspp5ud2jdfpaqn5c2k2vphatsjypfafyk8rcvkvwexnrhmwm94ex4jtqdqu24hxjapq23jhxapqf9h8vmmfvdjscqpjrzjqta942048v7qxh5x7pxwplhmtwfl0f25cq23jh87rhx7lgrwwvv86r90guqqnwgqqqqqqqqqqqqqqpsqyg9qxpqysgqylngsyg960lltngzy90e8n22v4j2hvjs4l4ttuy79qqefjv8q87q9ft7uhwdjakvnsgk44qyhalv6ust54x98whl3q635hkwgsyw8xgqjl7jwu",
|
||||||
"fee_limit_msat": 25000
|
"fee_limit_msat": 25000
|
||||||
@ -826,31 +826,17 @@
|
|||||||
"expect": {
|
"expect": {
|
||||||
"__eval__:error_message": "\"Payment failed: \" in \"{error_message}\"",
|
"__eval__:error_message": "\"Payment failed: \" in \"{error_message}\"",
|
||||||
"success": false,
|
"success": false,
|
||||||
|
"pending": false,
|
||||||
|
"failed": true,
|
||||||
"checking_id": null,
|
"checking_id": null,
|
||||||
"fee_msat": null,
|
"fee_msat": null,
|
||||||
"preimage": null
|
"preimage": null
|
||||||
},
|
},
|
||||||
"mocks": {
|
"mocks": {
|
||||||
"breez": {
|
"breez": {},
|
||||||
"sdk_services": [
|
|
||||||
{
|
|
||||||
"response_type": "data",
|
|
||||||
"response": {
|
|
||||||
"send_payment": {
|
|
||||||
"request_type": "function",
|
|
||||||
"response_type": "exception",
|
|
||||||
"response": {
|
|
||||||
"data": "test-error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"corelightning": {
|
"corelightning": {
|
||||||
"ln": [
|
"ln": [
|
||||||
{
|
{
|
||||||
"description": "test-error",
|
|
||||||
"response": {
|
"response": {
|
||||||
"call": {
|
"call": {
|
||||||
"description": "indirect call to `pay` (via `call`)",
|
"description": "indirect call to `pay` (via `call`)",
|
||||||
@ -867,7 +853,20 @@
|
|||||||
},
|
},
|
||||||
"response_type": "exception",
|
"response_type": "exception",
|
||||||
"response": {
|
"response": {
|
||||||
"data": "test-error"
|
"module": "pyln.client.lightning",
|
||||||
|
"class": "RpcError",
|
||||||
|
"data": {
|
||||||
|
"method": "test_method",
|
||||||
|
"payload": "y",
|
||||||
|
"error": {
|
||||||
|
"code": 205,
|
||||||
|
"attempts": [
|
||||||
|
{
|
||||||
|
"fail_reason": "some reason"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -994,7 +993,77 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "error",
|
||||||
|
"call_params": {
|
||||||
|
"bolt11": "lnbc210n1pjlgal5sp5xr3uwlfm7ltumdjyukhys0z2rw6grgm8me9k4w9vn05zt9svzzjspp5ud2jdfpaqn5c2k2vphatsjypfafyk8rcvkvwexnrhmwm94ex4jtqdqu24hxjapq23jhxapqf9h8vmmfvdjscqpjrzjqta942048v7qxh5x7pxwplhmtwfl0f25cq23jh87rhx7lgrwwvv86r90guqqnwgqqqqqqqqqqqqqqpsqyg9qxpqysgqylngsyg960lltngzy90e8n22v4j2hvjs4l4ttuy79qqefjv8q87q9ft7uhwdjakvnsgk44qyhalv6ust54x98whl3q635hkwgsyw8xgqjl7jwu",
|
||||||
|
"fee_limit_msat": 25000
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"__eval__:error_message": "\"Payment failed: \" in \"{error_message}\"",
|
||||||
|
"success": false,
|
||||||
|
"pending": true,
|
||||||
|
"failed": false,
|
||||||
|
"checking_id": null,
|
||||||
|
"fee_msat": null,
|
||||||
|
"preimage": null
|
||||||
|
},
|
||||||
|
"mocks": {
|
||||||
|
"breez": {
|
||||||
|
"sdk_services": [
|
||||||
|
{
|
||||||
|
"response_type": "data",
|
||||||
|
"response": {
|
||||||
|
"send_payment": {
|
||||||
|
"request_type": "function",
|
||||||
|
"response_type": "exception",
|
||||||
|
"response": {
|
||||||
|
"data": "test-error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"corelightning": {
|
||||||
|
"ln": [
|
||||||
|
{
|
||||||
|
"description": "test-error",
|
||||||
|
"response": {
|
||||||
|
"call": {
|
||||||
|
"description": "indirect call to `pay` (via `call`)",
|
||||||
|
"request_type": "function",
|
||||||
|
"request_data": {
|
||||||
|
"args": [
|
||||||
|
"pay",
|
||||||
|
{
|
||||||
|
"bolt11": "lnbc210n1pjlgal5sp5xr3uwlfm7ltumdjyukhys0z2rw6grgm8me9k4w9vn05zt9svzzjspp5ud2jdfpaqn5c2k2vphatsjypfafyk8rcvkvwexnrhmwm94ex4jtqdqu24hxjapq23jhxapqf9h8vmmfvdjscqpjrzjqta942048v7qxh5x7pxwplhmtwfl0f25cq23jh87rhx7lgrwwvv86r90guqqnwgqqqqqqqqqqqqqqpsqyg9qxpqysgqylngsyg960lltngzy90e8n22v4j2hvjs4l4ttuy79qqefjv8q87q9ft7uhwdjakvnsgk44qyhalv6ust54x98whl3q635hkwgsyw8xgqjl7jwu",
|
||||||
|
"description": "Unit Test Invoice",
|
||||||
|
"maxfee": 25000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"response_type": "exception",
|
||||||
|
"response": {
|
||||||
|
"data": "test-error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lndrpc": {
|
||||||
|
"rpc": [
|
||||||
|
{
|
||||||
|
"response": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"routerpc": [
|
||||||
{
|
{
|
||||||
"description": "RPC error.",
|
"description": "RPC error.",
|
||||||
"response": {
|
"response": {
|
||||||
@ -1024,11 +1093,13 @@
|
|||||||
"fee_limit_msat": 25000
|
"fee_limit_msat": 25000
|
||||||
},
|
},
|
||||||
"expect": {
|
"expect": {
|
||||||
|
"error_message": "Server error: 'missing required fields'",
|
||||||
"success": false,
|
"success": false,
|
||||||
|
"pending": true,
|
||||||
|
"failed": false,
|
||||||
"checking_id": null,
|
"checking_id": null,
|
||||||
"fee_msat": null,
|
"fee_msat": null,
|
||||||
"preimage": null,
|
"preimage": null
|
||||||
"error_message": "Server error: 'missing required fields'"
|
|
||||||
},
|
},
|
||||||
"mocks": {
|
"mocks": {
|
||||||
"breez": {
|
"breez": {
|
||||||
@ -1071,11 +1142,13 @@
|
|||||||
"fee_limit_msat": 25000
|
"fee_limit_msat": 25000
|
||||||
},
|
},
|
||||||
"expect": {
|
"expect": {
|
||||||
|
"error_message": "RPC 'test_method' failed with 'test-error'.",
|
||||||
"success": false,
|
"success": false,
|
||||||
|
"pending": true,
|
||||||
|
"failed": false,
|
||||||
"checking_id": null,
|
"checking_id": null,
|
||||||
"fee_msat": null,
|
"fee_msat": null,
|
||||||
"preimage": null,
|
"preimage": null
|
||||||
"error_message": "RPC 'test_method' failed with 'test-error'."
|
|
||||||
},
|
},
|
||||||
"mocks": {
|
"mocks": {
|
||||||
"breez": {
|
"breez": {
|
||||||
@ -1083,40 +1156,6 @@
|
|||||||
},
|
},
|
||||||
"corelightning": {
|
"corelightning": {
|
||||||
"ln": [
|
"ln": [
|
||||||
{
|
|
||||||
"response": {
|
|
||||||
"call": {
|
|
||||||
"description": "indirect call to `pay` (via `call`)",
|
|
||||||
"request_type": "function",
|
|
||||||
"request_data": {
|
|
||||||
"args": [
|
|
||||||
"pay",
|
|
||||||
{
|
|
||||||
"bolt11": "lnbc210n1pjlgal5sp5xr3uwlfm7ltumdjyukhys0z2rw6grgm8me9k4w9vn05zt9svzzjspp5ud2jdfpaqn5c2k2vphatsjypfafyk8rcvkvwexnrhmwm94ex4jtqdqu24hxjapq23jhxapqf9h8vmmfvdjscqpjrzjqta942048v7qxh5x7pxwplhmtwfl0f25cq23jh87rhx7lgrwwvv86r90guqqnwgqqqqqqqqqqqqqqpsqyg9qxpqysgqylngsyg960lltngzy90e8n22v4j2hvjs4l4ttuy79qqefjv8q87q9ft7uhwdjakvnsgk44qyhalv6ust54x98whl3q635hkwgsyw8xgqjl7jwu",
|
|
||||||
"description": "Unit Test Invoice",
|
|
||||||
"maxfee": 25000
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"response_type": "exception",
|
|
||||||
"response": {
|
|
||||||
"module": "pyln.client.lightning",
|
|
||||||
"class": "RpcError",
|
|
||||||
"data": {
|
|
||||||
"method": "test_method",
|
|
||||||
"payload": "y",
|
|
||||||
"error": {
|
|
||||||
"attempts": [
|
|
||||||
{
|
|
||||||
"fail_reason": "RPC 'test_method' failed with 'test-error'."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"response": {
|
"response": {
|
||||||
"call": {
|
"call": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user