/** * useSubscription Hook — state-machine-aligned subscription management. * * Exposes the operative subscription, any scheduled successor, available plans, * and ID-based mutation functions (activate, cancel, reactivate). */ import { useState, useEffect, useCallback } from 'react'; import { useApiRequest } from './useApi'; import { fetchSelectablePlans, fetchSubscriptionStatus, activatePlan as activatePlanApi, cancelSubscription as cancelSubscriptionApi, reactivateSubscription as reactivateSubscriptionApi, verifyCheckout as verifyCheckoutApi, type SubscriptionPlan, type MandateSubscription, type SubscriptionStatusResponse, type SubscriptionUsage, } from '../api/subscriptionApi'; export interface UseSubscriptionReturn { plans: SubscriptionPlan[]; subscription: MandateSubscription | null; plan: SubscriptionPlan | null; scheduled: MandateSubscription | null; usage: SubscriptionUsage | null; active: boolean; loading: boolean; error: string | null; loadPlans: () => Promise; loadStatus: () => Promise; activatePlan: (planKey: string) => Promise; cancelSubscription: (subscriptionId: string) => Promise; reactivateSubscription: (subscriptionId: string) => Promise; verifyCheckout: (sessionId: string) => Promise<{ status: string; message: string }>; refetch: () => Promise; } export function useSubscription(mandateId?: string): UseSubscriptionReturn { const [plans, setPlans] = useState([]); const [subscription, setSubscription] = useState(null); const [plan, setPlan] = useState(null); const [scheduled, setScheduled] = useState(null); const [usage, setUsage] = useState(null); const [active, setActive] = useState(false); const { request, isLoading: loading, error: apiError, clearCache } = useApiRequest(); const [error, setError] = useState(null); const loadPlans = useCallback(async () => { clearCache('/api/subscription/plans', 'get'); try { const data = await fetchSelectablePlans(request, mandateId); setPlans(Array.isArray(data) ? data : []); } catch (err) { console.error('Error loading plans:', err); setPlans([]); } }, [request, mandateId, clearCache]); const loadStatus = useCallback(async () => { clearCache('/api/subscription/status', 'get'); try { const data: SubscriptionStatusResponse = await fetchSubscriptionStatus(request, mandateId); setActive(data.active); setSubscription(data.subscription ?? null); setPlan(data.plan ?? null); setScheduled(data.scheduled ?? null); setUsage(data.usage ?? null); } catch (err) { console.error('Error loading subscription status:', err); setActive(false); setSubscription(null); setPlan(null); setScheduled(null); setUsage(null); } }, [request, mandateId, clearCache]); const activatePlan = useCallback(async (planKey: string) => { try { setError(null); const currentUrl = new URL(window.location.href); currentUrl.searchParams.delete('success'); currentUrl.searchParams.delete('canceled'); currentUrl.searchParams.delete('session_id'); currentUrl.searchParams.set('tab', 'subscription'); if (mandateId) currentUrl.searchParams.set('mandate', mandateId); const returnUrl = `${currentUrl.origin}${currentUrl.pathname}${currentUrl.search}`; const result = await activatePlanApi(request, planKey, mandateId, returnUrl); if (result?.redirectUrl) { window.location.href = result.redirectUrl; return; } await loadStatus(); } catch (err: any) { const msg = err?.response?.data?.detail || err.message || 'Fehler beim Aktivieren'; setError(msg); throw err; } }, [request, mandateId, loadStatus]); const cancelSub = useCallback(async (subscriptionId: string) => { try { setError(null); await cancelSubscriptionApi(request, subscriptionId, mandateId); await loadStatus(); } catch (err: any) { const msg = err?.response?.data?.detail || err.message || 'Fehler beim Kündigen'; setError(msg); throw err; } }, [request, mandateId, loadStatus]); const reactivateSub = useCallback(async (subscriptionId: string) => { try { setError(null); await reactivateSubscriptionApi(request, subscriptionId, mandateId); await loadStatus(); } catch (err: any) { const msg = err?.response?.data?.detail || err.message || 'Fehler beim Reaktivieren'; setError(msg); throw err; } }, [request, mandateId, loadStatus]); const verifyCheckout = useCallback(async (sessionId: string) => { const result = await verifyCheckoutApi(request, sessionId, mandateId); await loadStatus(); return result; }, [request, mandateId, loadStatus]); const refetch = useCallback(async () => { await Promise.all([loadPlans(), loadStatus()]); }, [loadPlans, loadStatus]); useEffect(() => { if (mandateId) { loadPlans(); loadStatus(); } else { setPlans([]); setSubscription(null); setPlan(null); setScheduled(null); setUsage(null); setActive(false); } }, [mandateId]); return { plans, subscription, plan, scheduled, usage, active, loading, error: error || (apiError ? String(apiError) : null), loadPlans, loadStatus, activatePlan, cancelSubscription: cancelSub, reactivateSubscription: reactivateSub, verifyCheckout, refetch, }; }