"""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']