Skip to content

Commit f3268ec

Browse files
committed
feat(payment): 实现动态国家货币配置加载功能
- 移除硬编码国家货币映射,改为从API动态加载 - 添加 /api/payment/countries 接口获取支持的国家/货币列表 - 实现国家列表缓存机制(1小时有效期) - 在支付页面添加国家选择下拉框动态渲染 - 添加网络请求失败时的备用数据方案 - 保留批量模式状态不被重置的注释说明
1 parent 2e47834 commit f3268ec

4 files changed

Lines changed: 91 additions & 20 deletions

File tree

src/web/routes/payment.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,52 @@ def mark_subscription(account_id: int, request: MarkSubscriptionRequest):
178178

179179
return {"success": True, "subscription_type": request.subscription_type}
180180

181+
# ============== 国家/货币配置 ==============
182+
183+
_countries_cache: dict = {} # {"data": [...], "expires_at": float}
184+
185+
186+
@router.get("/countries")
187+
def get_checkout_countries():
188+
"""从 ChatGPT checkout 接口获取支持的国家/货币列表(缓存 1 小时)"""
189+
import time
190+
import curl_cffi.requests as cffi_requests
191+
192+
now = time.time()
193+
if _countries_cache.get("expires_at", 0) > now:
194+
return {"success": True, "countries": _countries_cache["data"]}
195+
196+
with get_db() as db:
197+
proxy = get_settings().get_proxy_url(db=db)
198+
199+
try:
200+
resp = cffi_requests.get(
201+
"https://chatgpt.com/backend-api/checkout_pricing_config/countries",
202+
proxies={"http": proxy, "https": proxy} if proxy else None,
203+
timeout=15,
204+
impersonate="chrome110",
205+
)
206+
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}
212+
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)}
228+
181229

static/js/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1061,7 +1061,7 @@ function resetButtons() {
10611061
elements.cancelBtn.disabled = true;
10621062
currentTask = null;
10631063
currentBatch = null;
1064-
isBatchMode = false;
1064+
// 注意:不重置 isBatchMode,因为用户可能想继续使用批量模式
10651065
// 重置完成标志
10661066
taskCompleted = false;
10671067
batchCompleted = false;

static/js/payment.js

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,53 @@
22
* 支付页面 JavaScript
33
*/
44

5-
const COUNTRY_CURRENCY_MAP = {
6-
SG: 'SGD', US: 'USD', TR: 'TRY', JP: 'JPY',
7-
HK: 'HKD', GB: 'GBP', EU: 'EUR', AU: 'AUD',
8-
CA: 'CAD', IN: 'INR', BR: 'BRL', MX: 'MXN',
9-
};
10-
115
let selectedPlan = 'plus';
126
let generatedLink = '';
7+
let countryCurrencyMap = {}; // 动态从接口加载
138

149
// 初始化
1510
document.addEventListener('DOMContentLoaded', () => {
1611
loadAccounts();
12+
loadCountries();
1713
});
1814

15+
// 加载国家/货币列表
16+
async function loadCountries() {
17+
const sel = document.getElementById('country-select');
18+
try {
19+
const resp = await fetch('/api/payment/countries');
20+
const data = await resp.json();
21+
const countries = data.countries || [];
22+
23+
// 重建映射表
24+
countryCurrencyMap = {};
25+
countries.forEach(c => {
26+
countryCurrencyMap[c.country_code] = c.currency;
27+
});
28+
29+
// 记住当前选中值
30+
const current = sel.value;
31+
32+
// 渲染选项
33+
sel.innerHTML = countries.map(c =>
34+
`<option value="${c.country_code}">${c.country_name} (${c.currency})</option>`
35+
).join('');
36+
37+
// 恢复选中或默认 SG
38+
sel.value = current && countryCurrencyMap[current] ? current : 'SG';
39+
onCountryChange();
40+
41+
if (!data.success) {
42+
console.warn('国家列表使用内置 fallback:', data.error);
43+
}
44+
} catch (e) {
45+
console.error('加载国家列表失败:', e);
46+
sel.innerHTML = '<option value="SG">Singapore (SGD)</option>';
47+
countryCurrencyMap = { SG: 'SGD' };
48+
onCountryChange();
49+
}
50+
}
51+
1952
// 加载账号列表
2053
async function loadAccounts() {
2154
try {
@@ -37,7 +70,7 @@ async function loadAccounts() {
3770
// 国家切换
3871
function onCountryChange() {
3972
const country = document.getElementById('country-select').value;
40-
const currency = COUNTRY_CURRENCY_MAP[country] || 'USD';
73+
const currency = countryCurrencyMap[country] || '';
4174
document.getElementById('currency-display').value = currency;
4275
}
4376

templates/payment.html

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -106,22 +106,12 @@ <h3>Team</h3>
106106
<div class="form-group">
107107
<label for="country-select">计费国家</label>
108108
<select id="country-select" onchange="onCountryChange()">
109-
<option value="SG">新加坡 (SGD)</option>
110-
<option value="US">美国 (USD)</option>
111-
<option value="TR">土耳其 (TRY)</option>
112-
<option value="JP">日本 (JPY)</option>
113-
<option value="HK">香港 (HKD)</option>
114-
<option value="GB">英国 (GBP)</option>
115-
<option value="AU">澳大利亚 (AUD)</option>
116-
<option value="CA">加拿大 (CAD)</option>
117-
<option value="IN">印度 (INR)</option>
118-
<option value="BR">巴西 (BRL)</option>
119-
<option value="MX">墨西哥 (MXN)</option>
109+
<option value="">-- 加载中... --</option>
120110
</select>
121111
</div>
122112
<div class="form-group">
123113
<label>对应货币</label>
124-
<input type="text" id="currency-display" value="SGD" readonly style="background:var(--surface-hover);cursor:default">
114+
<input type="text" id="currency-display" value="" readonly style="background:var(--surface-hover);cursor:default">
125115
</div>
126116
</div>
127117

0 commit comments

Comments
 (0)