87 lines
2.3 KiB
Python
87 lines
2.3 KiB
Python
"""
|
|
FX Service — fetches and caches daily USD→VND exchange rate.
|
|
Primary source: open.er-api.com (free, no key)
|
|
Fallback: last known rate from DB
|
|
"""
|
|
|
|
import requests
|
|
from datetime import date, datetime
|
|
from app.extensions import db
|
|
from app.models.fx_rate import FxRate
|
|
|
|
|
|
ER_API_URL = 'https://open.er-api.com/v6/latest/USD'
|
|
REQUEST_TIMEOUT = 8 # seconds
|
|
|
|
|
|
def get_today_rate():
|
|
"""
|
|
Return today's USD→VND rate as a dict:
|
|
{ 'rate': 25450.00, 'date': date(...), 'source': '...', 'is_stale': False }
|
|
"""
|
|
today = date.today()
|
|
|
|
# 1. Check cache
|
|
cached = FxRate.query.filter_by(date=today).first()
|
|
if cached:
|
|
return {
|
|
'rate': float(cached.usd_to_vnd),
|
|
'date': cached.date,
|
|
'source': cached.source,
|
|
'is_stale': False,
|
|
}
|
|
|
|
# 2. Fetch from API
|
|
rate, source = _fetch_from_api()
|
|
|
|
if rate:
|
|
record = FxRate(
|
|
date=today,
|
|
usd_to_vnd=rate,
|
|
source=source,
|
|
fetched_at=datetime.utcnow(),
|
|
)
|
|
db.session.add(record)
|
|
try:
|
|
db.session.commit()
|
|
except Exception:
|
|
db.session.rollback()
|
|
return {'rate': rate, 'date': today, 'source': source, 'is_stale': False}
|
|
|
|
# 3. Fallback — last known rate
|
|
last = FxRate.query.order_by(FxRate.date.desc()).first()
|
|
if last:
|
|
return {
|
|
'rate': float(last.usd_to_vnd),
|
|
'date': last.date,
|
|
'source': last.source,
|
|
'is_stale': True,
|
|
}
|
|
|
|
# 4. Nothing available
|
|
return None
|
|
|
|
|
|
def _fetch_from_api():
|
|
"""Try open.er-api.com. Returns (rate, source) or (None, None)."""
|
|
try:
|
|
resp = requests.get(ER_API_URL, timeout=REQUEST_TIMEOUT)
|
|
if resp.status_code == 200:
|
|
data = resp.json()
|
|
vnd = data.get('rates', {}).get('VND')
|
|
if vnd:
|
|
return float(vnd), 'exchangerate-api'
|
|
except Exception:
|
|
pass
|
|
return None, None
|
|
|
|
|
|
def get_rate_history(days=30):
|
|
"""Return list of FxRate records for last N days, oldest first."""
|
|
from datetime import timedelta
|
|
since = date.today() - timedelta(days=days)
|
|
return (FxRate.query
|
|
.filter(FxRate.date >= since)
|
|
.order_by(FxRate.date.asc())
|
|
.all())
|