Files
sub2api/backend/internal/service/video_billing_resolution.go
T
shaw d4952154ff fix: bill Grok video per second and harden video usage logging
Follow-up fixes for the #3775 audit findings:

- Bill Grok video generation per second of output, matching the xAI rate
  card: parse the request duration (1-15s, upstream default 8s) and compute
  cost as per-second price x duration x count. The built-in rate card values
  were already xAI per-second prices but were previously charged per video,
  undercharging up to 15x with a user-controlled duration.
- Group video_price_* fields are now documented and surfaced as per-second
  rates (USD/s); admin UI labels, placeholders and hints updated accordingly.
- Persist video_count/video_resolution/video_duration_seconds on usage_logs
  (migration 172) so video billing is auditable, and exempt any row with
  video_count > 0 from the image_size check constraint: a video billed via a
  token-mode channel price produces billing_mode='token' with image_count=1
  and no image_size, which the previous constraint rejected, dropping the
  whole billing transaction.
- Only refetch the group in apiKeyWithFreshGroupMediaPricing when the group
  object actually looks like it is missing media pricing fields (both media
  multipliers zero and all prices nil, impossible for a normally loaded
  group), removing a per-usage DB query for groups without overrides.
- Frontend: drop the unused admin.groups.mediaPricing locale block, map
  cleared price inputs to null (create) / -1 (update, cleared via backend
  normalizePrice) instead of sending "" that failed *float64 unmarshalling,
  and align video price placeholders with the text-to-video default model
  (grok-imagine-video 0.05/0.07, 1080p only on 1.5 at 0.25).
2026-07-09 15:38:59 +08:00

46 lines
1.4 KiB
Go

package service
import "strings"
const (
VideoBillingResolution480P = "480p"
VideoBillingResolution720P = "720p"
VideoBillingResolution1080P = "1080p"
)
// xAI 视频生成按秒计费,duration 请求参数允许 1-15 秒;未指定时上游默认生成 8 秒。
// 计费时长必须与上游实际消耗对齐,否则用户可通过拉长 duration 套利(提交时长由用户控制)。
const (
VideoBillingMinDurationSeconds = 1
VideoBillingMaxDurationSeconds = 15
VideoBillingDefaultDurationSeconds = 8
)
// NormalizeVideoBillingDurationSecondsOrDefault 归一化计费用视频时长:
// 未指定(<=0)按上游默认 8 秒计,超出上游允许区间按边界收敛。
func NormalizeVideoBillingDurationSecondsOrDefault(durationSeconds int) int {
if durationSeconds <= 0 {
return VideoBillingDefaultDurationSeconds
}
if durationSeconds < VideoBillingMinDurationSeconds {
return VideoBillingMinDurationSeconds
}
if durationSeconds > VideoBillingMaxDurationSeconds {
return VideoBillingMaxDurationSeconds
}
return durationSeconds
}
func NormalizeVideoBillingResolutionOrDefault(resolution string) string {
switch strings.ToLower(strings.TrimSpace(resolution)) {
case "480", "480p", "sd":
return VideoBillingResolution480P
case "720", "720p", "hd":
return VideoBillingResolution720P
case "1080", "1080p", "full_hd", "full-hd", "fhd":
return VideoBillingResolution1080P
default:
return VideoBillingResolution480P
}
}