From 8639478a113448a8ce431449c3b457f7ae7d83c2 Mon Sep 17 00:00:00 2001 From: Matthias Meister Date: Fri, 3 Apr 2026 19:20:28 +0200 Subject: [PATCH] refactor(config): unify tier credit constants across frontend and backend --- convex/credits.ts | 15 ++++++++------- lib/polar-products.ts | 15 +++++++-------- lib/tier-credits.ts | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 lib/tier-credits.ts diff --git a/convex/credits.ts b/convex/credits.ts index 0388636..d02c510 100644 --- a/convex/credits.ts +++ b/convex/credits.ts @@ -2,6 +2,7 @@ import { query, mutation, internalMutation } from "./_generated/server"; import { v, ConvexError } from "convex/values"; import { optionalAuth, requireAuth } from "./helpers"; import { internal } from "./_generated/api"; +import { MONTHLY_TIER_CREDITS, normalizeBillingTier } from "../lib/tier-credits"; // ============================================================================ // Tier-Konfiguration @@ -9,35 +10,35 @@ import { internal } from "./_generated/api"; export const TIER_CONFIG = { free: { - monthlyCredits: 50, + monthlyCredits: MONTHLY_TIER_CREDITS.free, dailyGenerationCap: 10, concurrencyLimit: 1, premiumModels: false, topUpLimit: 50000, }, starter: { - monthlyCredits: 400, + monthlyCredits: MONTHLY_TIER_CREDITS.starter, dailyGenerationCap: 50, concurrencyLimit: 2, premiumModels: true, topUpLimit: 2000, // €20 pro Monat }, pro: { - monthlyCredits: 3300, + monthlyCredits: MONTHLY_TIER_CREDITS.pro, dailyGenerationCap: 200, concurrencyLimit: 2, premiumModels: true, topUpLimit: 10000, // €100 pro Monat }, max: { - monthlyCredits: 6700, + monthlyCredits: MONTHLY_TIER_CREDITS.max, dailyGenerationCap: 500, concurrencyLimit: 2, premiumModels: true, topUpLimit: 50000, }, business: { - monthlyCredits: 6700, + monthlyCredits: MONTHLY_TIER_CREDITS.business, dailyGenerationCap: 500, concurrencyLimit: 2, premiumModels: true, @@ -443,7 +444,7 @@ export const reserve = mutation({ .withIndex("by_user", (q) => q.eq("userId", user.userId)) .order("desc") .first(); - const tier = (subscription?.tier ?? "free") as Tier; + const tier = normalizeBillingTier(subscription?.tier); const config = TIER_CONFIG[tier]; // Daily Cap prüfen @@ -825,7 +826,7 @@ export const checkAbuseLimits = internalMutation({ .withIndex("by_user", (q) => q.eq("userId", user.userId)) .order("desc") .first(); - const tier = (subscription?.tier ?? "free") as Tier; + const tier = normalizeBillingTier(subscription?.tier); const config = TIER_CONFIG[tier]; const today = new Date().toISOString().split("T")[0]; diff --git a/lib/polar-products.ts b/lib/polar-products.ts index 38ecf01..a3ef1d8 100644 --- a/lib/polar-products.ts +++ b/lib/polar-products.ts @@ -1,3 +1,8 @@ +import { + PUBLIC_TIER_MONTHLY_CREDITS, + normalizePublicTier, +} from "./tier-credits"; + export const SUBSCRIPTION_PRODUCTS = { starter: { polarProductId: "81b6de07-cd41-430f-bd54-f0e7072deec6", @@ -47,15 +52,9 @@ export const TOPUP_PRODUCTS = [ ] as const; export const TIER_MONTHLY_CREDITS = { - free: 50, - starter: 400, - pro: 3300, - max: 6700, + ...PUBLIC_TIER_MONTHLY_CREDITS, } as const; export function normalizeTier(tier: string | undefined | null): keyof typeof TIER_MONTHLY_CREDITS { - if (!tier || tier === "free") return "free"; - if (tier === "starter" || tier === "pro" || tier === "max") return tier; - if (tier === "business") return "max"; - return "free"; + return normalizePublicTier(tier); } diff --git a/lib/tier-credits.ts b/lib/tier-credits.ts new file mode 100644 index 0000000..c6cfc68 --- /dev/null +++ b/lib/tier-credits.ts @@ -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; +}