Refactor backend and add registry build workflow
Build and publish Docker image / build-and-publish (push) Successful in 2m45s

This commit is contained in:
wfalkowski committed 2026-10-20 16:44:49 +00:00
commit c177d7981d
43 files changed
+18124

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
"""Exchange-rate retrieval and Stripe currency conversion."""
from datetime import datetime
import requests
from runtime_config import (
CACHE_DURATION,
EXCHANGE_RATE_CACHE,
EXCHANGE_RATE_LOCK,
logger,
)
__all__ = ['calculate_stripe_amount', 'fetch_exchange_rates', 'get_cached_exchange_rates']
def calculate_stripe_amount(credits: int, currency: str) -> int:
"""
Calculate amount in smallest currency unit (cents, pence, etc.)
Base rate: 100 credits = 1.00 USD
"""
# Get USD price
usd_price = credits / 100 # $1 per 100 credits
# Get exchange rate
rates = get_cached_exchange_rates()
rate = rates.get(currency, 1.0)
# Convert to target currency
currency_price = usd_price * rate
# Convert to smallest unit (cents)
# Most currencies use 2 decimal places, but some don't
if currency in ['JPY', 'KRW']: # Zero decimal currencies
return int(currency_price)
else:
return int(currency_price * 100)
def fetch_exchange_rates():
"""
Fetch exchange rates from the free currency API
Uses EUR as base currency
"""
try:
# Using the fawazahmed0 currency API - EUR as base
url = 'https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/eur.json'
logger.info("Fetching exchange rates from API...")
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
if 'eur' in data:
rates_data = data['eur']
# Extract rates for currencies we support - all relative to EUR
new_rates = {
'EUR': 1.00, # Base currency
'USD': rates_data.get('usd', 1.5), # EUR->USD rate
'GBP': rates_data.get('gbp', 0.85), # EUR->GBP rate
'PLN': rates_data.get('pln', 4.32), # EUR->PLN rate
'PHP': rates_data.get('php', 60.50), # EUR->PHP rate
'CZK': rates_data.get('czk', 24.65) # EUR->CZK rate
}
with EXCHANGE_RATE_LOCK:
EXCHANGE_RATE_CACHE['rates'] = new_rates
EXCHANGE_RATE_CACHE['last_updated'] = datetime.now().isoformat()
logger.info(f"✅ Exchange rates updated successfully: {new_rates}")
return True
else:
logger.error("Invalid response format from currency API")
return False
except requests.exceptions.Timeout:
logger.error("❌ Timeout fetching exchange rates")
return False
except requests.exceptions.RequestException as e:
logger.error(f"❌ Error fetching exchange rates: {e}")
return False
except Exception as e:
logger.error(f"❌ Unexpected error fetching exchange rates: {e}")
return False
def get_cached_exchange_rates():
"""Get cached exchange rates, fetch new ones if cache is stale"""
with EXCHANGE_RATE_LOCK:
last_updated = EXCHANGE_RATE_CACHE.get('last_updated')
# If never updated or cache is stale, fetch new rates
if last_updated is None:
logger.info("Exchange rates cache is empty, fetching...")
# Release lock before fetching to avoid deadlock
elif (datetime.now() - datetime.fromisoformat(last_updated)).total_seconds() > CACHE_DURATION:
logger.info("Exchange rates cache is stale, fetching...")
else:
# Cache is fresh, return it
return EXCHANGE_RATE_CACHE['rates']
# Fetch new rates (outside lock to avoid blocking)
fetch_exchange_rates()
# Return rates (either newly fetched or fallback)
with EXCHANGE_RATE_LOCK:
return EXCHANGE_RATE_CACHE['rates']