mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-02 22:42:14 +08:00
111 lines
2.5 KiB
Go
111 lines
2.5 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
var ErrUsageBillingRequestIDRequired = errors.New("usage billing request_id is required")
|
|
var ErrUsageBillingRequestConflict = errors.New("usage billing request fingerprint conflict")
|
|
|
|
// UsageBillingCommand describes one billable request that must be applied at most once.
|
|
type UsageBillingCommand struct {
|
|
RequestID string
|
|
APIKeyID int64
|
|
RequestFingerprint string
|
|
RequestPayloadHash string
|
|
|
|
UserID int64
|
|
AccountID int64
|
|
SubscriptionID *int64
|
|
AccountType string
|
|
Model string
|
|
ServiceTier string
|
|
ReasoningEffort string
|
|
BillingType int8
|
|
InputTokens int
|
|
OutputTokens int
|
|
CacheCreationTokens int
|
|
CacheReadTokens int
|
|
ImageCount int
|
|
MediaType string
|
|
|
|
BalanceCost float64
|
|
SubscriptionCost float64
|
|
APIKeyQuotaCost float64
|
|
APIKeyRateLimitCost float64
|
|
AccountQuotaCost float64
|
|
}
|
|
|
|
func (c *UsageBillingCommand) Normalize() {
|
|
if c == nil {
|
|
return
|
|
}
|
|
c.RequestID = strings.TrimSpace(c.RequestID)
|
|
if strings.TrimSpace(c.RequestFingerprint) == "" {
|
|
c.RequestFingerprint = buildUsageBillingFingerprint(c)
|
|
}
|
|
}
|
|
|
|
func buildUsageBillingFingerprint(c *UsageBillingCommand) string {
|
|
if c == nil {
|
|
return ""
|
|
}
|
|
raw := fmt.Sprintf(
|
|
"%d|%d|%d|%s|%s|%s|%s|%d|%d|%d|%d|%d|%d|%s|%d|%0.10f|%0.10f|%0.10f|%0.10f|%0.10f",
|
|
c.UserID,
|
|
c.AccountID,
|
|
c.APIKeyID,
|
|
strings.TrimSpace(c.AccountType),
|
|
strings.TrimSpace(c.Model),
|
|
strings.TrimSpace(c.ServiceTier),
|
|
strings.TrimSpace(c.ReasoningEffort),
|
|
c.BillingType,
|
|
c.InputTokens,
|
|
c.OutputTokens,
|
|
c.CacheCreationTokens,
|
|
c.CacheReadTokens,
|
|
c.ImageCount,
|
|
strings.TrimSpace(c.MediaType),
|
|
valueOrZero(c.SubscriptionID),
|
|
c.BalanceCost,
|
|
c.SubscriptionCost,
|
|
c.APIKeyQuotaCost,
|
|
c.APIKeyRateLimitCost,
|
|
c.AccountQuotaCost,
|
|
)
|
|
if payloadHash := strings.TrimSpace(c.RequestPayloadHash); payloadHash != "" {
|
|
raw += "|" + payloadHash
|
|
}
|
|
sum := sha256.Sum256([]byte(raw))
|
|
return hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
func HashUsageRequestPayload(payload []byte) string {
|
|
if len(payload) == 0 {
|
|
return ""
|
|
}
|
|
sum := sha256.Sum256(payload)
|
|
return hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
func valueOrZero(v *int64) int64 {
|
|
if v == nil {
|
|
return 0
|
|
}
|
|
return *v
|
|
}
|
|
|
|
type UsageBillingApplyResult struct {
|
|
Applied bool
|
|
APIKeyQuotaExhausted bool
|
|
}
|
|
|
|
type UsageBillingRepository interface {
|
|
Apply(ctx context.Context, cmd *UsageBillingCommand) (*UsageBillingApplyResult, error)
|
|
}
|