Skip to content

Commit 808d452

Browse files
committed
feat(payment): 优化国家货币列表获取逻辑
- 添加内置 fallback 国家/货币列表作为备用方案 - 实现多层缓存策略:内存缓存 -> DB 缓存 -> API 请求 - 改进 API 请求逻辑:先获取国家代码列表,再并发请求各国配置 - 使用线程池并发获取各国货币配置信息 - 增加详细的错误处理和日志记录 - 将缓存时间延长至 7 天并添加缓存回写功能 - 优化响应数据结构和字段提取逻辑
1 parent f458f21 commit 808d452

1 file changed

Lines changed: 95 additions & 22 deletions

File tree

src/web/routes/payment.py

Lines changed: 95 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -183,47 +183,120 @@ def mark_subscription(account_id: int, request: MarkSubscriptionRequest):
183183
_countries_cache: dict = {} # {"data": [...], "expires_at": float}
184184

185185

186+
def _get_fallback_countries():
187+
"""内置 fallback 国家/货币列表"""
188+
return [
189+
{"country_code": "AU", "currency": "AUD", "country_name": "AU"},
190+
{"country_code": "BR", "currency": "BRL", "country_name": "BR"},
191+
{"country_code": "CA", "currency": "CAD", "country_name": "CA"},
192+
{"country_code": "GB", "currency": "GBP", "country_name": "GB"},
193+
{"country_code": "HK", "currency": "HKD", "country_name": "HK"},
194+
{"country_code": "IN", "currency": "INR", "country_name": "IN"},
195+
{"country_code": "JP", "currency": "JPY", "country_name": "JP"},
196+
{"country_code": "MX", "currency": "MXN", "country_name": "MX"},
197+
{"country_code": "SG", "currency": "SGD", "country_name": "SG"},
198+
{"country_code": "TR", "currency": "TRY", "country_name": "TR"},
199+
{"country_code": "US", "currency": "USD", "country_name": "US"},
200+
]
201+
202+
186203
@router.get("/countries")
187204
def get_checkout_countries():
188-
"""从 ChatGPT checkout 接口获取支持的国家/货币列表(缓存 1 小时)"""
205+
"""从 ChatGPT checkout 接口获取支持的国家/货币列表(优先读 DB 缓存,成功后回写)"""
189206
import time
207+
import json
190208
import curl_cffi.requests as cffi_requests
209+
from concurrent.futures import ThreadPoolExecutor, as_completed
191210

211+
_DB_CACHE_KEY = "cache.checkout_countries"
192212
now = time.time()
213+
214+
# 1. 内存缓存命中
193215
if _countries_cache.get("expires_at", 0) > now:
194216
return {"success": True, "countries": _countries_cache["data"]}
195217

218+
# 2. 读取 DB 缓存
196219
with get_db() as db:
197220
proxy = get_settings().get_proxy_url(db=db)
221+
db_setting = crud.get_setting(db, _DB_CACHE_KEY)
222+
if db_setting and db_setting.value:
223+
try:
224+
cached = json.loads(db_setting.value)
225+
if cached.get("expires_at", 0) > now:
226+
_countries_cache.update(cached)
227+
return {"success": True, "countries": cached["data"]}
228+
except Exception:
229+
pass
230+
231+
proxies = {"http": proxy, "https": proxy} if proxy else None
198232

233+
# 3. 请求 ChatGPT API 获取国家代码列表
199234
try:
200235
resp = cffi_requests.get(
201236
"https://chatgpt.com/backend-api/checkout_pricing_config/countries",
202-
proxies={"http": proxy, "https": proxy} if proxy else None,
237+
proxies=proxies,
203238
timeout=15,
204239
impersonate="chrome110",
205240
)
206241
resp.raise_for_status()
207-
data = resp.json()
208-
countries = data if isinstance(data, list) else data.get("countries", [])
209-
_countries_cache["data"] = countries
210-
_countries_cache["expires_at"] = now + 3600
211-
return {"success": True, "countries": countries}
242+
raw = resp.json()
243+
country_codes = raw.get("countries", []) if isinstance(raw, dict) else raw
244+
if not isinstance(country_codes, list) or not country_codes:
245+
raise ValueError(f"国家列表为空或格式异常: {str(raw)[:200]}")
246+
except Exception as e:
247+
logger.warning(f"获取国家代码列表失败: {e}")
248+
return {"success": False, "countries": _get_fallback_countries(), "error": str(e)}
249+
250+
# 4. 并发请求各国 configs,提取 symbol_code
251+
def fetch_config(code: str):
252+
try:
253+
r = cffi_requests.get(
254+
f"https://chatgpt.com/backend-api/checkout_pricing_config/configs/{code}",
255+
proxies=proxies,
256+
timeout=10,
257+
impersonate="chrome110",
258+
)
259+
if r.status_code == 200:
260+
data = r.json()
261+
cfg = data.get("currency_config", {})
262+
currency = cfg.get("symbol_code") or cfg.get("symbol") or ""
263+
if currency:
264+
return {"country_code": code, "currency": currency, "country_name": code}
265+
except Exception:
266+
pass
267+
return None
268+
269+
countries = []
270+
with ThreadPoolExecutor(max_workers=10) as executor:
271+
futures = {executor.submit(fetch_config, code): code for code in country_codes}
272+
for future in as_completed(futures):
273+
result = future.result()
274+
if result:
275+
countries.append(result)
276+
277+
countries.sort(key=lambda c: c["country_code"])
278+
279+
if not countries:
280+
logger.warning("所有国家 configs 请求均失败,使用 fallback")
281+
return {"success": False, "countries": _get_fallback_countries(), "error": "所有 configs 请求失败"}
282+
283+
# 5. 写入内存缓存 + DB 缓存(缓存 7 天)
284+
expires_at = now + 86400 * 7
285+
cache_payload = {"data": countries, "expires_at": expires_at}
286+
_countries_cache.update(cache_payload)
287+
288+
try:
289+
with get_db() as db:
290+
crud.set_setting(
291+
db,
292+
key=_DB_CACHE_KEY,
293+
value=json.dumps(cache_payload, ensure_ascii=False),
294+
description="checkout 国家/货币列表缓存",
295+
category="cache",
296+
)
212297
except Exception as e:
213-
logger.warning(f"获取国家列表失败: {e}")
214-
fallback = [
215-
{"country_code": "SG", "currency": "SGD", "country_name": "Singapore"},
216-
{"country_code": "US", "currency": "USD", "country_name": "United States"},
217-
{"country_code": "TR", "currency": "TRY", "country_name": "Turkey"},
218-
{"country_code": "JP", "currency": "JPY", "country_name": "Japan"},
219-
{"country_code": "HK", "currency": "HKD", "country_name": "Hong Kong"},
220-
{"country_code": "GB", "currency": "GBP", "country_name": "United Kingdom"},
221-
{"country_code": "AU", "currency": "AUD", "country_name": "Australia"},
222-
{"country_code": "CA", "currency": "CAD", "country_name": "Canada"},
223-
{"country_code": "IN", "currency": "INR", "country_name": "India"},
224-
{"country_code": "BR", "currency": "BRL", "country_name": "Brazil"},
225-
{"country_code": "MX", "currency": "MXN", "country_name": "Mexico"},
226-
]
227-
return {"success": False, "countries": fallback, "error": str(e)}
298+
logger.warning(f"写入 DB 缓存失败(不影响返回结果): {e}")
299+
300+
return {"success": True, "countries": countries}
228301

229302

0 commit comments

Comments
 (0)