| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- package handler
- import (
- "net/http"
- "time"
- "github.com/Wei-Shaw/sub2api/internal/config"
- "github.com/Wei-Shaw/sub2api/internal/pkg/timezone"
- middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
- "github.com/Wei-Shaw/sub2api/internal/service"
- "github.com/gin-gonic/gin"
- )
- const keyBillingInfoSchemaVersion = 1
- type keyBillingInfoResponse struct {
- Object string `json:"object"`
- SchemaVersion int `json:"schema_version"`
- BillingScope string `json:"billing_scope"`
- GroupRateMultiplier float64 `json:"group_rate_multiplier"`
- UserRateMultiplier *float64 `json:"user_rate_multiplier,omitempty"`
- ResolvedRateMultiplier float64 `json:"resolved_rate_multiplier"`
- PeakRateEnabled bool `json:"peak_rate_enabled"`
- PeakStart *string `json:"peak_start,omitempty"`
- PeakEnd *string `json:"peak_end,omitempty"`
- PeakRateMultiplier *float64 `json:"peak_rate_multiplier,omitempty"`
- AppliedPeakMultiplier *float64 `json:"applied_peak_multiplier,omitempty"`
- EffectiveRateMultiplier float64 `json:"effective_rate_multiplier"`
- Timezone *string `json:"timezone,omitempty"`
- ObservedAt time.Time `json:"observed_at"`
- }
- // KeyBillingInfo returns the token billing multiplier effective for the authenticated API key.
- // GET /v1/sub2api/billing
- func (h *GatewayHandler) KeyBillingInfo(c *gin.Context) {
- apiKey, ok := middleware2.GetAPIKeyFromContext(c)
- if !ok {
- h.errorResponse(c, http.StatusUnauthorized, "authentication_error", "Invalid API key")
- return
- }
- if h.cfg != nil && h.cfg.RunMode == config.RunModeSimple {
- h.errorResponse(c, http.StatusNotFound, "not_found_error", "Billing information is not supported in simple mode")
- return
- }
- if apiKey.GroupID == nil {
- h.errorResponse(c, http.StatusForbidden, "permission_error", "API key is not assigned to a group")
- return
- }
- if apiKey.Group == nil {
- h.errorResponse(c, http.StatusInternalServerError, "api_error", "Billing information is unavailable")
- return
- }
- resolvedRate, ok := h.resolveKeyBillingRate(c, apiKey)
- if !ok {
- h.errorResponse(c, http.StatusInternalServerError, "api_error", "Billing information is unavailable")
- return
- }
- c.Header("Cache-Control", "no-store")
- c.JSON(http.StatusOK, buildKeyBillingInfo(apiKey, resolvedRate, timezone.Now()))
- }
- func (h *GatewayHandler) resolveKeyBillingRate(c *gin.Context, apiKey *service.APIKey) (float64, bool) {
- groupRate := apiKey.Group.RateMultiplier
- switch apiKey.Group.Platform {
- case service.PlatformOpenAI, service.PlatformGrok:
- if h.openAIGatewayService == nil {
- return 0, false
- }
- return h.openAIGatewayService.ResolveUserGroupRateMultiplier(c.Request.Context(), apiKey.UserID, *apiKey.GroupID, groupRate), true
- default:
- if h.gatewayService == nil {
- return 0, false
- }
- return h.gatewayService.ResolveUserGroupRateMultiplier(c.Request.Context(), apiKey.UserID, *apiKey.GroupID, groupRate), true
- }
- }
- func buildKeyBillingInfo(apiKey *service.APIKey, resolvedRate float64, now time.Time) keyBillingInfoResponse {
- groupRate := apiKey.Group.RateMultiplier
- var userRate *float64
- if resolvedRate != groupRate {
- userRate = &resolvedRate
- }
- appliedPeak := apiKey.Group.PeakMultiplierAt(now)
- response := keyBillingInfoResponse{
- Object: "sub2api.key_billing",
- SchemaVersion: keyBillingInfoSchemaVersion,
- BillingScope: "token",
- GroupRateMultiplier: groupRate,
- UserRateMultiplier: userRate,
- ResolvedRateMultiplier: resolvedRate,
- PeakRateEnabled: apiKey.Group.PeakRateEnabled,
- EffectiveRateMultiplier: resolvedRate * appliedPeak,
- ObservedAt: now.UTC(),
- }
- if apiKey.Group.PeakRateEnabled {
- response.PeakStart = &apiKey.Group.PeakStart
- response.PeakEnd = &apiKey.Group.PeakEnd
- response.PeakRateMultiplier = &apiKey.Group.PeakRateMultiplier
- response.AppliedPeakMultiplier = &appliedPeak
- tz := timezone.Location().String()
- response.Timezone = &tz
- }
- return response
- }
|