package billing import "strings" // CreditsPerAIProduct is the typical wallet debit for one AI enhance // (product_processing base + one openai_token_k pack when tokens ≤ 1000). // Each content-language pass burns another ~CreditsPerAIProduct per product. // Included monthly grants assume AssumedPrimaryContentLanguages only. const CreditsPerAIProduct = 2 // AssumedPrimaryContentLanguages is how many content languages the included // monthly grant is sized for. Extra languages → credit packs or BYOK. const AssumedPrimaryContentLanguages = 1 // ScaleMaxProducts is the top self-serve SKU ceiling. Huge catalogs (1M+) // belong on Enterprise (sales-led credits / BYOK), not public Scale. const ScaleMaxProducts = 12_000 // PlanAICoverPercent is included monthly AI as a share of CreditSKUBase // (primary language only). Paid public plans use ~50% cover so Starter stays // lean (100 credits ≈ 50 AI products on a 100-SKU plan) and higher tiers // scale with CreditSKUBase ≤ PlanMaxProducts — not a full-catalog AI bundle. // Formula: credits = (CreditSKUBase × cover% / 100) × CreditsPerAIProduct. func PlanAICoverPercent(planName string) int { switch strings.ToLower(strings.TrimSpace(planName)) { case "starter": return 50 // 100 × 50% × 2 = 100 case "plus": return 50 // 400 × 50% × 2 = 400 case "growth": return 50 // 1,200 × 50% × 2 = 1,200 case "business": return 50 // 4,000 × 50% × 2 = 4,000 case "scale": return 50 // 12,000 × 50% × 2 = 12,000 case "enterprise": return 50 // display ladder only; grant is EnterpriseUnlimitedCredits default: return 0 } } // PlanMaxProducts is the hard SKU ceiling for a public plan name. // Slow retail ladder for small→mid shops; Scale reaches ScaleMaxProducts; // Enterprise is unlimited (nil). Credits sized via CreditSKUBase (≤ MaxProducts). func PlanMaxProducts(planName string) *int { mp := func(n int) *int { return &n } switch strings.ToLower(strings.TrimSpace(planName)) { case "free": return mp(50) case "starter": return mp(100) case "plus": return mp(400) case "growth": return mp(1_200) case "business": return mp(4_000) case "scale": return mp(ScaleMaxProducts) default: // Enterprise and unknown custom plans — unlimited SKU cap. return nil } } // CreditSKUBase is the catalog size used ONLY to size included monthly AI credits. // May be smaller than PlanMaxProducts so large catalogs still get a bounded AI starter grant. // Public paid ladder: base equals PlanMaxProducts (50% cover → half-catalog primary-lang AI). func CreditSKUBase(planName string) int { switch strings.ToLower(strings.TrimSpace(planName)) { case "starter": return 100 case "plus": return 400 case "growth": return 1_200 case "business": return 4_000 case "scale": return 12_000 default: return 0 } } // MonthlyCreditsForSKUCover returns credits for coverPct% of skuBase at CreditsPerAIProduct each. func MonthlyCreditsForSKUCover(skuBase, coverPct int) int { if skuBase <= 0 || coverPct <= 0 { return 0 } if coverPct > 100 { coverPct = 100 } products := (skuBase * coverPct) / 100 return products * CreditsPerAIProduct * AssumedPrimaryContentLanguages } // MonthlyCreditsForPlan sizes the monthly grant from CreditSKUBase × PlanAICoverPercent. // The maxProducts argument is ignored when CreditSKUBase is set (paid public ladder). func MonthlyCreditsForPlan(planName string, maxProducts int) int { base := CreditSKUBase(planName) if base <= 0 { base = maxProducts } return MonthlyCreditsForSKUCover(base, PlanAICoverPercent(planName)) } // CreditPack is a one-time AI credit top-up sold via Stripe Checkout (mode=payment). // These are additional Stripe Products with one-time Prices — not subscription add-ons. type CreditPack struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Credits int `json:"credits"` PriceUSD int `json:"price_usd"` // whole dollars for marketing UI // Approx product×language passes at CreditsPerAIProduct. AIProducts int `json:"ai_products"` } func packFromCredits(id, name, desc string, credits, priceUSD int) CreditPack { return CreditPack{ ID: id, Name: name, Description: desc, Credits: credits, PriceUSD: priceUSD, AIProducts: credits / CreditsPerAIProduct, } } // DefaultCreditPacks is the public top-up ladder (credits-first sizing). // Prices are balanced so small packs are not punitive $/credit vs larger ones, // while staying expensive enough that packs cannot undercut plan upgrades or A1 (~€300). func DefaultCreditPacks() []CreditPack { return []CreditPack{ packFromCredits("tiny", "Nano pack", "Smoke tests / tiny fixes (25 credits ≈ 12 AI products)", 25, 29), packFromCredits("small", "Starter pack", "Small top-up (65 credits ≈ 32 AI products)", 65, 59), packFromCredits("medium", "Plus pack", "Burst top-up (200 credits ≈ 100 AI products)", 200, 149), packFromCredits("large", "Growth pack", "Mid buffer (500 credits ≈ 250 AI products)", 500, 299), packFromCredits("xl", "Catalog pack", "Catalog / re-run buffer (1,200 credits ≈ 600 AI products)", 1200, 599), packFromCredits("xxl", "Business pack", "Large multi-language buffer (3,000 credits ≈ 1,500 AI products)", 3000, 1299), packFromCredits("mega", "Scale pack", "Distributor / agency burst (8,000 credits ≈ 4,000 AI products)", 8000, 2999), } } // CreditPackByID returns a pack from DefaultCreditPacks. func CreditPackByID(id string) (CreditPack, bool) { want := strings.ToLower(strings.TrimSpace(id)) for _, p := range DefaultCreditPacks() { if p.ID == want { return p, true } } return CreditPack{}, false } // CreditPackPriceKey is the Stripe PriceIDs map key for a one-time pack (pack:). func CreditPackPriceKey(packID string) string { return "pack:" + strings.ToLower(strings.TrimSpace(packID)) } // CreditPackSettingsKey is the platformsettings Values key (stripe.price.pack.). func CreditPackSettingsKey(packID string) string { return "stripe.price.pack." + strings.ToLower(strings.TrimSpace(packID)) } // CreditPackEnvVar is the optional process-env fallback (STRIPE_PRICE_PACK_). func CreditPackEnvVar(packID string) string { return "STRIPE_PRICE_PACK_" + strings.ToUpper(strings.TrimSpace(packID)) }