From f5764d8dc6fbc0f98e0bef292b16bd570b0897eb Mon Sep 17 00:00:00 2001 From: wucm667 Date: Sat, 28 Mar 2026 16:22:06 +0800 Subject: [PATCH] =?UTF-8?q?fix(billing):=20=E8=AE=A1=E8=B4=B9=E5=A7=8B?= =?UTF-8?q?=E7=BB=88=E4=BD=BF=E7=94=A8=E7=94=A8=E6=88=B7=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E7=9A=84=E5=8E=9F=E5=A7=8B=E6=A8=A1=E5=9E=8B=EF=BC=8C=E8=80=8C?= =?UTF-8?q?=E9=9D=9E=E6=98=A0=E5=B0=84=E5=90=8E=E7=9A=84=E4=B8=8A=E6=B8=B8?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当账号配置了模型映射(如 claude-sonnet-4-6 → glm-5.0)时,系统错误地 使用映射后的上游模型名计算费用。由于上游模型(如 glm-5.0)在定价系统中 没有价格配置,导致计费失败后被静默置为 0,用户不被扣费。 修改 forwardResultBillingModel 优先返回请求模型名,并移除 OpenAI 路径 中 BillingModel 字段对计费模型的覆盖逻辑。 --- .../internal/service/openai_gateway_record_usage_test.go | 6 ++++-- backend/internal/service/openai_gateway_service.go | 3 --- backend/internal/service/usage_log_helpers.go | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/backend/internal/service/openai_gateway_record_usage_test.go b/backend/internal/service/openai_gateway_record_usage_test.go index 5aa4db8a..7a636afa 100644 --- a/backend/internal/service/openai_gateway_record_usage_test.go +++ b/backend/internal/service/openai_gateway_record_usage_test.go @@ -895,14 +895,16 @@ func TestOpenAIGatewayServiceRecordUsage_UsesRequestedModelAndUpstreamModelMetad require.Equal(t, 1, userRepo.deductCalls) } -func TestOpenAIGatewayServiceRecordUsage_BillsMappedRequestsUsingUpstreamModelFallback(t *testing.T) { +func TestOpenAIGatewayServiceRecordUsage_BillsMappedRequestsUsingRequestedModel(t *testing.T) { usageRepo := &openAIRecordUsageLogRepoStub{inserted: true} userRepo := &openAIRecordUsageUserRepoStub{} subRepo := &openAIRecordUsageSubRepoStub{} svc := newOpenAIRecordUsageServiceForTest(usageRepo, userRepo, subRepo, nil) usage := OpenAIUsage{InputTokens: 20, OutputTokens: 10} - expectedCost, err := svc.billingService.CalculateCost("gpt-5.1-codex", UsageTokens{ + // Billing should use the requested model ("gpt-5.1"), not the upstream mapped model ("gpt-5.1-codex"). + // This ensures pricing is always based on the model the user requested. + expectedCost, err := svc.billingService.CalculateCost("gpt-5.1", UsageTokens{ InputTokens: 20, OutputTokens: 10, }, 1.1) diff --git a/backend/internal/service/openai_gateway_service.go b/backend/internal/service/openai_gateway_service.go index a72a86ac..0a959615 100644 --- a/backend/internal/service/openai_gateway_service.go +++ b/backend/internal/service/openai_gateway_service.go @@ -4153,9 +4153,6 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec } billingModel := forwardResultBillingModel(result.Model, result.UpstreamModel) - if result.BillingModel != "" { - billingModel = strings.TrimSpace(result.BillingModel) - } serviceTier := "" if result.ServiceTier != nil { serviceTier = strings.TrimSpace(*result.ServiceTier) diff --git a/backend/internal/service/usage_log_helpers.go b/backend/internal/service/usage_log_helpers.go index 57c51540..a7bcae99 100644 --- a/backend/internal/service/usage_log_helpers.go +++ b/backend/internal/service/usage_log_helpers.go @@ -21,8 +21,8 @@ func optionalNonEqualStringPtr(value, compare string) *string { } func forwardResultBillingModel(requestedModel, upstreamModel string) string { - if trimmedUpstream := strings.TrimSpace(upstreamModel); trimmedUpstream != "" { - return trimmedUpstream + if trimmed := strings.TrimSpace(requestedModel); trimmed != "" { + return trimmed } - return strings.TrimSpace(requestedModel) + return strings.TrimSpace(upstreamModel) }