fix: 修复 OpenRouter 令牌余额查询 403 错误#4950
Conversation
WalkthroughThis PR enhances OpenRouter balance fetching by introducing a fallback mechanism. A new ChangesOpenRouter Balance Fallback
Sequence DiagramsequenceDiagram
participant Caller
participant updateChannelOpenRouterBalance
participant CreditsEndpoint as /api/v1/credits
participant KeyEndpoint as /api/v1/key
participant ChannelStorage as Channel Balance Storage
Caller->>updateChannelOpenRouterBalance: fetch OpenRouter balance
updateChannelOpenRouterBalance->>CreditsEndpoint: GET with auth header
alt credits request succeeds
CreditsEndpoint-->>updateChannelOpenRouterBalance: credits JSON (TotalCredits, TotalUsage)
updateChannelOpenRouterBalance->>updateChannelOpenRouterBalance: balance = TotalCredits - TotalUsage
updateChannelOpenRouterBalance->>ChannelStorage: update channel balance
ChannelStorage-->>updateChannelOpenRouterBalance: success
updateChannelOpenRouterBalance-->>Caller: return balance, nil
else credits request fails
CreditsEndpoint-->>updateChannelOpenRouterBalance: error
updateChannelOpenRouterBalance->>KeyEndpoint: GET /api/v1/key with auth header
KeyEndpoint-->>updateChannelOpenRouterBalance: key JSON (LimitRemaining)
updateChannelOpenRouterBalance->>updateChannelOpenRouterBalance: balance = LimitRemaining
updateChannelOpenRouterBalance->>ChannelStorage: update channel balance
ChannelStorage-->>updateChannelOpenRouterBalance: success
updateChannelOpenRouterBalance-->>Caller: return balance, nil
else both requests fail
updateChannelOpenRouterBalance-->>Caller: return 0, error
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@controller/channel-billing.go`:
- Around line 323-336: The new OpenRouter code is calling json.Unmarshal
directly; replace those calls with the repo wrapper common.Unmarshal to comply
with the JSON-wrapper rule. In channel-billing.go, locate the json.Unmarshal
usages around the OpenRouterKeyResponse and other OpenRouter response parsing
(the block that follows GetResponseBody and the earlier response parsing that
sets balance and calls channel.UpdateBalance) and change them to
common.Unmarshal(body, &response); ensure the common package is imported if
missing and remove any direct encoding/json usage in these business paths.
- Around line 320-345: The current code falls back to GET /api/v1/key for any
error from GetResponseBody("GET", url, channel, authHeader) for /credits; change
the logic so the fallback to the /key path only happens when the /credits
request failed with HTTP 403 (forbidden). To do this, modify GetResponseBody to
return the HTTP status (or an error type that exposes status) alongside body and
err (e.g., GetResponseBody -> (body []byte, status int, err error)) and update
this function call in this routine to inspect the returned status; only when
status == 403 call the /api/v1/key flow (unmarshal into OpenRouterKeyResponse
and call channel.UpdateBalance), otherwise return the original error
immediately. Ensure references: GetResponseBody, OpenRouterCreditResponse,
OpenRouterKeyResponse, and channel.UpdateBalance are updated accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 69521a98-b625-47ce-9832-ca2b95f3180e
📒 Files selected for processing (1)
controller/channel-billing.go
| body, err := GetResponseBody("GET", url, channel, authHeader) | ||
| if err == nil { | ||
| response := OpenRouterCreditResponse{} | ||
| err = json.Unmarshal(body, &response) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
| balance := response.Data.TotalCredits - response.Data.TotalUsage | ||
| channel.UpdateBalance(balance) | ||
| return balance, nil | ||
| } | ||
| response := OpenRouterCreditResponse{} | ||
| err = json.Unmarshal(body, &response) | ||
| if err != nil { | ||
| return 0, err | ||
|
|
||
| url = "https://openrouter.ai/api/v1/key" | ||
| body, err = GetResponseBody("GET", url, channel, authHeader) | ||
| if err == nil { | ||
| response := OpenRouterKeyResponse{} | ||
| err = json.Unmarshal(body, &response) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
| balance := response.Data.LimitRemaining | ||
| channel.UpdateBalance(balance) | ||
| return balance, nil | ||
| } | ||
| balance := response.Data.TotalCredits - response.Data.TotalUsage | ||
| channel.UpdateBalance(balance) | ||
| return balance, nil | ||
| return 0, err | ||
| } |
There was a problem hiding this comment.
Restrict fallback to 403 instead of all /credits errors.
Current logic falls back to /api/v1/key for any /credits failure. That can hide non-auth failures (network/5xx/contract), making balance updates silently succeed under the wrong path. Gate fallback to 403 and return other errors directly.
OpenRouter API docs: for GET /api/v1/credits and GET /api/v1/key, what status codes are returned for normal API keys vs admin keys, and is 403 the expected code for non-admin access to /credits?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@controller/channel-billing.go` around lines 320 - 345, The current code falls
back to GET /api/v1/key for any error from GetResponseBody("GET", url, channel,
authHeader) for /credits; change the logic so the fallback to the /key path only
happens when the /credits request failed with HTTP 403 (forbidden). To do this,
modify GetResponseBody to return the HTTP status (or an error type that exposes
status) alongside body and err (e.g., GetResponseBody -> (body []byte, status
int, err error)) and update this function call in this routine to inspect the
returned status; only when status == 403 call the /api/v1/key flow (unmarshal
into OpenRouterKeyResponse and call channel.UpdateBalance), otherwise return the
original error immediately. Ensure references: GetResponseBody,
OpenRouterCreditResponse, OpenRouterKeyResponse, and channel.UpdateBalance are
updated accordingly.
| err = json.Unmarshal(body, &response) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
| balance := response.Data.TotalCredits - response.Data.TotalUsage | ||
| channel.UpdateBalance(balance) | ||
| return balance, nil | ||
| } | ||
| response := OpenRouterCreditResponse{} | ||
| err = json.Unmarshal(body, &response) | ||
| if err != nil { | ||
| return 0, err | ||
|
|
||
| url = "https://openrouter.ai/api/v1/key" | ||
| body, err = GetResponseBody("GET", url, channel, authHeader) | ||
| if err == nil { | ||
| response := OpenRouterKeyResponse{} | ||
| err = json.Unmarshal(body, &response) |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Use common.Unmarshal instead of json.Unmarshal in the new OpenRouter paths.
The new changed lines directly call json.Unmarshal, which violates the repo JSON-wrapper rule for Go business code.
Suggested patch
- err = json.Unmarshal(body, &response)
+ err = common.Unmarshal(body, &response)
if err != nil {
return 0, err
}
@@
- err = json.Unmarshal(body, &response)
+ err = common.Unmarshal(body, &response)
if err != nil {
return 0, err
}As per coding guidelines: "**/*.go: All JSON marshal/unmarshal operations MUST use wrapper functions from common/json.go... Do NOT directly import or call encoding/json in business code."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@controller/channel-billing.go` around lines 323 - 336, The new OpenRouter
code is calling json.Unmarshal directly; replace those calls with the repo
wrapper common.Unmarshal to comply with the JSON-wrapper rule. In
channel-billing.go, locate the json.Unmarshal usages around the
OpenRouterKeyResponse and other OpenRouter response parsing (the block that
follows GetResponseBody and the earlier response parsing that sets balance and
calls channel.UpdateBalance) and change them to common.Unmarshal(body,
&response); ensure the common package is imported if missing and remove any
direct encoding/json usage in these business paths.
Important
📝 变更描述 / Description
默认
/api/v1/credits接口需要管理秘钥才能获取余额,403 错误时降级使用/api/v1/key让普通秘钥也能正确获取余额。🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
失败截图:

成功截图:

Summary by CodeRabbit