mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-07-12 16:01:22 +08:00
feat(payment): 实现动态国家货币配置加载功能
- 移除硬编码国家货币映射,改为从API动态加载 - 添加 /api/payment/countries 接口获取支持的国家/货币列表 - 实现国家列表缓存机制(1小时有效期) - 在支付页面添加国家选择下拉框动态渲染 - 添加网络请求失败时的备用数据方案 - 保留批量模式状态不被重置的注释说明
This commit is contained in:
@@ -178,4 +178,52 @@ def mark_subscription(account_id: int, request: MarkSubscriptionRequest):
|
|||||||
|
|
||||||
return {"success": True, "subscription_type": request.subscription_type}
|
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)}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1061,7 +1061,7 @@ function resetButtons() {
|
|||||||
elements.cancelBtn.disabled = true;
|
elements.cancelBtn.disabled = true;
|
||||||
currentTask = null;
|
currentTask = null;
|
||||||
currentBatch = null;
|
currentBatch = null;
|
||||||
isBatchMode = false;
|
// 注意:不重置 isBatchMode,因为用户可能想继续使用批量模式
|
||||||
// 重置完成标志
|
// 重置完成标志
|
||||||
taskCompleted = false;
|
taskCompleted = false;
|
||||||
batchCompleted = false;
|
batchCompleted = false;
|
||||||
|
|||||||
@@ -2,20 +2,53 @@
|
|||||||
* 支付页面 JavaScript
|
* 支付页面 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 selectedPlan = 'plus';
|
||||||
let generatedLink = '';
|
let generatedLink = '';
|
||||||
|
let countryCurrencyMap = {}; // 动态从接口加载
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
loadAccounts();
|
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() {
|
async function loadAccounts() {
|
||||||
try {
|
try {
|
||||||
@@ -37,7 +70,7 @@ async function loadAccounts() {
|
|||||||
// 国家切换
|
// 国家切换
|
||||||
function onCountryChange() {
|
function onCountryChange() {
|
||||||
const country = document.getElementById('country-select').value;
|
const country = document.getElementById('country-select').value;
|
||||||
const currency = COUNTRY_CURRENCY_MAP[country] || 'USD';
|
const currency = countryCurrencyMap[country] || '';
|
||||||
document.getElementById('currency-display').value = currency;
|
document.getElementById('currency-display').value = currency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,22 +106,12 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="country-select">计费国家</label>
|
<label for="country-select">计费国家</label>
|
||||||
<select id="country-select" onchange="onCountryChange()">
|
<select id="country-select" onchange="onCountryChange()">
|
||||||
<option value="SG">新加坡 (SGD)</option>
|
<option value="">-- 加载中... --</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>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>对应货币</label>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user