From dc0e56e45014b5e2b4587bf44820dfb0aafd1263 Mon Sep 17 00:00:00 2001 From: 9qeklajc Date: Tue, 7 Jul 2026 15:54:37 +0200 Subject: [PATCH] add new status code for refund not ready --- routstr/balance.py | 42 +++++++++++++++++++++++++++++++++++++- tests/unit/test_balance.py | 30 +++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/routstr/balance.py b/routstr/balance.py index 2f24cb8c..9f61ca44 100644 --- a/routstr/balance.py +++ b/routstr/balance.py @@ -259,11 +259,26 @@ async def refund_wallet_endpoint( ) ) in_tx = in_tx_result.first() + cashu_fingerprint = hashlib.sha256(x_cashu.encode()).hexdigest()[:16] if in_tx is None: + logger.info( + "refund_wallet_endpoint: x-cashu inbound transaction not found", + extra={"cashu_fingerprint": cashu_fingerprint}, + ) raise HTTPException(status_code=404, detail="Refund not found") # Use the request_id to find the associated "out" (refund) transaction if in_tx.request_id is None: + logger.info( + "refund_wallet_endpoint: x-cashu inbound transaction has no request_id", + extra={ + "cashu_fingerprint": cashu_fingerprint, + "cashu_transaction_id": in_tx.id, + "amount": in_tx.amount, + "unit": in_tx.unit, + "mint_url": in_tx.mint_url, + }, + ) raise HTTPException(status_code=404, detail="Refund not found") out_tx_result = await session.exec( @@ -274,8 +289,33 @@ async def refund_wallet_endpoint( ) out_tx = out_tx_result.first() if out_tx is None: - raise HTTPException(status_code=404, detail="Refund not found") + logger.info( + "refund_wallet_endpoint: x-cashu refund transaction not ready", + extra={ + "cashu_fingerprint": cashu_fingerprint, + "cashu_transaction_id": in_tx.id, + "refund_request_id": in_tx.request_id, + "amount": in_tx.amount, + "unit": in_tx.unit, + "mint_url": in_tx.mint_url, + "age_seconds": max(0, int(time.time()) - in_tx.created_at), + }, + ) + raise HTTPException( + status_code=425, + detail="Refund not ready. Retry later.", + headers={"Retry-After": "5"}, + ) if out_tx.swept: + logger.info( + "refund_wallet_endpoint: x-cashu refund transaction already swept", + extra={ + "cashu_fingerprint": cashu_fingerprint, + "cashu_transaction_id": in_tx.id, + "refund_transaction_id": out_tx.id, + "refund_request_id": in_tx.request_id, + }, + ) raise HTTPException(status_code=410, detail="Refund has been swept") out_tx.collected = True diff --git a/tests/unit/test_balance.py b/tests/unit/test_balance.py index ba9d54da..0b746508 100644 --- a/tests/unit/test_balance.py +++ b/tests/unit/test_balance.py @@ -2,6 +2,7 @@ import json from unittest.mock import AsyncMock, MagicMock, patch import pytest +from fastapi import HTTPException from fastapi.responses import JSONResponse from routstr.balance import refund_wallet_endpoint @@ -88,8 +89,6 @@ async def test_refund_x_cashu_sat_unit() -> None: @pytest.mark.asyncio async def test_refund_x_cashu_not_found_raises_404() -> None: - from fastapi import HTTPException - session = MagicMock() session.exec = AsyncMock(return_value=_exec_result(None)) @@ -104,9 +103,32 @@ async def test_refund_x_cashu_not_found_raises_404() -> None: @pytest.mark.asyncio -async def test_refund_x_cashu_swept_raises_410() -> None: - from fastapi import HTTPException +async def test_refund_x_cashu_pending_out_tx_raises_425() -> None: + in_tx = _make_cashu_tx( + token="cashuApending_token", + amount=0, + unit="msat", + type="in", + request_id="req-pending", + ) + session = MagicMock() + session.exec = AsyncMock(side_effect=[_exec_result(in_tx), _exec_result(None)]) + + with pytest.raises(HTTPException) as exc_info: + await refund_wallet_endpoint( + authorization="Bearer sk-somekey", + x_cashu="cashuApending_token", + session=session, + ) + + assert exc_info.value.status_code == 425 + assert exc_info.value.detail == "Refund not ready. Retry later." + assert exc_info.value.headers == {"Retry-After": "5"} + + +@pytest.mark.asyncio +async def test_refund_x_cashu_swept_raises_410() -> None: in_tx = _make_cashu_tx(token="cashuAswept_token", amount=0, unit="msat", type="in", request_id="req-swept") out_tx = _make_cashu_tx(token="cashuAswept", amount=100, unit="msat", type="out", request_id="req-swept", swept=True)