feat(payment): 实现动态国家货币配置加载功能

- 移除硬编码国家货币映射,改为从API动态加载
- 添加 /api/payment/countries 接口获取支持的国家/货币列表
- 实现国家列表缓存机制(1小时有效期)
- 在支付页面添加国家选择下拉框动态渲染
- 添加网络请求失败时的备用数据方案
- 保留批量模式状态不被重置的注释说明
This commit is contained in:
cnlimiter
2026-03-27 17:00:55 +08:00
parent 2e47834152
commit f3268ec0a6
4 changed files with 91 additions and 20 deletions

View File

@@ -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)}

View File

@@ -1061,7 +1061,7 @@ function resetButtons() {
elements.cancelBtn.disabled = true;
currentTask = null;
currentBatch = null;
isBatchMode = false;
// 注意:不重置 isBatchMode因为用户可能想继续使用批量模式
// 重置完成标志
taskCompleted = false;
batchCompleted = false;

View File

@@ -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 =>
`<option value="${c.country_code}">${c.country_name} (${c.currency})</option>`
).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 = '<option value="SG">Singapore (SGD)</option>';
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;
}

View File

@@ -106,22 +106,12 @@
<div class="form-group">
<label for="country-select">计费国家</label>
<select id="country-select" onchange="onCountryChange()">
<option value="SG">新加坡 (SGD)</option>
<option value="US">美国 (USD)</option>
<option value="TR">土耳其 (TRY)</option>
<option value="JP">日本 (JPY)</option>
<option value="HK">香港 (HKD)</option>
<option value="GB">英国 (GBP)</option>
<option value="AU">澳大利亚 (AUD)</option>
<option value="CA">加拿大 (CAD)</option>
<option value="IN">印度 (INR)</option>
<option value="BR">巴西 (BRL)</option>
<option value="MX">墨西哥 (MXN)</option>
<option value="">-- 加载中... --</option>
</select>
</div>
<div class="form-group">
<label>对应货币</label>
<input type="text" id="currency-display" value="SGD" readonly style="background:var(--surface-hover);cursor:default">
<input type="text" id="currency-display" value="" readonly style="background:var(--surface-hover);cursor:default">
</div>
</div>