'use client'; import { useState, useEffect, useCallback } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { loadStripe } from '@stripe/stripe-js'; import { EmbeddedCheckoutProvider, EmbeddedCheckout } from '@stripe/react-stripe-js'; import { Check, Shield, Zap, Crown, X, ExternalLink, Loader2, CheckCircle2, Activity, Clock, ArrowRight } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useLanguage } from '@/lib/i18n'; import { toast } from 'sonner'; import { format } from 'date-fns'; import { motion } from 'motion/react'; type Tier = 'PRO' | 'BUSINESS'; type Interval = 'month' | 'year'; interface BillingStatus { tier: string; effectiveTier: string; status: string; currentPeriodEnd: string | null; cancelAtPeriodEnd: boolean; hasStripeSubscription: boolean; } const billingEnabled = process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED === 'true'; let stripePromise: ReturnType | null = null; function getStripePromise() { if (!billingEnabled) return null; if (!stripePromise && process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY) { stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY); } return stripePromise; } export function BillingPlans() { const { t } = useLanguage(); const queryClient = useQueryClient(); const [interval, setInterval] = useState('month'); const [checkoutClientSecret, setCheckoutClientSecret] = useState(null); const [isCheckoutOpen, setIsCheckoutOpen] = useState(false); const [checkoutLoading, setCheckoutLoading] = useState(null); const [portalLoading, setPortalLoading] = useState(false); const [successBanner, setSuccessBanner] = useState(null); const { data: status, isLoading } = useQuery({ queryKey: ['billing', 'status'], queryFn: async () => { const res = await fetch('/api/billing/status'); if (!res.ok) throw new Error('Failed to fetch billing status'); return res.json(); }, }); const { data: quotas } = useQuery({ queryKey: ['usage', 'current'], queryFn: async () => { const res = await fetch('/api/usage/current'); if (!res.ok) throw new Error('Failed to fetch quotas'); const data = await res.json(); return data.quotas as Record; }, refetchInterval: 60000, }); useEffect(() => { const params = new URLSearchParams(window.location.search); const sessionId = params.get('session_id'); if (sessionId) { const tier = status?.effectiveTier ?? 'Pro'; setSuccessBanner(t('billing.checkoutSuccessBody').replace('{tier}', tier)); queryClient.invalidateQueries({ queryKey: ['usage', 'current'] }); queryClient.invalidateQueries({ queryKey: ['billing', 'status'] }); window.history.replaceState({}, '', '/settings/billing'); } }, [status, t, queryClient]); const handleCheckout = async (tier: Tier) => { if (!billingEnabled) return; setCheckoutLoading(tier); try { const res = await fetch('/api/billing/create-checkout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tier, interval }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error ?? 'Failed to create checkout'); if (data.clientSecret) { setCheckoutClientSecret(data.clientSecret); setIsCheckoutOpen(true); } else if (data.url) { window.location.href = data.url; } } catch (err) { console.error('[BillingPlans] checkout error:', err); toast.error('Failed to start checkout. Please try again.'); } finally { setCheckoutLoading(null); } }; const handlePortal = async () => { setPortalLoading(true); try { const res = await fetch('/api/billing/portal', { method: 'POST' }); const data = await res.json(); if (!res.ok) throw new Error(data.error ?? 'Failed to open portal'); window.location.href = data.url; } catch (err) { console.error('[BillingPlans] portal error:', err); toast.error('Failed to open billing portal.'); } finally { setPortalLoading(false); } }; const handleCheckoutComplete = useCallback(() => { setIsCheckoutOpen(false); setCheckoutClientSecret(null); const tier = status?.effectiveTier ?? 'Pro'; setSuccessBanner(t('billing.checkoutSuccessBody').replace('{tier}', tier)); queryClient.invalidateQueries({ queryKey: ['usage', 'current'] }); queryClient.invalidateQueries({ queryKey: ['billing', 'status'] }); }, [status, t, queryClient]); if (isLoading) { return (
); } const effectiveTier = status?.effectiveTier ?? 'BASIC'; const isPaid = effectiveTier !== 'BASIC'; const aiUsed = quotas?.semantic_search?.used ?? 0; const aiLimit = quotas?.semantic_search?.limit ?? 50; const aiPct = aiLimit > 0 ? (aiUsed / aiLimit) * 100 : 0; const plans = [ { id: 'free', name: t('billing.freePlan'), price: t('billing.freePrice') || 'Gratuit', period: '', description: t('billing.freeDescription') || 'Pour découvrir la magie de Momento.', features: [ t('billing.freeF1') || '100 Notes max', t('billing.freeF2') || '3 Carnets', t('billing.freeF3') || '50 crédits IA (Lifetime)', t('billing.freeF4') || 'Recherche sémantique', t('billing.freeF5') || 'Historique 7 jours', ], current: effectiveTier === 'BASIC', buttonText: effectiveTier === 'BASIC' ? (t('billing.currentPlan') || 'Plan Actuel') : t('billing.startCheckout'), buttonClass: effectiveTier === 'BASIC' ? 'bg-paper text-concrete cursor-default' : 'bg-ink text-white shadow-xl shadow-ink/20 hover:scale-[1.02] active:scale-95', onClick: () => {}, }, { id: 'pro', name: t('billing.proPlan'), price: interval === 'month' ? (t('billing.proPrice') || '9,90€') : (t('billing.proAnnualPrice') || '99€'), period: interval === 'month' ? '/mois' : '/an', description: t('billing.proDescription') || 'Pour les consultants et créateurs exigeants.', features: [ t('billing.proFeature1') || 'Notes illimitées', t('billing.proFeature2') || 'BYOK (OpenAI/Anthropic)', t('billing.proFeature3') || '200 recherches sémantiques', t('billing.proFeature4') || 'Agents (12 runs/mois)', t('billing.proFeature5') || 'Historique 30 jours', t('billing.proFeature6') || 'Support Email', ], current: effectiveTier === 'PRO', popular: true, buttonText: effectiveTier === 'PRO' ? (t('billing.currentPlan') || 'Plan Actuel') : (t('billing.proCta') || 'Passer au Plan Pro'), buttonClass: effectiveTier === 'PRO' ? 'bg-paper text-concrete cursor-default' : 'bg-brand-accent text-white shadow-xl shadow-brand-accent/20 hover:scale-[1.02] active:scale-95', onClick: () => handleCheckout('PRO'), }, { id: 'business', name: t('billing.businessPlan'), price: interval === 'month' ? (t('billing.businessPrice') || '29,90€') : (t('billing.businessAnnualPrice') || '299€'), period: interval === 'month' ? '/mois' : '/an', description: t('billing.businessDescription') || 'Pour les équipes et chefs de produit.', features: [ t('billing.businessFeature1') || '10 Collaborateurs inclus', t('billing.businessFeature2') || 'BYOK (13 fournisseurs)', t('billing.businessFeature3') || '1000 recherches sémantiques', t('billing.businessFeature4') || 'Agents (60 runs/mois)', t('billing.businessFeature5') || 'Brainstorm illimité', t('billing.businessFeature6') || 'Accès API', ], current: effectiveTier === 'BUSINESS', buttonText: effectiveTier === 'BUSINESS' ? (t('billing.currentPlan') || 'Plan Actuel') : (t('billing.businessCta') || 'Choisir Plan Business'), buttonClass: effectiveTier === 'BUSINESS' ? 'bg-paper text-concrete cursor-default' : 'bg-ink text-white shadow-xl shadow-ink/20 hover:scale-[1.02] active:scale-95', onClick: () => handleCheckout('BUSINESS'), }, { id: 'enterprise', name: t('billing.enterpriseTitle') || 'Enterprise', price: t('billing.contactSales') || 'Sur devis', period: '', description: t('billing.enterpriseDescription') || 'Quotas personnalisés, SSO, support prioritaire.', features: [ t('billing.enterpriseFeature1') || 'Quotas illimités', t('billing.enterpriseFeature2') || 'SSO / SAML', t('billing.enterpriseFeature3') || 'Support dédié', t('billing.enterpriseFeature4') || 'Facturation personnalisée', t('billing.enterpriseFeature5') || 'SLA garanti', ], current: effectiveTier === 'ENTERPRISE', buttonText: effectiveTier === 'ENTERPRISE' ? (t('billing.currentPlan') || 'Plan Actuel') : (t('billing.contactSales') || 'Contact Sales'), buttonClass: effectiveTier === 'ENTERPRISE' ? 'bg-paper text-concrete cursor-default' : 'bg-ink text-white shadow-xl shadow-ink/20 hover:scale-[1.02] active:scale-95', onClick: () => { window.location.href = 'mailto:sales@memento-note.com'; }, }, ]; return ( {/* Success Banner */} {successBanner && (

{t('billing.checkoutSuccessTitle')}

{successBanner}

)}

{t('billing.subtitle') || 'Gérer votre abonnement et votre facturation'}

{/* Usage Overview */}

{t('billing.currentUsage') || 'Utilisation actuelle'}

{t('billing.currentPeriod') || 'Période en cours'}

{effectiveTier === 'BASIC' ? 'Discovery' : effectiveTier}
{!quotas && (
)} {quotas && Object.entries(quotas).filter(([_, q]) => q.limit > 0).length === 0 && (

Aucune donnée d'utilisation disponible

)} {quotas && Object.entries(quotas).filter(([_, q]) => q.limit > 0).map(([key, q]) => { const pct = q.limit > 0 ? (q.used / q.limit) * 100 : 0 const featureLabels: Record = { semantic_search: t('usageMeter.featureSearch'), auto_tag: t('usageMeter.featureTags'), auto_title: t('usageMeter.featureTitles'), reformulate: t('usageMeter.featureReformulate'), chat: t('usageMeter.featureChat'), brainstorm_create: t('usageMeter.featureBrainstormCreate'), brainstorm_expand: t('usageMeter.featureBrainstormExpand'), brainstorm_enrich: t('usageMeter.featureBrainstormEnrich'), } return (
{featureLabels[key] || key}
{q.remaining === Infinity ? '∞' : q.remaining} / {q.limit === Infinity ? '∞' : q.limit}
= 90 ? 'bg-rose-500' : pct >= 70 ? 'bg-amber-500' : 'bg-brand-accent')} style={{ width: `${Math.min(pct, 100)}%` }} />
) })}

{t('billing.billing') || 'Facturation'}

{t('billing.renewal') || 'Renouvellement'}

{isPaid ? t('billing.paidPlanDesc') || 'Votre abonnement se renouvelle automatiquement.' : t('billing.freePlanDesc') || 'Votre plan gratuit n\'expire jamais. Passez à la vitesse supérieure pour débloquer toute la puissance de Momento.'}

{isPaid && ( )}
{t('billing.currentPlan')} {effectiveTier === 'BASIC' ? 'GRATUIT' : effectiveTier}
{/* Interval Toggle */} {billingEnabled && effectiveTier === 'BASIC' && (
)} {/* Plan Cards */} {(billingEnabled || true) && (
{plans.map((plan) => (
{plan.popular && (
{t('billing.recommended') || 'Recommandé'}
)}

{plan.name}

{plan.price} {plan.period}

{plan.description}

{plan.features.map((feature, i) => (
{feature}
))}
))}
)} {/* Footer Info */}
{t('billing.secureTransactions') || 'Transactions sécurisées'}

{t('billing.secureDesc') || 'Paiement via Stripe. Annulez à tout moment, sans engagement.'}

{t('billing.instantActivation') || 'Activation instantanée'}
{t('billing.satisfactionGuarantee') || 'Garantie satisfait'}
{/* Embedded Checkout Modal */} {isCheckoutOpen && checkoutClientSecret && (

{t('billing.upgradeTitle')}

)} ); }