From f3268ec0a63082619ea3e968d6d9e2518a975eca Mon Sep 17 00:00:00 2001 From: cnlimiter Date: Fri, 27 Mar 2026 17:00:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(payment):=20=E5=AE=9E=E7=8E=B0=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E5=9B=BD=E5=AE=B6=E8=B4=A7=E5=B8=81=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除硬编码国家货币映射,改为从API动态加载 - 添加 /api/payment/countries 接口获取支持的国家/货币列表 - 实现国家列表缓存机制(1小时有效期) - 在支付页面添加国家选择下拉框动态渲染 - 添加网络请求失败时的备用数据方案 - 保留批量模式状态不被重置的注释说明 --- src/web/routes/payment.py | 48 +++++++++++++++++++++++++++++++++++++++ static/js/app.js | 2 +- static/js/payment.js | 47 ++++++++++++++++++++++++++++++++------ templates/payment.html | 14 ++---------- 4 files changed, 91 insertions(+), 20 deletions(-) diff --git a/src/web/routes/payment.py b/src/web/routes/payment.py index 4a9014e..b80c8c7 100644 --- a/src/web/routes/payment.py +++ b/src/web/routes/payment.py @@ -178,4 +178,52 @@ def mark_subscription(account_id: int, request: MarkSubscriptionRequest): return {"success": True, "subscription_type": request.subscription_type} +# ============== 国家/货币配置 ============== + +_countries_cache: dict = {} # {"data": [...], "expires_at": float} + + +@router.get("/countries") +def get_checkout_countries(): + """从 ChatGPT checkout 接口获取支持的国家/货币列表(缓存 1 小时)""" + import time + import curl_cffi.requests as cffi_requests + + now = time.time() + if _countries_cache.get("expires_at", 0) > now: + return {"success": True, "countries": _countries_cache["data"]} + + with get_db() as db: + proxy = get_settings().get_proxy_url(db=db) + + try: + resp = cffi_requests.get( + "https://chatgpt.com/backend-api/checkout_pricing_config/countries", + proxies={"http": proxy, "https": proxy} if proxy else None, + timeout=15, + impersonate="chrome110", + ) + resp.raise_for_status() + data = resp.json() + countries = data if isinstance(data, list) else data.get("countries", []) + _countries_cache["data"] = countries + _countries_cache["expires_at"] = now + 3600 + return {"success": True, "countries": countries} + except Exception as e: + logger.warning(f"获取国家列表失败: {e}") + fallback = [ + {"country_code": "SG", "currency": "SGD", "country_name": "Singapore"}, + {"country_code": "US", "currency": "USD", "country_name": "United States"}, + {"country_code": "TR", "currency": "TRY", "country_name": "Turkey"}, + {"country_code": "JP", "currency": "JPY", "country_name": "Japan"}, + {"country_code": "HK", "currency": "HKD", "country_name": "Hong Kong"}, + {"country_code": "GB", "currency": "GBP", "country_name": "United Kingdom"}, + {"country_code": "AU", "currency": "AUD", "country_name": "Australia"}, + {"country_code": "CA", "currency": "CAD", "country_name": "Canada"}, + {"country_code": "IN", "currency": "INR", "country_name": "India"}, + {"country_code": "BR", "currency": "BRL", "country_name": "Brazil"}, + {"country_code": "MX", "currency": "MXN", "country_name": "Mexico"}, + ] + return {"success": False, "countries": fallback, "error": str(e)} + diff --git a/static/js/app.js b/static/js/app.js index 1f87d5b..a554f5e 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -1061,7 +1061,7 @@ function resetButtons() { elements.cancelBtn.disabled = true; currentTask = null; currentBatch = null; - isBatchMode = false; + // 注意:不重置 isBatchMode,因为用户可能想继续使用批量模式 // 重置完成标志 taskCompleted = false; batchCompleted = false; diff --git a/static/js/payment.js b/static/js/payment.js index c170387..ab0c621 100644 --- a/static/js/payment.js +++ b/static/js/payment.js @@ -2,20 +2,53 @@ * 支付页面 JavaScript */ -const COUNTRY_CURRENCY_MAP = { - SG: 'SGD', US: 'USD', TR: 'TRY', JP: 'JPY', - HK: 'HKD', GB: 'GBP', EU: 'EUR', AU: 'AUD', - CA: 'CAD', IN: 'INR', BR: 'BRL', MX: 'MXN', -}; - let selectedPlan = 'plus'; let generatedLink = ''; +let countryCurrencyMap = {}; // 动态从接口加载 // 初始化 document.addEventListener('DOMContentLoaded', () => { loadAccounts(); + loadCountries(); }); +// 加载国家/货币列表 +async function loadCountries() { + const sel = document.getElementById('country-select'); + try { + const resp = await fetch('/api/payment/countries'); + const data = await resp.json(); + const countries = data.countries || []; + + // 重建映射表 + countryCurrencyMap = {}; + countries.forEach(c => { + countryCurrencyMap[c.country_code] = c.currency; + }); + + // 记住当前选中值 + const current = sel.value; + + // 渲染选项 + sel.innerHTML = countries.map(c => + `` + ).join(''); + + // 恢复选中或默认 SG + sel.value = current && countryCurrencyMap[current] ? current : 'SG'; + onCountryChange(); + + if (!data.success) { + console.warn('国家列表使用内置 fallback:', data.error); + } + } catch (e) { + console.error('加载国家列表失败:', e); + sel.innerHTML = ''; + countryCurrencyMap = { SG: 'SGD' }; + onCountryChange(); + } +} + // 加载账号列表 async function loadAccounts() { try { @@ -37,7 +70,7 @@ async function loadAccounts() { // 国家切换 function onCountryChange() { const country = document.getElementById('country-select').value; - const currency = COUNTRY_CURRENCY_MAP[country] || 'USD'; + const currency = countryCurrencyMap[country] || ''; document.getElementById('currency-display').value = currency; } diff --git a/templates/payment.html b/templates/payment.html index e2941f1..4aab9c3 100644 --- a/templates/payment.html +++ b/templates/payment.html @@ -106,22 +106,12 @@
- +