fix: include fiat amount in BTCPay webhook payload; quiet wallet-rpc spam

Two fixes:

1. _build_payload now emits amount (fiat), currency, and amount_crypto. Downstream processors like Medusa need the amount to capture the right value. Without it, Medusa was capturing 0 and leaving payments stuck 'authorized' forever despite the InvoiceSettled/InvoicePaymentSettled webhooks firing correctly at 10+ confirmations.

2. get_status() called set_daemon RPC without first opening a wallet. set_daemon requires an open wallet, so on backends holding no wallet (the common idle case with our 3-backend hash-sharded setup) it errored with RPC -13 'No wallet file' every 5 seconds, flooding logs with a dozens-per-minute stream of raw RPC error dumps from the python-monero library logger. Dropped the set_daemon call — _daemon_height() already validates daemon reachability directly and doesn't need wallet-rpc.

3. Silenced monero.backends.jsonrpc.wallet library logger at CRITICAL so expected transient -13 errors from our _ensure_wallet_open close_wallet call stop polluting the stream.
This commit is contained in:
jwinterm
2026-04-18 16:31:32 -04:00
parent 8e48fdbda6
commit eb15c5202a
3 changed files with 34 additions and 17 deletions
+17 -1
View File
@@ -118,6 +118,10 @@ def _build_payload(
invoice: Invoice,
manually_marked: bool,
) -> dict[str, object]:
metadata = invoice.metadata_json or {}
quote = metadata.get("quote") if isinstance(metadata, dict) else None
fiat_amount = quote.get("fiat_amount") if isinstance(quote, dict) else None
fiat_currency = quote.get("fiat_currency") if isinstance(quote, dict) else None
return {
"type": event_type,
"timestamp": int(time.time()),
@@ -127,7 +131,19 @@ def _build_payload(
"overPaid": False,
"partiallyPaid": False,
"afterExpiration": _after_expiration(invoice),
"metadata": invoice.metadata_json or {},
"metadata": metadata,
# Fiat amount/currency so downstream processors (e.g. Medusa) can
# capture at the right number. Omitting this defaults Medusa's capture
# to 0 which leaves captured_at NULL and payment_collection stuck at
# authorized forever.
"amount": str(fiat_amount) if fiat_amount is not None else None,
"currency": fiat_currency,
# Crypto amount is also useful for reconciliation in some downstreams.
"amount_crypto": (
str(invoice.amount_xmr)
if getattr(invoice, "amount_xmr", None) is not None
else None
),
}
+9 -16
View File
@@ -99,6 +99,14 @@ class MoneroWalletService:
self._wallet_dir = MONERO_WALLET_RPC_WALLET_DIR.strip()
def get_status(self) -> dict[str, str | int | None]:
# Probe each wallet-rpc backend with get_version (works without an open
# wallet). Probe daemon reachability via direct HTTP get_height, which
# bypasses wallet-rpc. We previously also called set_daemon here as a
# "secondary daemon check", but set_daemon requires an open wallet —
# on backends that currently hold no wallet (most of the time for idle
# reconcilers) that produces a constant stream of RPC -13 "No wallet
# file" errors from the python-monero logger. The get_version +
# _daemon_height pair is sufficient and doesn't require any wallet.
daemon_height = self._daemon_height()
for backend in self._backends:
try:
@@ -109,24 +117,9 @@ class MoneroWalletService:
"daemon": "ok" if daemon_height is not None else "unreachable",
"daemon_height": daemon_height,
}
if self._daemon_address:
try:
self._backends[0].client.raw_request(
"set_daemon",
{"address": self._daemon_address},
)
except RPCError as exc:
message = str(exc)
if "no connection to daemon" in message or "no_connection_to_daemon" in message:
return {
"wallet_rpc": "ok",
"daemon": "unreachable",
"daemon_height": daemon_height,
}
return {
"wallet_rpc": "ok",
"daemon": "ok" if self._daemon_address else "unknown",
"daemon": "ok" if daemon_height is not None else "unreachable",
"daemon_height": daemon_height,
}
+8
View File
@@ -28,6 +28,14 @@ def main() -> None:
level_name = "INFO"
level = getattr(logging, level_name.upper(), logging.INFO)
logging.basicConfig(level=level)
# Silence expected RPC -13 ("No wallet file") spam from the python-monero
# library. Those happen routinely when close_wallet is called on an idle
# backend (by _ensure_wallet_open) and are handled by our retry logic;
# they are not actionable errors and were drowning real log output.
# Real wallet-rpc problems still bubble up as exceptions which we log
# explicitly through logger.error / logger.exception.
logging.getLogger("monero.backends.jsonrpc.wallet").setLevel(logging.CRITICAL)
while True:
status_db: Session | None = None
try: