refactor(config): unify tier credit constants across frontend and backend

This commit is contained in:
2026-04-03 19:20:28 +02:00
parent 7e87a74df9
commit 8639478a11
3 changed files with 47 additions and 15 deletions

View File

@@ -2,6 +2,7 @@ import { query, mutation, internalMutation } from "./_generated/server";
import { v, ConvexError } from "convex/values"; import { v, ConvexError } from "convex/values";
import { optionalAuth, requireAuth } from "./helpers"; import { optionalAuth, requireAuth } from "./helpers";
import { internal } from "./_generated/api"; import { internal } from "./_generated/api";
import { MONTHLY_TIER_CREDITS, normalizeBillingTier } from "../lib/tier-credits";
// ============================================================================ // ============================================================================
// Tier-Konfiguration // Tier-Konfiguration
@@ -9,35 +10,35 @@ import { internal } from "./_generated/api";
export const TIER_CONFIG = { export const TIER_CONFIG = {
free: { free: {
monthlyCredits: 50, monthlyCredits: MONTHLY_TIER_CREDITS.free,
dailyGenerationCap: 10, dailyGenerationCap: 10,
concurrencyLimit: 1, concurrencyLimit: 1,
premiumModels: false, premiumModels: false,
topUpLimit: 50000, topUpLimit: 50000,
}, },
starter: { starter: {
monthlyCredits: 400, monthlyCredits: MONTHLY_TIER_CREDITS.starter,
dailyGenerationCap: 50, dailyGenerationCap: 50,
concurrencyLimit: 2, concurrencyLimit: 2,
premiumModels: true, premiumModels: true,
topUpLimit: 2000, // €20 pro Monat topUpLimit: 2000, // €20 pro Monat
}, },
pro: { pro: {
monthlyCredits: 3300, monthlyCredits: MONTHLY_TIER_CREDITS.pro,
dailyGenerationCap: 200, dailyGenerationCap: 200,
concurrencyLimit: 2, concurrencyLimit: 2,
premiumModels: true, premiumModels: true,
topUpLimit: 10000, // €100 pro Monat topUpLimit: 10000, // €100 pro Monat
}, },
max: { max: {
monthlyCredits: 6700, monthlyCredits: MONTHLY_TIER_CREDITS.max,
dailyGenerationCap: 500, dailyGenerationCap: 500,
concurrencyLimit: 2, concurrencyLimit: 2,
premiumModels: true, premiumModels: true,
topUpLimit: 50000, topUpLimit: 50000,
}, },
business: { business: {
monthlyCredits: 6700, monthlyCredits: MONTHLY_TIER_CREDITS.business,
dailyGenerationCap: 500, dailyGenerationCap: 500,
concurrencyLimit: 2, concurrencyLimit: 2,
premiumModels: true, premiumModels: true,
@@ -443,7 +444,7 @@ export const reserve = mutation({
.withIndex("by_user", (q) => q.eq("userId", user.userId)) .withIndex("by_user", (q) => q.eq("userId", user.userId))
.order("desc") .order("desc")
.first(); .first();
const tier = (subscription?.tier ?? "free") as Tier; const tier = normalizeBillingTier(subscription?.tier);
const config = TIER_CONFIG[tier]; const config = TIER_CONFIG[tier];
// Daily Cap prüfen // Daily Cap prüfen
@@ -825,7 +826,7 @@ export const checkAbuseLimits = internalMutation({
.withIndex("by_user", (q) => q.eq("userId", user.userId)) .withIndex("by_user", (q) => q.eq("userId", user.userId))
.order("desc") .order("desc")
.first(); .first();
const tier = (subscription?.tier ?? "free") as Tier; const tier = normalizeBillingTier(subscription?.tier);
const config = TIER_CONFIG[tier]; const config = TIER_CONFIG[tier];
const today = new Date().toISOString().split("T")[0]; const today = new Date().toISOString().split("T")[0];

View File

@@ -1,3 +1,8 @@
import {
PUBLIC_TIER_MONTHLY_CREDITS,
normalizePublicTier,
} from "./tier-credits";
export const SUBSCRIPTION_PRODUCTS = { export const SUBSCRIPTION_PRODUCTS = {
starter: { starter: {
polarProductId: "81b6de07-cd41-430f-bd54-f0e7072deec6", polarProductId: "81b6de07-cd41-430f-bd54-f0e7072deec6",
@@ -47,15 +52,9 @@ export const TOPUP_PRODUCTS = [
] as const; ] as const;
export const TIER_MONTHLY_CREDITS = { export const TIER_MONTHLY_CREDITS = {
free: 50, ...PUBLIC_TIER_MONTHLY_CREDITS,
starter: 400,
pro: 3300,
max: 6700,
} as const; } as const;
export function normalizeTier(tier: string | undefined | null): keyof typeof TIER_MONTHLY_CREDITS { export function normalizeTier(tier: string | undefined | null): keyof typeof TIER_MONTHLY_CREDITS {
if (!tier || tier === "free") return "free"; return normalizePublicTier(tier);
if (tier === "starter" || tier === "pro" || tier === "max") return tier;
if (tier === "business") return "max";
return "free";
} }

32
lib/tier-credits.ts Normal file
View File

@@ -0,0 +1,32 @@
export const MONTHLY_TIER_CREDITS = {
free: 50,
starter: 400,
pro: 3300,
max: 6700,
business: 6700,
} as const;
export type BillingTier = keyof typeof MONTHLY_TIER_CREDITS;
export const PUBLIC_TIER_MONTHLY_CREDITS = {
free: MONTHLY_TIER_CREDITS.free,
starter: MONTHLY_TIER_CREDITS.starter,
pro: MONTHLY_TIER_CREDITS.pro,
max: MONTHLY_TIER_CREDITS.max,
} as const;
export type PublicTier = keyof typeof PUBLIC_TIER_MONTHLY_CREDITS;
export function normalizeBillingTier(tier: string | undefined | null): BillingTier {
if (!tier || tier === "free") return "free";
if (tier === "starter" || tier === "pro" || tier === "max" || tier === "business") {
return tier;
}
return "free";
}
export function normalizePublicTier(tier: string | undefined | null): PublicTier {
const normalizedTier = normalizeBillingTier(tier);
if (normalizedTier === "business") return "max";
return normalizedTier;
}