mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-09-09 09:26:37 +08:00
feat(payment): 优化国家货币列表获取逻辑
- 添加内置 fallback 国家/货币列表作为备用方案 - 实现多层缓存策略:内存缓存 -> DB 缓存 -> API 请求 - 改进 API 请求逻辑:先获取国家代码列表,再并发请求各国配置 - 使用线程池并发获取各国货币配置信息 - 增加详细的错误处理和日志记录 - 将缓存时间延长至 7 天并添加缓存回写功能 - 优化响应数据结构和字段提取逻辑
This commit is contained in:
+95
-22
@@ -183,47 +183,120 @@ def mark_subscription(account_id: int, request: MarkSubscriptionRequest):
|
|||||||
_countries_cache: dict = {} # {"data": [...], "expires_at": float}
|
_countries_cache: dict = {} # {"data": [...], "expires_at": float}
|
||||||
|
|
||||||
|
|
||||||
|
def _get_fallback_countries():
|
||||||
|
"""内置 fallback 国家/货币列表"""
|
||||||
|
return [
|
||||||
|
{"country_code": "AU", "currency": "AUD", "country_name": "AU"},
|
||||||
|
{"country_code": "BR", "currency": "BRL", "country_name": "BR"},
|
||||||
|
{"country_code": "CA", "currency": "CAD", "country_name": "CA"},
|
||||||
|
{"country_code": "GB", "currency": "GBP", "country_name": "GB"},
|
||||||
|
{"country_code": "HK", "currency": "HKD", "country_name": "HK"},
|
||||||
|
{"country_code": "IN", "currency": "INR", "country_name": "IN"},
|
||||||
|
{"country_code": "JP", "currency": "JPY", "country_name": "JP"},
|
||||||
|
{"country_code": "MX", "currency": "MXN", "country_name": "MX"},
|
||||||
|
{"country_code": "SG", "currency": "SGD", "country_name": "SG"},
|
||||||
|
{"country_code": "TR", "currency": "TRY", "country_name": "TR"},
|
||||||
|
{"country_code": "US", "currency": "USD", "country_name": "US"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@router.get("/countries")
|
@router.get("/countries")
|
||||||
def get_checkout_countries():
|
def get_checkout_countries():
|
||||||
"""从 ChatGPT checkout 接口获取支持的国家/货币列表(缓存 1 小时)"""
|
"""从 ChatGPT checkout 接口获取支持的国家/货币列表(优先读 DB 缓存,成功后回写)"""
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
import curl_cffi.requests as cffi_requests
|
import curl_cffi.requests as cffi_requests
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
|
||||||
|
_DB_CACHE_KEY = "cache.checkout_countries"
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
|
# 1. 内存缓存命中
|
||||||
if _countries_cache.get("expires_at", 0) > now:
|
if _countries_cache.get("expires_at", 0) > now:
|
||||||
return {"success": True, "countries": _countries_cache["data"]}
|
return {"success": True, "countries": _countries_cache["data"]}
|
||||||
|
|
||||||
|
# 2. 读取 DB 缓存
|
||||||
with get_db() as db:
|
with get_db() as db:
|
||||||
proxy = get_settings().get_proxy_url(db=db)
|
proxy = get_settings().get_proxy_url(db=db)
|
||||||
|
db_setting = crud.get_setting(db, _DB_CACHE_KEY)
|
||||||
|
if db_setting and db_setting.value:
|
||||||
|
try:
|
||||||
|
cached = json.loads(db_setting.value)
|
||||||
|
if cached.get("expires_at", 0) > now:
|
||||||
|
_countries_cache.update(cached)
|
||||||
|
return {"success": True, "countries": cached["data"]}
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
proxies = {"http": proxy, "https": proxy} if proxy else None
|
||||||
|
|
||||||
|
# 3. 请求 ChatGPT API 获取国家代码列表
|
||||||
try:
|
try:
|
||||||
resp = cffi_requests.get(
|
resp = cffi_requests.get(
|
||||||
"https://chatgpt.com/backend-api/checkout_pricing_config/countries",
|
"https://chatgpt.com/backend-api/checkout_pricing_config/countries",
|
||||||
proxies={"http": proxy, "https": proxy} if proxy else None,
|
proxies=proxies,
|
||||||
timeout=15,
|
timeout=15,
|
||||||
impersonate="chrome110",
|
impersonate="chrome110",
|
||||||
)
|
)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
raw = resp.json()
|
||||||
countries = data if isinstance(data, list) else data.get("countries", [])
|
country_codes = raw.get("countries", []) if isinstance(raw, dict) else raw
|
||||||
_countries_cache["data"] = countries
|
if not isinstance(country_codes, list) or not country_codes:
|
||||||
_countries_cache["expires_at"] = now + 3600
|
raise ValueError(f"国家列表为空或格式异常: {str(raw)[:200]}")
|
||||||
return {"success": True, "countries": countries}
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"获取国家列表失败: {e}")
|
logger.warning(f"获取国家代码列表失败: {e}")
|
||||||
fallback = [
|
return {"success": False, "countries": _get_fallback_countries(), "error": str(e)}
|
||||||
{"country_code": "SG", "currency": "SGD", "country_name": "Singapore"},
|
|
||||||
{"country_code": "US", "currency": "USD", "country_name": "United States"},
|
# 4. 并发请求各国 configs,提取 symbol_code
|
||||||
{"country_code": "TR", "currency": "TRY", "country_name": "Turkey"},
|
def fetch_config(code: str):
|
||||||
{"country_code": "JP", "currency": "JPY", "country_name": "Japan"},
|
try:
|
||||||
{"country_code": "HK", "currency": "HKD", "country_name": "Hong Kong"},
|
r = cffi_requests.get(
|
||||||
{"country_code": "GB", "currency": "GBP", "country_name": "United Kingdom"},
|
f"https://chatgpt.com/backend-api/checkout_pricing_config/configs/{code}",
|
||||||
{"country_code": "AU", "currency": "AUD", "country_name": "Australia"},
|
proxies=proxies,
|
||||||
{"country_code": "CA", "currency": "CAD", "country_name": "Canada"},
|
timeout=10,
|
||||||
{"country_code": "IN", "currency": "INR", "country_name": "India"},
|
impersonate="chrome110",
|
||||||
{"country_code": "BR", "currency": "BRL", "country_name": "Brazil"},
|
)
|
||||||
{"country_code": "MX", "currency": "MXN", "country_name": "Mexico"},
|
if r.status_code == 200:
|
||||||
]
|
data = r.json()
|
||||||
return {"success": False, "countries": fallback, "error": str(e)}
|
cfg = data.get("currency_config", {})
|
||||||
|
currency = cfg.get("symbol_code") or cfg.get("symbol") or ""
|
||||||
|
if currency:
|
||||||
|
return {"country_code": code, "currency": currency, "country_name": code}
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
countries = []
|
||||||
|
with ThreadPoolExecutor(max_workers=10) as executor:
|
||||||
|
futures = {executor.submit(fetch_config, code): code for code in country_codes}
|
||||||
|
for future in as_completed(futures):
|
||||||
|
result = future.result()
|
||||||
|
if result:
|
||||||
|
countries.append(result)
|
||||||
|
|
||||||
|
countries.sort(key=lambda c: c["country_code"])
|
||||||
|
|
||||||
|
if not countries:
|
||||||
|
logger.warning("所有国家 configs 请求均失败,使用 fallback")
|
||||||
|
return {"success": False, "countries": _get_fallback_countries(), "error": "所有 configs 请求失败"}
|
||||||
|
|
||||||
|
# 5. 写入内存缓存 + DB 缓存(缓存 7 天)
|
||||||
|
expires_at = now + 86400 * 7
|
||||||
|
cache_payload = {"data": countries, "expires_at": expires_at}
|
||||||
|
_countries_cache.update(cache_payload)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with get_db() as db:
|
||||||
|
crud.set_setting(
|
||||||
|
db,
|
||||||
|
key=_DB_CACHE_KEY,
|
||||||
|
value=json.dumps(cache_payload, ensure_ascii=False),
|
||||||
|
description="checkout 国家/货币列表缓存",
|
||||||
|
category="cache",
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"写入 DB 缓存失败(不影响返回结果): {e}")
|
||||||
|
|
||||||
|
return {"success": True, "countries": countries}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user