fix(contribution): improve redeem feedback and backend fallback

This commit is contained in:
cong
2026-04-05 00:38:44 +08:00
parent 0c90b83c40
commit 3c2f5e11ea
2 changed files with 85 additions and 14 deletions

View File

@@ -24,6 +24,10 @@ KEY_INFO_CANDIDATES: list[tuple[str, str]] = [
("POST", "/public/key-info"),
("POST", "/public/key/info"),
]
REDEEM_CANDIDATES: list[tuple[str, str]] = [
("POST", "/public/redeem"),
("POST", "/api/contribution/redeem"),
]
class ContributionProxyRequest(BaseModel):
@@ -94,9 +98,13 @@ def _request_json(
data = {"raw": response.text}
if response.status_code >= 400:
detail = data
detail: Any = data
if isinstance(data, dict):
detail = data.get("error") or data.get("message") or data
detail = data.get("detail") or data.get("error") or data.get("message") or data
if isinstance(detail, dict):
detail = detail.get("message") or detail.get("error") or detail.get("code") or json_dumps_safe(detail)
if not isinstance(detail, str):
detail = json_dumps_safe(detail)
raise HTTPException(status_code=response.status_code, detail=detail)
if isinstance(data, dict):
@@ -104,6 +112,15 @@ def _request_json(
return {"data": data}
def json_dumps_safe(value: Any) -> str:
try:
import json
return json.dumps(value, ensure_ascii=False)
except Exception:
return str(value)
@router.post("/quota-stats")
def get_quota_stats(body: ContributionProxyRequest):
server_url = _resolve_server_url(body.server_url)
@@ -221,16 +238,48 @@ def get_key_info(body: ContributionProxyRequest):
def redeem(body: ContributionRedeemRequest):
server_url = _resolve_server_url(body.server_url)
key = _resolve_key(body.key)
data = _request_json(
"POST",
server_url,
"/public/redeem",
key,
payload={"amount_usd": body.amount_usd},
)
attempts: list[dict[str, Any]] = []
data: dict[str, Any] | None = None
endpoint_hit = ""
for method, endpoint in REDEEM_CANDIDATES:
try:
data = _request_json(
method,
server_url,
endpoint,
key,
payload={"amount_usd": body.amount_usd},
)
endpoint_hit = endpoint
break
except HTTPException as exc:
attempts.append(
{
"method": method,
"endpoint": endpoint,
"status_code": exc.status_code,
"detail": exc.detail,
}
)
if data is None:
raise HTTPException(
status_code=502,
detail={
"message": "调用提现接口失败,请确认 codex2api 是否已启用 /public/redeem",
"attempts": attempts,
},
)
redeemed_amount = data.get("redeemed_amount_usd")
redeem_code = data.get("code")
return {
"ok": True,
"endpoint": "/public/redeem",
"endpoint": endpoint_hit or "/public/redeem",
"redeemed_amount_usd": redeemed_amount,
"code": redeem_code,
"message": f"提现成功!额度:{redeemed_amount if redeemed_amount is not None else '-'} 兑换码:{redeem_code or '-'}",
"data": data,
}

View File

@@ -1106,6 +1106,13 @@ function ContributionPanel({
const serverQuotaUsedPercent = pickNumber(serverInfo, ['quota_used_percent'])
const serverQuotaRemainingPercent = pickNumber(serverInfo, ['quota_remaining_percent'])
const serverQuotaRemainingAccounts = pickNumber(serverInfo, ['quota_remaining_accounts'])
const redeemData = asRecord(redeemResponse?.['data']) || asRecord(redeemResponse)
const redeemCode = pickString(redeemData, ['code', 'redeem_code', 'voucher_code'])
const redeemedAmountUSD = pickNumber(redeemData, ['redeemed_amount_usd', 'redeemed_amount', 'amount_usd'])
const redeemSuccessText =
redeemResponse
? `提现成功!额度:${redeemedAmountUSD !== null ? formatDisplayNumber(redeemedAmountUSD, 2) : '-'} 兑换码:${redeemCode || '-'}`
: ''
const fetchStats = async (silent = false, keyOverride?: string) => {
if (!contributionEnabled) {
@@ -1172,11 +1179,21 @@ function ContributionPanel({
amount_usd: redeemAmount,
}),
})
setRedeemResponse(asRecord(data))
message.success('提现请求已提交')
const result = asRecord(data)
const payload = asRecord(result?.['data']) || result
const code = pickString(payload, ['code', 'redeem_code', 'voucher_code'])
const amount = pickNumber(payload, ['redeemed_amount_usd', 'redeemed_amount', 'amount_usd'])
setRedeemResponse(result)
if (amount !== null || code) {
message.success(`提现成功!额度:${amount !== null ? formatDisplayNumber(amount, 2) : '-'} 兑换码:${code || '-'}`)
} else {
message.success('提现成功')
}
await fetchStats(true)
} catch (e: any) {
message.error(String(e?.message || '提现失败'))
const detail = String(e?.message || '提现失败')
setRedeemResponse({ ok: false, error: detail })
message.error(detail)
} finally {
setRedeeming(false)
}
@@ -1323,7 +1340,12 @@ function ContributionPanel({
</Button>
{redeemResponse ? (
<pre style={{ marginTop: 8, whiteSpace: 'pre-wrap' }}>{formatResultText(redeemResponse)}</pre>
<Alert
type={redeemResponse.ok === false ? 'error' : 'success'}
showIcon
message={redeemResponse.ok === false ? `提现失败:${String(redeemResponse.error || '-')}` : redeemSuccessText}
description={<pre style={{ margin: 0, whiteSpace: 'pre-wrap' }}>{formatResultText(redeemResponse)}</pre>}
/>
) : null}
</Space>
</Card>