import { loadStripe } from '@stripe/stripe-js'; import Stripe from 'stripe'; import { SparklesIcon, RocketLaunchIcon, UserGroupIcon, StarIcon, DocumentTextIcon } from '@heroicons/react/24/outline'; import { validateStripeEnv } from './env-validation'; // Initialize Stripe on the server side only let stripe: Stripe | null = null; if (typeof window === 'undefined') { try { const config = validateStripeEnv(); stripe = new Stripe(config.STRIPE_SECRET_KEY!, { apiVersion: '2025-05-28.basil', typescript: true, }); } catch (error) { console.error('Failed to initialize server-side Stripe:', error); } } // Initialize Stripe on the client side let stripePromise: Promise | null = null; export const getStripe = () => { if (typeof window === 'undefined') return null; if (!stripePromise) { const config = validateStripeEnv(); stripePromise = loadStripe(config.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!); } return stripePromise; }; // Export the server-side Stripe instance export { stripe }; export type BillingInterval = 'month' | 'year'; // Environment-specific price IDs const isProduction = process.env.NODE_ENV === 'production'; const PRICE_IDS = { EXPLORER: { monthly: isProduction ? 'price_1NXtg7Jf6tol2iFRXqG1nUiV' : 'price_1RWxDvJf6tol2iFRdfnnvAKE', annually: isProduction ? 'price_1NXtg8Jf6tol2iFRcx8QZyPU' : 'price_1RWxDvJf6tol2iFRil3C7eKB', }, STORYTELLER: { monthly: isProduction ? 'price_1NXtuFJf6tol2iFRRJJg3pDN' : 'price_1RWxEpJf6tol2iFRwl5HJraX', annually: isProduction ? 'price_1NXtuFJf6tol2iFR7SpaNHcz' : 'price_1RWxEpJf6tol2iFRZAlz7zaY', }, PROFESSIONAL: { monthly: isProduction ? 'price_1NF2phJf6tol2iFRcdzQkGV5' : 'price_1RWxFQJf6tol2iFRGxE9JQcG', annually: isProduction ? 'price_1NkB3DJf6tol2iFRRzOqr9JR' : 'price_1RWxFiJf6tol2iFRs0eFTe1h', }, }; export interface PricingTier { id: string; name: string; description: string; features: string[]; cta: string; popular: boolean; icon: typeof SparklesIcon; color: string; borderColor: string; // Benefit details (consolidated from PRICING_TIER_BENEFITS) maxSmartPrompts: number; hasUnlimitedFastPrompts: boolean; maxFastPrompts: number; maxStorageGB: number; canCreateCustomTemplates: boolean; maxCustomTemplates?: number; // undefined means unlimited maxBooks?: number; // undefined means unlimited hasPrioritySupport: boolean; hasAdvancedExports: boolean; hasCollaboration: boolean; hasAPIAccess: boolean; resetPeriod: 'monthly' | 'annually'; pricing: { monthly: { price: number; priceId: string; }; annually: { price: number; priceId: string; savings: number; // How much they save per month }; }; } // Define the pricing tiers with environment-specific Stripe price IDs export const PRICING_TIERS: Record = { FREE: { id: 'free', name: 'Free', description: 'Get started with basic features', features: [ 'Create up to 3 books', '3 prompts to 🧠 Smart AI models', '15 prompts to ⚡ Fast AI models', 'Cloud storage (100MB)', 'Community support', ], cta: 'Get Started Free', popular: false, icon: DocumentTextIcon, color: 'from-gray-600 to-gray-700', borderColor: 'border-gray-600/50', maxSmartPrompts: 3, hasUnlimitedFastPrompts: false, maxFastPrompts: 15, maxStorageGB: 0.1, canCreateCustomTemplates: false, maxCustomTemplates: 0, maxBooks: 3, hasPrioritySupport: false, hasAdvancedExports: false, hasCollaboration: false, hasAPIAccess: false, resetPeriod: 'monthly', pricing: { monthly: { price: 0, priceId: 'free', }, annually: { price: 0, priceId: 'free', savings: 0, }, }, }, EXPLORER: { id: 'explorer', name: 'Explorer', description: 'Perfect for writers just starting their journey', features: [ 'Unlimited writing projects', '100 🧠 Smart AI prompts', '1000 ⚡ Fast AI prompts', 'Cloud storage (1GB)', 'Standard support', ], cta: 'Start Writing', popular: false, icon: SparklesIcon, color: 'from-teal-600 to-cyan-600', borderColor: 'border-teal-600/50', maxSmartPrompts: 100, hasUnlimitedFastPrompts: false, maxFastPrompts: 1000, maxStorageGB: 1, canCreateCustomTemplates: false, maxCustomTemplates: 0, maxBooks: undefined, // unlimited hasPrioritySupport: false, hasAdvancedExports: false, hasCollaboration: false, hasAPIAccess: false, resetPeriod: 'monthly', pricing: { monthly: { price: 9, priceId: PRICE_IDS.EXPLORER.monthly, }, annually: { price: 90, priceId: PRICE_IDS.EXPLORER.annually, savings: 1.5, // $9 * 12 = $108, they pay $90, save $18/year = $1.5/month }, }, }, STORYTELLER: { id: 'storyteller', name: 'Storyteller', description: 'For serious writers crafting their masterpieces', features: [ 'Everything in Explorer', '500 🧠 Smart AI prompts', 'Unlimited ⚡ Fast AI prompts', 'Cloud storage (10GB)', 'Priority support', ], cta: 'Upgrade to Storyteller', popular: true, icon: RocketLaunchIcon, color: 'from-cyan-500 to-teal-500', borderColor: 'border-cyan-500/50', maxSmartPrompts: 500, hasUnlimitedFastPrompts: true, maxFastPrompts: 999999, maxStorageGB: 10, canCreateCustomTemplates: true, maxCustomTemplates: undefined, // unlimited once you can create them maxBooks: undefined, // unlimited hasPrioritySupport: true, hasAdvancedExports: true, hasCollaboration: false, hasAPIAccess: false, resetPeriod: 'monthly', pricing: { monthly: { price: 35, priceId: PRICE_IDS.STORYTELLER.monthly, }, annually: { price: 350, priceId: PRICE_IDS.STORYTELLER.annually, savings: 5, // $35 * 12 = $420, they pay $350, save $70/year = $5.83/month }, }, }, PROFESSIONAL: { id: 'professional', name: 'Professional', description: 'For professional authors and publishing teams', features: [ 'Everything in Storyteller', '2000 🧠 Smart AI prompts', 'Unlimited ⚡ Fast AI prompts', 'Cloud storage (40GB)', '24/7 priority support', ], cta: 'Upgrade to Professional', popular: false, icon: StarIcon, color: 'from-amber-500 to-orange-500', borderColor: 'border-amber-500/50', maxSmartPrompts: 2000, hasUnlimitedFastPrompts: true, maxFastPrompts: 999999, maxStorageGB: 20, canCreateCustomTemplates: true, maxCustomTemplates: undefined, // unlimited once you can create them maxBooks: undefined, // unlimited hasPrioritySupport: true, hasAdvancedExports: true, hasCollaboration: true, hasAPIAccess: true, resetPeriod: 'monthly', pricing: { monthly: { price: 99, priceId: PRICE_IDS.PROFESSIONAL.monthly, }, annually: { price: 990, priceId: PRICE_IDS.PROFESSIONAL.annually, savings: 9, // $99 * 12 = $1188, they pay $990, save $198/year = $16.5/month }, }, }, }; // Utility functions export function getPlanByPriceId(priceId: string) { return Object.values(PRICING_TIERS).find(tier => tier.pricing.monthly.priceId === priceId || tier.pricing.annually.priceId === priceId ) || null; } export function formatPrice(price: number): string { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 0, }).format(price); } export function formatDate(dateString: string | null | undefined): string { if (!dateString) { return 'Not available'; } const date = new Date(dateString); // Check if date is valid if (isNaN(date.getTime())) { return 'Invalid date'; } return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric', }).format(date); } export function getPriceForInterval(tier: PricingTier, interval: BillingInterval) { return interval === 'month' ? tier.pricing.monthly : tier.pricing.annually; } export function calculateMonthlySavings(tier: PricingTier): number { const monthlyPrice = tier.pricing.monthly.price; const annualMonthlyPrice = tier.pricing.annually.price / 12; return monthlyPrice - annualMonthlyPrice; } // Create a map of price IDs to tier IDs for quick lookup const PRICE_ID_TO_TIER_MAP: Record = {}; Object.values(PRICING_TIERS).forEach(tier => { PRICE_ID_TO_TIER_MAP[tier.pricing.monthly.priceId] = tier.id.toUpperCase(); PRICE_ID_TO_TIER_MAP[tier.pricing.annually.priceId] = tier.id.toUpperCase(); }); // Utility function to get tier by price ID export function getTierByPriceId(priceId: string | null): PricingTier { if (!priceId || priceId === 'free') return PRICING_TIERS.FREE; const tierId = PRICE_ID_TO_TIER_MAP[priceId]; return PRICING_TIERS[tierId] || PRICING_TIERS.FREE; } // Utility function to get tier by tier ID export function getTierById(tierId: string | null): PricingTier { if (!tierId) return PRICING_TIERS.FREE; return PRICING_TIERS[tierId.toUpperCase()] || PRICING_TIERS.FREE; }