From 0b9f40e23044b7d987e448669f5a7c829bed9b8a Mon Sep 17 00:00:00 2001 From: wucm667 Date: Sun, 2 Aug 2026 20:50:13 +0800 Subject: [PATCH] fix(billing): retain usage logs on billing failure --- .../service/gateway_record_usage_test.go | 16 ++++++++++++---- .../internal/service/gateway_usage_billing.go | 2 ++ .../service/openai_gateway_record_usage_test.go | 16 ++++++++++++---- backend/internal/service/openai_gateway_usage.go | 2 ++ 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/backend/internal/service/gateway_record_usage_test.go b/backend/internal/service/gateway_record_usage_test.go index fda35a8e2a..517d4723cb 100644 --- a/backend/internal/service/gateway_record_usage_test.go +++ b/backend/internal/service/gateway_record_usage_test.go @@ -577,9 +577,10 @@ func TestGatewayServiceRecordUsage_DroppedUsageLogFallsBackToSyncCreate(t *testi require.NoError(t, usageRepo.lastCtxErr) } -func TestGatewayServiceRecordUsage_BillingErrorSkipsUsageLogWrite(t *testing.T) { +func TestGatewayServiceRecordUsage_BillingErrorWritesUnsettledUsageLog(t *testing.T) { usageRepo := &openAIRecordUsageLogRepoStub{} - billingRepo := &openAIRecordUsageBillingRepoStub{err: context.DeadlineExceeded} + billingErr := errors.New("billing tx failed") + billingRepo := &openAIRecordUsageBillingRepoStub{err: billingErr} userRepo := &openAIRecordUsageUserRepoStub{} subRepo := &openAIRecordUsageSubRepoStub{} svc := newGatewayRecordUsageServiceWithBillingRepoForTest(usageRepo, billingRepo, userRepo, subRepo) @@ -599,9 +600,16 @@ func TestGatewayServiceRecordUsage_BillingErrorSkipsUsageLogWrite(t *testing.T) Account: &Account{ID: 705}, }) - require.Error(t, err) + require.ErrorIs(t, err, billingErr) require.Equal(t, 1, billingRepo.calls) - require.Equal(t, 0, usageRepo.calls) + require.Equal(t, 1, usageRepo.calls) + require.NotNil(t, usageRepo.lastLog) + require.Equal(t, 10, usageRepo.lastLog.InputTokens) + require.Equal(t, 6, usageRepo.lastLog.OutputTokens) + require.Greater(t, usageRepo.lastLog.InputCost, 0.0) + require.Greater(t, usageRepo.lastLog.OutputCost, 0.0) + require.Greater(t, usageRepo.lastLog.TotalCost, 0.0) + require.Zero(t, usageRepo.lastLog.ActualCost) } func TestGatewayServiceRecordUsage_ReasoningEffortPersisted(t *testing.T) { diff --git a/backend/internal/service/gateway_usage_billing.go b/backend/internal/service/gateway_usage_billing.go index 80de338be4..0d07b355c6 100644 --- a/backend/internal/service/gateway_usage_billing.go +++ b/backend/internal/service/gateway_usage_billing.go @@ -786,6 +786,8 @@ func (s *GatewayService) recordUsageCore(ctx context.Context, input *recordUsage }, s.billingDeps(), s.usageBillingRepo) if billingErr != nil { + usageLog.ActualCost = 0 + writeUsageLogBestEffort(ctx, s.usageLogRepo, usageLog, "service.gateway") return billingErr } writeUsageLogBestEffort(ctx, s.usageLogRepo, usageLog, "service.gateway") diff --git a/backend/internal/service/openai_gateway_record_usage_test.go b/backend/internal/service/openai_gateway_record_usage_test.go index d3ccf24c74..beb674474e 100644 --- a/backend/internal/service/openai_gateway_record_usage_test.go +++ b/backend/internal/service/openai_gateway_record_usage_test.go @@ -936,9 +936,10 @@ func TestOpenAIGatewayServiceRecordUsage_GeneratesRequestIDWhenAllSourcesMissing require.Equal(t, billingRepo.lastCmd.RequestID, usageRepo.lastLog.RequestID) } -func TestOpenAIGatewayServiceRecordUsage_BillingErrorSkipsUsageLogWrite(t *testing.T) { +func TestOpenAIGatewayServiceRecordUsage_BillingErrorWritesUnsettledUsageLog(t *testing.T) { usageRepo := &openAIRecordUsageLogRepoStub{} - billingRepo := &openAIRecordUsageBillingRepoStub{err: errors.New("billing tx failed")} + billingErr := errors.New("billing tx failed") + billingRepo := &openAIRecordUsageBillingRepoStub{err: billingErr} userRepo := &openAIRecordUsageUserRepoStub{} subRepo := &openAIRecordUsageSubRepoStub{} svc := newOpenAIRecordUsageServiceWithBillingRepoForTest(usageRepo, billingRepo, userRepo, subRepo, nil) @@ -958,9 +959,16 @@ func TestOpenAIGatewayServiceRecordUsage_BillingErrorSkipsUsageLogWrite(t *testi Account: &Account{ID: 30048}, }) - require.Error(t, err) + require.ErrorIs(t, err, billingErr) require.Equal(t, 1, billingRepo.calls) - require.Equal(t, 0, usageRepo.calls) + require.Equal(t, 1, usageRepo.calls) + require.NotNil(t, usageRepo.lastLog) + require.Equal(t, 8, usageRepo.lastLog.InputTokens) + require.Equal(t, 4, usageRepo.lastLog.OutputTokens) + require.Greater(t, usageRepo.lastLog.InputCost, 0.0) + require.Greater(t, usageRepo.lastLog.OutputCost, 0.0) + require.Greater(t, usageRepo.lastLog.TotalCost, 0.0) + require.Zero(t, usageRepo.lastLog.ActualCost) } func TestOpenAIGatewayServiceRecordUsage_UpdatesAPIKeyQuotaWhenConfigured(t *testing.T) { diff --git a/backend/internal/service/openai_gateway_usage.go b/backend/internal/service/openai_gateway_usage.go index d1b27a1ff7..0725c34cf7 100644 --- a/backend/internal/service/openai_gateway_usage.go +++ b/backend/internal/service/openai_gateway_usage.go @@ -400,6 +400,8 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec }() if billingErr != nil { + usageLog.ActualCost = 0 + writeUsageLogBestEffort(ctx, s.usageLogRepo, usageLog, "service.openai_gateway") return billingErr } writeUsageLogBestEffort(ctx, s.usageLogRepo, usageLog, "service.openai_gateway")