diff --git a/api/app/btcpay_webhooks.py b/api/app/btcpay_webhooks.py index 8a48005..b2754c7 100644 --- a/api/app/btcpay_webhooks.py +++ b/api/app/btcpay_webhooks.py @@ -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 + ), } diff --git a/api/app/monero_service.py b/api/app/monero_service.py index b3606ad..a773859 100644 --- a/api/app/monero_service.py +++ b/api/app/monero_service.py @@ -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, } diff --git a/api/app/reconciler.py b/api/app/reconciler.py index bd0c0a3..1dd5491 100644 --- a/api/app/reconciler.py +++ b/api/app/reconciler.py @@ -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: