fix(api): Add automatic token refresh on 401 errors in GoCardless service.

- Add _make_authenticated_request helper that automatically handles 401 errors
- Clear token cache and retry once when encountering expired tokens
- Refactor all API methods to use centralized request handling
- Fix banks API to properly handle institutions response structure
- Eliminates need for container restarts when tokens expire

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Elisiário Couto
2025-09-24 14:58:45 +01:00
parent d211a14703
commit 36d698f7ce
2 changed files with 44 additions and 55 deletions

View File

@@ -32,7 +32,7 @@ async def get_bank_institutions(
countries=inst["countries"],
logo=inst.get("logo"),
)
for inst in institutions_data
for inst in institutions_data.get("results", [])
]
return APIResponse(

View File

@@ -1,6 +1,6 @@
import json
from pathlib import Path
from typing import Any, Dict, List
from typing import Any, Dict
import httpx
from loguru import logger
@@ -30,6 +30,27 @@ class GoCardlessService:
)
self._token = None
async def _make_authenticated_request(
self, method: str, url: str, **kwargs
) -> Dict[str, Any]:
"""Make authenticated request with automatic token refresh on 401"""
headers = await self._get_auth_headers()
async with httpx.AsyncClient() as client:
response = await client.request(method, url, headers=headers, **kwargs)
_log_rate_limits(response)
# If we get 401, clear token cache and retry once
if response.status_code == 401:
logger.warning("Got 401, clearing token cache and retrying")
self._token = None
headers = await self._get_auth_headers()
response = await client.request(method, url, headers=headers, **kwargs)
_log_rate_limits(response)
response.raise_for_status()
return response.json()
async def _get_auth_headers(self) -> Dict[str, str]:
"""Get authentication headers for GoCardless API"""
token = await self._get_token()
@@ -102,74 +123,42 @@ class GoCardlessService:
with open(auth_file, "w") as f:
json.dump(auth_data, f)
async def get_institutions(self, country: str = "PT") -> List[Dict[str, Any]]:
async def get_institutions(self, country: str = "PT") -> Dict[str, Any]:
"""Get available bank institutions for a country"""
headers = await self._get_auth_headers()
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/institutions/",
headers=headers,
params={"country": country},
)
_log_rate_limits(response)
response.raise_for_status()
return response.json()
return await self._make_authenticated_request(
"GET", f"{self.base_url}/institutions/", params={"country": country}
)
async def create_requisition(
self, institution_id: str, redirect_url: str
) -> Dict[str, Any]:
"""Create a bank connection requisition"""
headers = await self._get_auth_headers()
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/requisitions/",
headers=headers,
json={"institution_id": institution_id, "redirect": redirect_url},
)
_log_rate_limits(response)
response.raise_for_status()
return response.json()
return await self._make_authenticated_request(
"POST",
f"{self.base_url}/requisitions/",
json={"institution_id": institution_id, "redirect": redirect_url},
)
async def get_requisitions(self) -> Dict[str, Any]:
"""Get all requisitions"""
headers = await self._get_auth_headers()
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/requisitions/", headers=headers
)
_log_rate_limits(response)
response.raise_for_status()
return response.json()
return await self._make_authenticated_request(
"GET", f"{self.base_url}/requisitions/"
)
async def get_account_details(self, account_id: str) -> Dict[str, Any]:
"""Get account details"""
headers = await self._get_auth_headers()
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/accounts/{account_id}/", headers=headers
)
_log_rate_limits(response)
response.raise_for_status()
return response.json()
return await self._make_authenticated_request(
"GET", f"{self.base_url}/accounts/{account_id}/"
)
async def get_account_balances(self, account_id: str) -> Dict[str, Any]:
"""Get account balances"""
headers = await self._get_auth_headers()
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/accounts/{account_id}/balances/", headers=headers
)
_log_rate_limits(response)
response.raise_for_status()
return response.json()
return await self._make_authenticated_request(
"GET", f"{self.base_url}/accounts/{account_id}/balances/"
)
async def get_account_transactions(self, account_id: str) -> Dict[str, Any]:
"""Get account transactions"""
headers = await self._get_auth_headers()
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/accounts/{account_id}/transactions/", headers=headers
)
_log_rate_limits(response)
response.raise_for_status()
return response.json()
return await self._make_authenticated_request(
"GET", f"{self.base_url}/accounts/{account_id}/transactions/"
)