Skip to content

fix(billing): harden tiered retry group-switch billing (follow-up to #6518) - #6570

Merged
Calcium-Ion merged 1 commit into
mainfrom
fix/tiered-retry-billing-followups
Aug 1, 2026
Merged

fix(billing): harden tiered retry group-switch billing (follow-up to #6518)#6570
Calcium-Ion merged 1 commit into
mainfrom
fix/tiered-retry-billing-followups

Conversation

@Calcium-Ion

@Calcium-Ion Calcium-Ion commented Jul 31, 2026

Copy link
Copy Markdown
Member

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

Follow-up to #6518 (fix for #6480), addressing three non-blocking review findings on the tiered-billing retry path. This PR was developed with AI assistance (reviewed before submission).

  1. 钱包 Reserve 补扣的欠费(arrears)语义 — 重试落到更贵的分组时,预扣目标的补扣差额必须全额无条件扣减model.DecreaseUserQuota 是无条件 quota - ? 递减,余额不足的部分记为用户欠费(余额可为负),与结算路径(SettleBilling 正差额 → WalletFunding.Settle)语义完全一致,请求不因补扣不足而中断。这是对账要求:日志记录的预扣/结算额度必须与用户余额的实际变动一致。本 PR 为该分支补充了明确的语义注释,并新增回归测试锁定该行为(此前无任何测试覆盖,未来改动若引入余额检查会立即破坏对账一致性)。真正的数据库错误仍返回 update_data_error 并使本次尝试失败,绝不静默少预扣。订阅资金来源不变PostConsumeUserSubscriptionDelta 强制 used <= total 硬上限(model/subscription.go),订阅不支持欠费,因此保留其 insufficient_user_quota(403)行为。
  2. 免费转付费分组后 PriceData.FreeModel 未同步 — 初始分组 ratio 为 0(跳过预扣费)而重试落到付费分组时,PrepareTieredBillingForSelectedGroup 会创建计费会话,但 FreeModel 仍为 true。现在在付费分组分支将其清为 false。付费转免费方向保持不变:会话已存在时 "跳过预扣费" 不再成立,且结算对 ratio 0 本就产出 0。
  3. controller/relay.go 顺序问题 — (a) getChannelHandleGroupRatio 原先在 CacheGetRandomSatisfiedChannel 的错误检查之前执行,选路失败也会改写定价状态,现移到错误处理之后;(b) 重试循环中 PrepareTieredBillingForSelectedGroup 原先在 addUsedChannel 之前,Prepare 失败时选中的渠道不会出现在 use_channel 重试日志里,现先记录渠道再执行 Prepare。

计费不变量保持:扣费金额永不为负(欠费指余额可为负,而非扣费额为负);FinalPreConsumedQuota 与会话预扣量始终反映完整的补扣后预扣目标,保证预扣与结算差额正确对账;补扣遇数据库错误即失败整个尝试。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 描述基于对代码逻辑的实际理解撰写并经过整理;本 PR 为 AI 辅助开发(AI-assisted),特此说明。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

$ go build ./...   # ok

$ go test ./service/... ./pkg/billingexpr/... ./controller/...
ok  	github.com/QuantumNous/new-api/service	0.419s
ok  	github.com/QuantumNous/new-api/service/authz	0.021s
ok  	github.com/QuantumNous/new-api/pkg/billingexpr	0.012s
ok  	github.com/QuantumNous/new-api/controller	0.311s

$ go test ./service/ -run 'TestPrepareTieredBilling|TestBillingSessionReserve' -v
--- PASS: TestPrepareTieredBillingForSelectedGroupUpdatesReservation (0.00s)
--- PASS: TestPrepareTieredBillingForSelectedGroupStartsBillingAfterFreeGroup (0.00s)
--- PASS: TestPrepareTieredBillingForSelectedGroupPaidToFreeKeepsFreeModelFalse (0.00s)
--- PASS: TestPrepareTieredBillingForSelectedGroupTopUpArrearsAllowsNegativeBalance (0.00s)
--- PASS: TestBillingSessionReserveWalletTopUpDecrementsBalance (0.00s)
PASS

新增测试覆盖:余额不足时补扣不中断请求(Prepare 返回 nil)、全额扣减使余额进入欠费(-30k)、FinalPreConsumedQuota 等于完整新预扣目标(100k)、后续结算按完整预扣正确对账(80k 实耗 → 退 20k → 余额 -10k);余额充足的补扣正确扣减并同步 FinalPreConsumedQuota;免费转付费清除 FreeModel;付费转免费保持 FreeModel=false 且不触发 Reserve。

Summary by CodeRabbit

  • Bug Fixes
    • Improved relay channel selection and pricing preparation when selection fails or no channel is available.
    • Updated wallet funding to allow temporary negative balances when usage exceeds available funds, with subsequent settlement adjustments.
    • Corrected free and paid model status transitions when switching billing groups.
    • Improved consistency for paid-group billing reservations and pre-consumption.

@cursor

cursor Bot commented Jul 31, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The relay flow records and validates channels before pricing. Tiered billing updates FreeModel across group transitions. Wallet reservations can create arrears and are covered by settlement and deduction tests.

Changes

Relay billing flow

Layer / File(s) Summary
Channel selection and billing order
controller/relay.go
The relay records the selected channel before billing preparation. getChannel returns channel errors before applying group-ratio pricing.
Tiered billing state transitions
service/tiered_settle.go, service/tiered_settle_test.go
Tiered billing handles missing snapshots and zero-ratio groups separately. Paid-group selection clears PriceData.FreeModel. Tests cover paid-to-free and free-to-paid transitions.
Wallet quota reservation
service/billing_session.go, service/tiered_settle_test.go
Wallet reservations decrement the full requested quota, even when the balance becomes negative. Tests cover arrears, settlement refunds, and incremental deductions.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Poem

A rabbit records the chosen route,
Paid groups clear the free-state suit.
Wallets may dip below the line,
Settlement makes the balance fine.
Tests hop through each billing byte.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main billing changes for tiered retry group switching.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tiered-retry-billing-followups

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
service/billing_session.go (1)

232-256: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Do not use a separate read-then-unconditional-write path for wallet balance.

reserveFunding reads model.GetUserQuota(funding.userId, false), checks userQuota-delta < 0, and then calls model.DecreaseUserQuota(funding.userId, delta, false). DecreaseUserQuota updates quota = quota - ? without a quota >= ? guard, and Reserve only serializes calls on the same BillingSession. Two concurrent wallet requests can both pass the read check and both decrement, leaving the wallet balance negative. Use an atomic conditional update that rejects quota - ? < 0 (including handling rows-affected), or a per-user lock around the read/decrement sequence, including the initial pre-consume wallet path.

🤖 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 `@service/billing_session.go` around lines 232 - 256, Replace the separate
GetUserQuota check and unconditional DecreaseUserQuota call in
BillingSession.reserveFunding with an atomic quota decrement guarded by
sufficient balance, and handle zero rows affected as insufficient_user_quota
while preserving database errors as update_data_error. Apply the same
synchronization or conditional-update protection to the initial wallet
pre-consume path used by tryWallet so concurrent requests cannot overdraw a user
wallet.
🤖 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/relay.go`:
- Around line 314-324: The retry path computes the group ratio before the
selected retry group is available in the context, so `HandleGroupRatio` may use
the wrong group. Update the call to `helper.HandleGroupRatio` in the retry flow
to receive or otherwise use `selectGroup`, ensuring `info.UsingGroup` reflects
the retry-selected group before `PriceData.GroupRatioInfo` is assigned.

---

Outside diff comments:
In `@service/billing_session.go`:
- Around line 232-256: Replace the separate GetUserQuota check and unconditional
DecreaseUserQuota call in BillingSession.reserveFunding with an atomic quota
decrement guarded by sufficient balance, and handle zero rows affected as
insufficient_user_quota while preserving database errors as update_data_error.
Apply the same synchronization or conditional-update protection to the initial
wallet pre-consume path used by tryWallet so concurrent requests cannot overdraw
a user wallet.
🪄 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 Plus

Run ID: 926a473f-4135-4d79-86e3-a8333c0d1610

📥 Commits

Reviewing files that changed from the base of the PR and between df43f80 and dd924e5.

📒 Files selected for processing (4)
  • controller/relay.go
  • service/billing_session.go
  • service/tiered_settle.go
  • service/tiered_settle_test.go

Comment thread controller/relay.go
Comment on lines 314 to 324
channel, selectGroup, err := service.CacheGetRandomSatisfiedChannel(retryParam)

info.PriceData.GroupRatioInfo = helper.HandleGroupRatio(c, info)

if err != nil {
return nil, types.NewError(fmt.Errorf("获取分组 %s 下模型 %s 的可用渠道失败(retry): %s", selectGroup, info.OriginModelName, err.Error()), types.ErrorCodeGetChannelFailed, types.ErrOptionWithSkipRetry())
}
if channel == nil {
return nil, types.NewError(fmt.Errorf("分组 %s 下模型 %s 的可用渠道不存在(retry)", selectGroup, info.OriginModelName), types.ErrorCodeGetChannelFailed, types.ErrOptionWithSkipRetry())
}

info.PriceData.GroupRatioInfo = helper.HandleGroupRatio(c, info)

newAPIError := middleware.SetupContextForSelectedChannel(c, channel, info.OriginModelName)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "func HandleGroupRatio" --type go -A 40
echo "---"
rg -n "func SetupContextForSelectedChannel" --type go -A 40
echo "---"
rg -n "UsingGroup\s*=" --type go

Repository: QuantumNous/new-api

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -u

echo "Go files:"
git ls-files '*.go' | wc -l

echo "--- locate candidate files by basename ---"
fd -a 'relay.go|tiered_settle.go|group|group_ratio|json.go' . | sed 's#^\./##' | head -200

echo "--- search symbols with case-insensitive text search ---"
rg -n -i 'HandleGroupRatio|SetupContextForSelectedChannel|UsingGroup|SelectGroup|selectGroup|refreshTieredBillingGroup' . | head -300

echo "--- git diff stat/summary if available ---"
git diff --stat || true

Repository: QuantumNous/new-api

Length of output: 32906


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- relay helper price.go ---"
cat -n relay/helper/price.go | sed -n '1,110p'

echo "--- controller/relay.go relevant section ---"
cat -n controller/relay.go | sed -n '260,350p'

echo "--- middleware auth context key setter ---"
cat -n middleware/auth.go | sed -n '455,480p'

echo "--- relay common relay_info.go BuildFromContext section ---"
cat -n relay/common/relay_info.go | sed -n '430,495p'

echo "--- middleware distributor SetupContextForSelectedChannel ---"
cat -n middleware/distributor.go | sed -n '440,490p'

echo "--- tiered settle refreshTieredBillingGroup ---"
cat -n service/tiered_settle.go | sed -n '85,105p'

Repository: QuantumNous/new-api

Length of output: 15711


Thread the selected retry group into HandleGroupRatio.

HandleGroupRatio(c, info) sets relayInfo.UsingGroup from the auto_group context value, but controller/relay.go:322 calls it before middleware.SetupContextForSelectedChannel sets that value. In the retry path, pass selectGroup to the helper or set auto_group/info.UsingGroup with the retry-selected group before computing the group ratio.

🤖 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/relay.go` around lines 314 - 324, The retry path computes the
group ratio before the selected retry group is available in the context, so
`HandleGroupRatio` may use the wrong group. Update the call to
`helper.HandleGroupRatio` in the retry flow to receive or otherwise use
`selectGroup`, ensuring `info.UsingGroup` reflects the retry-selected group
before `PriceData.GroupRatioInfo` is assigned.

Follow-up to #6518 (issue #6480) addressing three review findings:

- Document and lock in arrears semantics for the wallet Reserve top-up:
  when an auto-group retry lands on a more expensive group, the full
  reservation delta is deducted unconditionally (balance may go
  negative), mirroring settlement, so the logged pre-consumed quota
  always reconciles with the actual balance movement. Genuine DB
  errors still fail the attempt with update_data_error. Subscription
  funding keeps its insufficient-quota behavior: subscriptions enforce
  a hard used<=total cap and do not support arrears.
- PriceData.FreeModel is cleared when a retry switches from a free
  group to a paid one, keeping it consistent with the billing session
  created at that point.
- getChannel refreshes GroupRatioInfo only after channel selection
  succeeds, and the retry loop records the channel in use_channel
  before PrepareTieredBillingForSelectedGroup can fail.
@Calcium-Ion
Calcium-Ion force-pushed the fix/tiered-retry-billing-followups branch from dd924e5 to e5aa3f5 Compare August 1, 2026 01:32
@Calcium-Ion
Calcium-Ion merged commit cfaba1d into main Aug 1, 2026
2 of 3 checks passed
kimberxu pushed a commit to kimberxu/new-api that referenced this pull request Aug 1, 2026
Upstream 2026-07-31..2026-08-01: deepseek responses api (QuantumNous#6562),
zstd request decompression (QuantumNous#6545), log stream status (QuantumNous#6558),
tiered retry billing settlement (QuantumNous#6518, QuantumNous#6570), OAuth opener fix
(QuantumNous#6425), multipart image edit fix (QuantumNous#6559), public header nav style
(QuantumNous#6557).

Conflict resolution:
- controller/relay.go: keep per-channel rate limit check (custom) and
  upstream PrepareTieredBillingForSelectedGroup call
- relay/common/relay_info.go: keep RequestDebugSnapshot (custom) and
  upstream TieredBillingSnapshot comment
0401lucky pushed a commit to 0401lucky/new-api that referenced this pull request Aug 2, 2026
)

Follow-up to QuantumNous#6518 (issue QuantumNous#6480) addressing three review findings:

- Document and lock in arrears semantics for the wallet Reserve top-up:
  when an auto-group retry lands on a more expensive group, the full
  reservation delta is deducted unconditionally (balance may go
  negative), mirroring settlement, so the logged pre-consumed quota
  always reconciles with the actual balance movement. Genuine DB
  errors still fail the attempt with update_data_error. Subscription
  funding keeps its insufficient-quota behavior: subscriptions enforce
  a hard used<=total cap and do not support arrears.
- PriceData.FreeModel is cleared when a retry switches from a free
  group to a paid one, keeping it consistent with the billing session
  created at that point.
- getChannel refreshes GroupRatioInfo only after channel selection
  succeeds, and the retry loop records the channel in use_channel
  before PrepareTieredBillingForSelectedGroup can fail.
bigfish9 added a commit to lanlingxiawu/new-api-er that referenced this pull request Aug 3, 2026
合并上游 16 个提交,主要是 token Auto 分组 (QuantumNous#6590)、deepseek responses API
(QuantumNous#6562)、Bedrock 客户端断开取消 (QuantumNous#6589)、分层重试计费加固 (QuantumNous#6518/QuantumNous#6570)、
zstd 请求解压 (QuantumNous#6545)、OIDC 自定义显示名 (QuantumNous#6012)、日志暴露 stream_status
(QuantumNous#6558)。

33 处冲突的处理:

- 11 个 legacy channel adaptor:上游删除 panic 之后的死代码以配合新增的
  go vet CI,本仓早已把整段 panic 换成返回 ErrLegacyAdaptorNotImplemented,
  已达成同一目的且不会让中继链路 panic,保留本仓实现。
- relay-aws.go 流式循环:两侧改动正交,合并保留 —— 上游的 ctx.Done() 取消
  分支(客户端断开时不再空转上游),加本仓的 finalizeClaudeOnError(提前
  返回时补发流终止符,否则 Claude 格式调用方会一直挂着)。
- 7 个 locale:两侧各自插入相邻 key,按字母序归并;上游 24 个新 key 与本仓
  1278 个 fork key 全部保留,逐一核对无丢失。
- keys / oauth / profile 前端:上游 Auto 分组为主体,叠加本仓改动;
  account-bindings-tab 与 oauth/$provider 保留本仓实现,理由见下。

测试取舍:

- 不引入 model/token_auto_groups_cache_test.go:它依赖上游的 truncateTables,
  该助手全局清表,与本仓行级清理原则冲突(共享库里有开发数据)。
- 不携带上游 controller/token_test.go:其迁移兼容测试会替换 model.DB 并在
  cleanup 里关闭,后续用 harness 连接的测试会拿到已关闭的句柄。按既有约定,
  token_auto_groups_test.go 需要的四个助手放进 zz_upstream_test_shims_test.go,
  其中 openTokenControllerTestDB 增加了 model.DB/LOG_DB 的成对保存还原。
  本仓原 token_test.go 的 controller 行为测试迁到
  gen_ctrl_token_handlers_test.go,与上游文件名脱钩以免再冲突。
- 三处断言随上游行为更新:stream_status 现对日志所有者可见;deepseek 的
  ConvertOpenAIResponsesRequest 已实现不再返回错误;计费路径判定收紧为
  「标记之外还需带对应 usage payload」,并补了三个反向用例锁住该语义。

遗留:oauth 绑定回调仍用 window.opener 判定 bind/login,上游 QuantumNous#6425 已改为
sessionStorage 标记 + state 比对。该修复要求 popup 先以 about:blank 打开再打
标记,本仓四个内置 provider 走 window.open(url) 直开,直接套用会让绑定永远
判成 login,故本次未采纳,需单独改造。
dreamlx added a commit to dreamlx/new-api that referenced this pull request Aug 3, 2026
Sync upstream/main (rc.11 → rc.23, 46 commits) into main. Key upstream
changes: QuantumNous#6369 relaykit module extraction (dto/types/service/relayconvert
moved to relaykit/ submodule with require+replace), QuantumNous#6518/QuantumNous#6570 tiered
retry billing, QuantumNous#6558 stream status exposed to log owners, QuantumNous#6562 DeepSeek
responses API, QuantumNous#6590 auto group, QuantumNous#6545 zstd decompression, per-channel
HTTP transport controls.

Conflicts resolved:
- constant/api_type.go, constant/channel.go: keep LH channel numbering
  (OspreyAI=58/HappyHorse=59/Seedance=60/AdvancedCustom=61 via reserved
  placeholders), append upstream Sub2API=62/NewAPI=63 before Dummy.
- relay/common/relay_info_test.go: union imports (LH common2/constant/gin
  + upstream convmeta/relaykit-types/assert), keep both test sets.

LH-only packages (ospreyai/happyhorse/seedance/task variants) had stale
host dto/types imports after relaykit extraction — migrated to
relaykit/dto and relaykit/types (task-family TaskError kept on host dto
via taskdto alias, matching upstream doubao pattern).

Co-Authored-By: Claude <noreply@anthropic.com>
speedxcc pushed a commit to speedxcc/new-api-speed that referenced this pull request Aug 4, 2026
合并官方上游 Calcium-Ion/new-api main 分支的 10 个新 commit:
- Feat/auto group (QuantumNous#6590)
- fix(aws): cancel Bedrock requests on client disconnect (QuantumNous#6589)
- fix(billing): harden tiered retry group-switch billing (QuantumNous#6570)
- fix(billing): settle tiered retries with final group (QuantumNous#6518)
- feat: deepseek responses api (QuantumNous#6562)
- fix(oauth): stop treating a foreign window.opener as a bind flow (QuantumNous#6425)
- fix(relay): preserve multipart image edits for New API channels (QuantumNous#6559)
- feat(logs): expose stream status to log owners (QuantumNous#6558)
- feat: support zstd request decompression (QuantumNous#6545)
- style: use text-sm for public header nav links (QuantumNous#6557)

冲突解决: model/option.go 的 switch case 区域,双方各自新增了选项注册
(我方 GroupPassThrough + 官方 MaxTokenAutoGroups),取并集保留两者。
其余文件均自动合并成功。

验证: go build + go test 全过(relay/helper、middleware、controller)
nsuanningmeng added a commit to nsuanningmeng/LemonHub that referenced this pull request Aug 6, 2026
Sync 71 upstream commits (rc.21 -> rc.23). Major upstream changes adopted:
- stateless-token dashboard auth (sessions removed); fork site scoping
  re-grafted fail-closed via model.GetUserSiteId in setDashboardAuthContext
- relaykit module extraction; fork billing guards, error override, and
  O(N^2) DoS fix re-applied inside relaykit paths
- web/default -> web frontend move; web/classic theme deleted (fork's
  classic-only edits verified ported to the new tree before deletion)
- auto-group (QuantumNous#6590), per-channel HTTP transport, tool pricing, zstd,
  New API channel, tiered-retry billing fixes (QuantumNous#6518/QuantumNous#6570)

Fork features preserved and verified: multi-site scoping, tickets,
captcha suite (per-request verification now, no session memo), email
campaigns, request-body recording, epay three-path settlement, quota
saturation invariants, model-redirect hiding, token multi-group priority.

Notable resolutions:
- 76 fork-only frontend files missed by rename detection were manually
  relocated from web/default/src to web/src (tickets/referral/site-admin/
  sub-site-management/contact/affiliate + routes + locale extras)
- altcha dependency restored in web/package.json
- getModelListGroups adopts upstream empty-on-filtered-snapshot semantics
- relayconvert goldens regenerated for fork's BillingUsage json:"-" privacy
- upstream tests adapted to fork signatures (site scope args, SSRF-off
  TestMain in controller)

Semantic changes accepted from upstream: external identity binding is now
platform-global (was per-site); captcha verification is per-request.

go build/vet/test green in root and relaykit modules.
yuqiyi pushed a commit to yuqiyi/new-api that referenced this pull request Aug 16, 2026
* v1.0.0-rc.24: (117 commits)
  CI: enhance release synchronization workflow with optional file syncing
  fix: 修复兑换码额度精度损失 (QuantumNous#6685)
  feat(rate-limit): add user critical rate limit middleware for access token and aff transfer routes
  fix: test Claude/Gemini endpoints with native request format (QuantumNous#6698)
  feat(channels): refine fetched model categorization (QuantumNous#6632)
  Merge commit from fork
  refactor(relay): move replay metadata onto request bodies
  fix(relay): set Request.GetBody so the HTTP/2 transport can transparently retry after an upstream stream reset (QuantumNous#6249)
  Feat/auto group (QuantumNous#6590)
  fix(aws): cancel Bedrock requests on client disconnect (QuantumNous#6589)
  fix(billing): harden tiered retry group-switch billing (QuantumNous#6570)
  fix(billing): settle tiered retries with final group (QuantumNous#6518)
  feat: deepseek responses api (QuantumNous#6562)
  style: use text-sm for public header nav links to match other nav components (QuantumNous#6557)
  fix(oauth): stop treating a foreign window.opener as a bind flow (QuantumNous#6425)
  fix(relay): preserve multipart image edits for New API channels (QuantumNous#6559)
  feat(logs): expose stream status to log owners (QuantumNous#6558)
  feat: support zstd request decompression (QuantumNous#6545)
  fix: preserve Qwen thinking_budget passthrough (QuantumNous#5836)
  feat(oidc): 支持自定义 OIDC 登录显示名称 (QuantumNous#6012)
  ...

# Conflicts:
#	service/text_quota.go
#	web/src/features/models/components/drawers/model-mutate-drawer.tsx
#	web/src/features/pricing/components/model-details.tsx
#	web/src/features/pricing/lib/price.ts
refeiner pushed a commit to wuqiang44444444/new-api that referenced this pull request Aug 17, 2026
)

Follow-up to QuantumNous#6518 (issue QuantumNous#6480) addressing three review findings:

- Document and lock in arrears semantics for the wallet Reserve top-up:
  when an auto-group retry lands on a more expensive group, the full
  reservation delta is deducted unconditionally (balance may go
  negative), mirroring settlement, so the logged pre-consumed quota
  always reconciles with the actual balance movement. Genuine DB
  errors still fail the attempt with update_data_error. Subscription
  funding keeps its insufficient-quota behavior: subscriptions enforce
  a hard used<=total cap and do not support arrears.
- PriceData.FreeModel is cleared when a retry switches from a free
  group to a paid one, keeping it consistent with the billing session
  created at that point.
- getChannel refreshes GroupRatioInfo only after channel selection
  succeeds, and the retry loop records the channel in use_channel
  before PrepareTieredBillingForSelectedGroup can fail.
330079598 pushed a commit to 330079598/new-api that referenced this pull request Aug 19, 2026
)

Follow-up to QuantumNous#6518 (issue QuantumNous#6480) addressing three review findings:

- Document and lock in arrears semantics for the wallet Reserve top-up:
  when an auto-group retry lands on a more expensive group, the full
  reservation delta is deducted unconditionally (balance may go
  negative), mirroring settlement, so the logged pre-consumed quota
  always reconciles with the actual balance movement. Genuine DB
  errors still fail the attempt with update_data_error. Subscription
  funding keeps its insufficient-quota behavior: subscriptions enforce
  a hard used<=total cap and do not support arrears.
- PriceData.FreeModel is cleared when a retry switches from a free
  group to a paid one, keeping it consistent with the billing session
  created at that point.
- getChannel refreshes GroupRatioInfo only after channel selection
  succeeds, and the retry loop records the channel in use_channel
  before PrepareTieredBillingForSelectedGroup can fail.
@Calcium-Ion
Calcium-Ion deleted the fix/tiered-retry-billing-followups branch August 29, 2026 06:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant