Просмотр исходного кода

feat(billing): add subscription plans and usage tracking

- Add 4 subscription plans: Free Trial (10 min/14 days), Starter (40 min),
  Growth (130 min), Scale (350 min) with multi-currency pricing (HUF/EUR/USD)
- Add additional minute packages (50/100/250 min) with auto-purchase option
- Create database schema with subscription_plans, store_subscriptions,
  package_purchases, billing_history tables
- Implement Supabase Realtime subscription for live usage updates
- Add usage display in sidebar footer (progress bar, minutes, expiration)
- Rewrite Billing page with plan selection grid and package purchase UI
- Add i18n translations for billing (en, hu, de)
- Auto-assign free trial to new stores via database trigger

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fszontagh 4 месяцев назад
Родитель
Сommit
baafce3754

+ 80 - 2
shopcall.ai-main/src/components/AppSidebar.tsx

@@ -15,13 +15,15 @@ import {
   SidebarFooter,
   SidebarFooter,
 } from "@/components/ui/sidebar";
 } from "@/components/ui/sidebar";
 import { useNavigate, useSearchParams } from "react-router-dom";
 import { useNavigate, useSearchParams } from "react-router-dom";
-import { LayoutDashboard, Phone, BarChart3, Settings, CreditCard, Layers3, PhoneCall, LogOut, Brain, Database, Store, ChevronDown, Package, Globe, FileText } from "lucide-react";
+import { LayoutDashboard, Phone, BarChart3, Settings, CreditCard, Layers3, PhoneCall, LogOut, Brain, Database, Store, ChevronDown, Package, Globe, FileText, Clock, Zap, AlertTriangle } from "lucide-react";
 import { useAuth } from "@/components/context/AuthContext";
 import { useAuth } from "@/components/context/AuthContext";
 import { useShop } from "@/components/context/ShopContext";
 import { useShop } from "@/components/context/ShopContext";
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import { LanguageSelector } from "./LanguageSelector";
 import { LanguageSelector } from "./LanguageSelector";
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
 import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
 import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
+import { useSubscription } from "@/hooks/useSubscription";
+import { getUsageProgressColor } from "@/types/subscription.types";
 
 
 export function AppSidebar() {
 export function AppSidebar() {
   const { t } = useTranslation();
   const { t } = useTranslation();
@@ -30,6 +32,18 @@ export function AppSidebar() {
   const currentPath = window.location.pathname;
   const currentPath = window.location.pathname;
   const { logout } = useAuth();
   const { logout } = useAuth();
   const { selectedShop, setSelectedShop, stores } = useShop();
   const { selectedShop, setSelectedShop, stores } = useShop();
+
+  // Subscription data for usage display
+  const {
+    subscription,
+    isLoading: subLoading,
+    minutesRemaining,
+    totalMinutesRemaining,
+    usagePercentage,
+    isExpired,
+    daysRemaining
+  } = useSubscription(selectedShop?.id);
+
   // Keep AI menu open when on any AI submenu page
   // Keep AI menu open when on any AI submenu page
   const aiMenuPaths = ['/ai-config', '/products', '/website-content', '/custom-content'];
   const aiMenuPaths = ['/ai-config', '/products', '/website-content', '/custom-content'];
   const [isAIMenuOpen, setIsAIMenuOpen] = useState(aiMenuPaths.includes(currentPath));
   const [isAIMenuOpen, setIsAIMenuOpen] = useState(aiMenuPaths.includes(currentPath));
@@ -315,11 +329,75 @@ export function AppSidebar() {
         </SidebarGroup>
         </SidebarGroup>
       </SidebarContent>
       </SidebarContent>
 
 
-      <SidebarFooter className="p-6 bg-slate-900">
+      <SidebarFooter className="p-4 bg-slate-900">
         <div className="flex flex-col gap-2">
         <div className="flex flex-col gap-2">
           <div className="flex justify-center mb-2">
           <div className="flex justify-center mb-2">
             <LanguageSelector />
             <LanguageSelector />
           </div>
           </div>
+
+          {/* Subscription Usage Display */}
+          {selectedShop && subscription && !subLoading && (
+            <button
+              onClick={() => navigate('/billing')}
+              className={`p-3 w-full rounded-lg transition-colors cursor-pointer ${
+                currentPath === '/billing'
+                  ? 'bg-cyan-500/20 border border-cyan-500/30'
+                  : 'bg-slate-800/50 hover:bg-slate-800 border border-transparent'
+              } ${isExpired ? 'border-red-500/50' : ''}`}
+            >
+              <div className="flex items-center gap-2 mb-2">
+                {subscription.status === 'trial' ? (
+                  <Zap className="w-4 h-4 text-yellow-400" />
+                ) : (
+                  <CreditCard className="w-4 h-4 text-cyan-400" />
+                )}
+                <span className="text-sm font-medium text-white truncate">
+                  {subscription.plan_translation?.name || subscription.plan?.plan_code || t('sidebar.subscription')}
+                </span>
+                {isExpired && (
+                  <AlertTriangle className="w-3 h-3 text-red-400 ml-auto" />
+                )}
+              </div>
+
+              {/* Minutes usage */}
+              <div className="flex items-center justify-between text-xs mb-1">
+                <span className="text-slate-400">{t('sidebar.minutesUsed')}</span>
+                <span className={`font-medium ${
+                  usagePercentage >= 90 ? 'text-red-400' :
+                  usagePercentage >= 75 ? 'text-yellow-400' :
+                  'text-slate-300'
+                }`}>
+                  {Math.round(subscription.minutes_used)} / {subscription.minutes_included} {t('sidebar.min')}
+                </span>
+              </div>
+
+              {/* Progress bar */}
+              <div className="h-1.5 bg-slate-700 rounded-full overflow-hidden mb-2">
+                <div
+                  className={`h-full transition-all duration-300 ${getUsageProgressColor(usagePercentage)}`}
+                  style={{ width: `${Math.min(usagePercentage, 100)}%` }}
+                />
+              </div>
+
+              {/* Expiration date */}
+              <div className="flex items-center gap-1 text-xs text-slate-500">
+                <Clock className="w-3 h-3" />
+                <span>
+                  {subscription.status === 'trial'
+                    ? `${daysRemaining} ${t('sidebar.daysLeft')}`
+                    : new Date(subscription.period_end).toLocaleDateString('hu-HU', {
+                        year: 'numeric',
+                        month: '2-digit',
+                        day: '2-digit',
+                        hour: '2-digit',
+                        minute: '2-digit'
+                      })
+                  }
+                </span>
+              </div>
+            </button>
+          )}
+
           <button
           <button
             onClick={() => navigate('/settings')}
             onClick={() => navigate('/settings')}
             className={`flex items-center gap-3 p-3 w-full rounded-lg hover:bg-slate-800 transition-colors cursor-pointer ${
             className={`flex items-center gap-3 p-3 w-full rounded-lg hover:bg-slate-800 transition-colors cursor-pointer ${

+ 4 - 2
shopcall.ai-main/src/components/OnboardingContent.tsx

@@ -140,8 +140,10 @@ export function OnboardingContent() {
       selectedPhone,
       selectedPhone,
       selectedPackage
       selectedPackage
     });
     });
-    // Redirect to dashboard or show success
-    window.location.href = "/";
+    // Note: Free trial is automatically assigned when store is created via database trigger
+    // User can upgrade their plan from the billing page
+    // Redirect to dashboard
+    window.location.href = "/dashboard";
   };
   };
 
 
   return (
   return (

+ 267 - 0
shopcall.ai-main/src/hooks/usePlans.ts

@@ -0,0 +1,267 @@
+import { useState, useEffect, useCallback } from 'react';
+import { supabase } from '@/lib/supabase';
+import { useTranslation } from 'react-i18next';
+import type {
+  SubscriptionPlan,
+  SubscriptionPlanTranslation,
+  SubscriptionPlanFeature,
+  SubscriptionFeatureTranslation,
+  AdditionalPackage,
+  AdditionalPackageTranslation,
+  PlanWithTranslation,
+  FeatureWithTranslation,
+  PackageWithTranslation
+} from '@/types/subscription.types';
+
+interface UsePlansReturn {
+  plans: PlanWithTranslation[];
+  isLoading: boolean;
+  error: string | null;
+  refetch: () => Promise<void>;
+}
+
+export function usePlans(): UsePlansReturn {
+  const { i18n } = useTranslation();
+  const [plans, setPlans] = useState<PlanWithTranslation[]>([]);
+  const [isLoading, setIsLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);
+
+  const currentLanguage = i18n.language?.split('-')[0] || 'en';
+
+  const fetchPlans = useCallback(async () => {
+    try {
+      setIsLoading(true);
+      setError(null);
+
+      // Fetch all active plans
+      const { data: plansData, error: plansError } = await supabase
+        .from('subscription_plans')
+        .select('*')
+        .eq('is_active', true)
+        .order('sort_order');
+
+      if (plansError) throw plansError;
+
+      if (!plansData || plansData.length === 0) {
+        setPlans([]);
+        return;
+      }
+
+      // Fetch translations for all plans
+      const planIds = plansData.map(p => p.id);
+      const { data: translationsData } = await supabase
+        .from('subscription_plan_translations')
+        .select('*')
+        .in('plan_id', planIds)
+        .eq('language_code', currentLanguage);
+
+      // Fetch features for all plans
+      const { data: featuresData } = await supabase
+        .from('subscription_plan_features')
+        .select('*')
+        .in('plan_id', planIds)
+        .order('sort_order');
+
+      // Fetch feature translations
+      const featureIds = featuresData?.map(f => f.id) || [];
+      const { data: featureTranslationsData } = await supabase
+        .from('subscription_feature_translations')
+        .select('*')
+        .in('feature_id', featureIds)
+        .eq('language_code', currentLanguage);
+
+      // Combine data
+      const combinedPlans: PlanWithTranslation[] = plansData.map(plan => {
+        const translation = translationsData?.find(t => t.plan_id === plan.id);
+        const features = featuresData?.filter(f => f.plan_id === plan.id) || [];
+        const featuresWithTranslations: FeatureWithTranslation[] = features.map(feature => {
+          const featureTranslation = featureTranslationsData?.find(
+            ft => ft.feature_id === feature.id
+          );
+          return {
+            ...feature,
+            translations: featureTranslation || {
+              id: '',
+              feature_id: feature.id,
+              language_code: currentLanguage,
+              label: feature.feature_code,
+              created_at: ''
+            }
+          };
+        });
+
+        return {
+          ...plan,
+          translations: translation || {
+            id: '',
+            plan_id: plan.id,
+            language_code: currentLanguage,
+            name: plan.plan_code,
+            description: null,
+            period_label: null,
+            overage_label: null,
+            badge_text: null,
+            created_at: '',
+            updated_at: ''
+          },
+          features: featuresWithTranslations
+        };
+      });
+
+      setPlans(combinedPlans);
+    } catch (err) {
+      console.error('Error fetching plans:', err);
+      setError(err instanceof Error ? err.message : 'Failed to fetch plans');
+    } finally {
+      setIsLoading(false);
+    }
+  }, [currentLanguage]);
+
+  useEffect(() => {
+    fetchPlans();
+  }, [fetchPlans]);
+
+  return {
+    plans,
+    isLoading,
+    error,
+    refetch: fetchPlans
+  };
+}
+
+// Hook for fetching additional packages
+interface UsePackagesReturn {
+  packages: PackageWithTranslation[];
+  isLoading: boolean;
+  error: string | null;
+  purchasePackage: (storeId: string, subscriptionId: string, packageId: string, pkg: AdditionalPackage, currency: string) => Promise<boolean>;
+  refetch: () => Promise<void>;
+}
+
+export function usePackages(): UsePackagesReturn {
+  const { i18n } = useTranslation();
+  const [packages, setPackages] = useState<PackageWithTranslation[]>([]);
+  const [isLoading, setIsLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);
+
+  const currentLanguage = i18n.language?.split('-')[0] || 'en';
+
+  const fetchPackages = useCallback(async () => {
+    try {
+      setIsLoading(true);
+      setError(null);
+
+      // Fetch all active packages
+      const { data: packagesData, error: packagesError } = await supabase
+        .from('additional_packages')
+        .select('*')
+        .eq('is_active', true)
+        .order('sort_order');
+
+      if (packagesError) throw packagesError;
+
+      if (!packagesData || packagesData.length === 0) {
+        setPackages([]);
+        return;
+      }
+
+      // Fetch translations
+      const packageIds = packagesData.map(p => p.id);
+      const { data: translationsData } = await supabase
+        .from('additional_package_translations')
+        .select('*')
+        .in('package_id', packageIds)
+        .eq('language_code', currentLanguage);
+
+      // Combine data
+      const combinedPackages: PackageWithTranslation[] = packagesData.map(pkg => {
+        const translation = translationsData?.find(t => t.package_id === pkg.id);
+        return {
+          ...pkg,
+          translations: translation || {
+            id: '',
+            package_id: pkg.id,
+            language_code: currentLanguage,
+            name: `${pkg.minutes} Minutes`,
+            description: null,
+            created_at: ''
+          }
+        };
+      });
+
+      setPackages(combinedPackages);
+    } catch (err) {
+      console.error('Error fetching packages:', err);
+      setError(err instanceof Error ? err.message : 'Failed to fetch packages');
+    } finally {
+      setIsLoading(false);
+    }
+  }, [currentLanguage]);
+
+  const purchasePackage = async (
+    storeId: string,
+    subscriptionId: string,
+    packageId: string,
+    pkg: AdditionalPackage,
+    currency: string
+  ): Promise<boolean> => {
+    try {
+      // Get subscription end date for package expiry
+      const { data: subData } = await supabase
+        .from('store_subscriptions')
+        .select('period_end')
+        .eq('id', subscriptionId)
+        .single();
+
+      const price = currency === 'EUR' ? pkg.price_eur :
+                    currency === 'USD' ? pkg.price_usd :
+                    pkg.price_huf;
+
+      // Insert package purchase
+      const { error: purchaseError } = await supabase
+        .from('package_purchases')
+        .insert({
+          store_id: storeId,
+          subscription_id: subscriptionId,
+          package_id: packageId,
+          minutes_purchased: pkg.minutes,
+          minutes_remaining: pkg.minutes,
+          currency: currency,
+          price_paid: price,
+          is_auto_purchase: false,
+          expires_at: subData?.period_end || new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
+          is_active: true
+        });
+
+      if (purchaseError) throw purchaseError;
+
+      // Log billing history
+      await supabase.from('billing_history').insert({
+        store_id: storeId,
+        subscription_id: subscriptionId,
+        event_type: 'package_purchased',
+        currency: currency,
+        amount: price,
+        package_id: packageId,
+        description: `Purchased ${pkg.minutes} minutes package`
+      });
+
+      return true;
+    } catch (err) {
+      console.error('Error purchasing package:', err);
+      return false;
+    }
+  };
+
+  useEffect(() => {
+    fetchPackages();
+  }, [fetchPackages]);
+
+  return {
+    packages,
+    isLoading,
+    error,
+    purchasePackage,
+    refetch: fetchPackages
+  };
+}

+ 309 - 0
shopcall.ai-main/src/hooks/useSubscription.ts

@@ -0,0 +1,309 @@
+import { useState, useEffect, useCallback } from 'react';
+import { supabase } from '@/lib/supabase';
+import { useTranslation } from 'react-i18next';
+import type {
+  StoreSubscription,
+  SubscriptionPlan,
+  SubscriptionPlanTranslation,
+  PackagePurchase,
+  SubscriptionWithPlan,
+  calculateMinutesRemaining,
+  isSubscriptionExpired,
+  getUsagePercentage
+} from '@/types/subscription.types';
+
+interface UseSubscriptionReturn {
+  subscription: SubscriptionWithPlan | null;
+  isLoading: boolean;
+  error: string | null;
+  minutesRemaining: number;
+  packageMinutesRemaining: number;
+  totalMinutesRemaining: number;
+  usagePercentage: number;
+  isExpired: boolean;
+  daysRemaining: number;
+  refetch: () => Promise<void>;
+}
+
+export function useSubscription(storeId: string | undefined): UseSubscriptionReturn {
+  const { i18n } = useTranslation();
+  const [subscription, setSubscription] = useState<SubscriptionWithPlan | null>(null);
+  const [activePackages, setActivePackages] = useState<PackagePurchase[]>([]);
+  const [isLoading, setIsLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);
+
+  const currentLanguage = i18n.language?.split('-')[0] || 'en';
+
+  // Fetch subscription data
+  const fetchSubscription = useCallback(async () => {
+    if (!storeId) {
+      setSubscription(null);
+      setActivePackages([]);
+      setIsLoading(false);
+      return;
+    }
+
+    try {
+      setIsLoading(true);
+      setError(null);
+
+      // Fetch subscription with plan data
+      const { data: subData, error: subError } = await supabase
+        .from('store_subscriptions')
+        .select(`
+          *,
+          plan:subscription_plans(*)
+        `)
+        .eq('store_id', storeId)
+        .single();
+
+      if (subError) {
+        if (subError.code === 'PGRST116') {
+          // No subscription found
+          setSubscription(null);
+          setActivePackages([]);
+          return;
+        }
+        throw subError;
+      }
+
+      // Fetch plan translation
+      let planTranslation: SubscriptionPlanTranslation | undefined;
+      if (subData?.plan?.id) {
+        const { data: transData } = await supabase
+          .from('subscription_plan_translations')
+          .select('*')
+          .eq('plan_id', subData.plan.id)
+          .eq('language_code', currentLanguage)
+          .single();
+
+        if (transData) {
+          planTranslation = transData;
+        }
+      }
+
+      // Fetch active packages
+      const { data: packagesData } = await supabase
+        .from('package_purchases')
+        .select('*')
+        .eq('store_id', storeId)
+        .eq('is_active', true)
+        .gt('expires_at', new Date().toISOString());
+
+      setSubscription({
+        ...subData,
+        plan_translation: planTranslation
+      } as SubscriptionWithPlan);
+
+      setActivePackages(packagesData || []);
+    } catch (err) {
+      console.error('Error fetching subscription:', err);
+      setError(err instanceof Error ? err.message : 'Failed to fetch subscription');
+    } finally {
+      setIsLoading(false);
+    }
+  }, [storeId, currentLanguage]);
+
+  // Initial fetch
+  useEffect(() => {
+    fetchSubscription();
+  }, [fetchSubscription]);
+
+  // Realtime subscription for live updates
+  useEffect(() => {
+    if (!storeId) return;
+
+    // Subscribe to store_subscriptions changes
+    const channel = supabase
+      .channel(`subscription:${storeId}`)
+      .on(
+        'postgres_changes',
+        {
+          event: '*',
+          schema: 'public',
+          table: 'store_subscriptions',
+          filter: `store_id=eq.${storeId}`
+        },
+        (payload) => {
+          console.log('Subscription update received:', payload);
+          // Refetch to get full data with joins
+          fetchSubscription();
+        }
+      )
+      .on(
+        'postgres_changes',
+        {
+          event: '*',
+          schema: 'public',
+          table: 'package_purchases',
+          filter: `store_id=eq.${storeId}`
+        },
+        (payload) => {
+          console.log('Package purchase update received:', payload);
+          // Refetch packages
+          fetchSubscription();
+        }
+      )
+      .subscribe();
+
+    return () => {
+      supabase.removeChannel(channel);
+    };
+  }, [storeId, fetchSubscription]);
+
+  // Calculate derived values
+  const minutesRemaining = subscription
+    ? Math.max(subscription.minutes_included - subscription.minutes_used, 0)
+    : 0;
+
+  const packageMinutesRemaining = activePackages.reduce(
+    (sum, pkg) => sum + pkg.minutes_remaining,
+    0
+  );
+
+  const totalMinutesRemaining = minutesRemaining + packageMinutesRemaining;
+
+  const usagePercentage = subscription
+    ? Math.min((subscription.minutes_used / subscription.minutes_included) * 100, 100)
+    : 0;
+
+  const isExpired = subscription ? (() => {
+    const now = new Date();
+    const periodEnd = new Date(subscription.period_end);
+
+    // Time expired
+    if (now > periodEnd) return true;
+
+    // For trial, also check minutes
+    if (subscription.status === 'trial' && minutesRemaining <= 0) {
+      return true;
+    }
+
+    return false;
+  })() : false;
+
+  const daysRemaining = subscription
+    ? Math.max(
+        Math.ceil(
+          (new Date(subscription.period_end).getTime() - Date.now()) / (1000 * 60 * 60 * 24)
+        ),
+        0
+      )
+    : 0;
+
+  return {
+    subscription,
+    isLoading,
+    error,
+    minutesRemaining,
+    packageMinutesRemaining,
+    totalMinutesRemaining,
+    usagePercentage,
+    isExpired,
+    daysRemaining,
+    refetch: fetchSubscription
+  };
+}
+
+// Hook for updating subscription (changing plans)
+export function useUpdateSubscription() {
+  const [isUpdating, setIsUpdating] = useState(false);
+  const [error, setError] = useState<string | null>(null);
+
+  const changePlan = async (
+    storeId: string,
+    newPlanId: string,
+    newPlan: SubscriptionPlan
+  ): Promise<boolean> => {
+    try {
+      setIsUpdating(true);
+      setError(null);
+
+      // Get current subscription
+      const { data: currentSub } = await supabase
+        .from('store_subscriptions')
+        .select('*')
+        .eq('store_id', storeId)
+        .single();
+
+      const now = new Date();
+      const periodEnd = new Date(now);
+      periodEnd.setMonth(periodEnd.getMonth() + 1);
+
+      // Update subscription
+      const { error: updateError } = await supabase
+        .from('store_subscriptions')
+        .update({
+          plan_id: newPlanId,
+          status: newPlan.is_trial ? 'trial' : 'active',
+          billing_period: newPlan.is_trial ? 'trial' : 'monthly',
+          period_start: now.toISOString(),
+          period_end: newPlan.is_trial
+            ? new Date(now.getTime() + (newPlan.trial_days || 14) * 24 * 60 * 60 * 1000).toISOString()
+            : periodEnd.toISOString(),
+          minutes_included: newPlan.minutes_included,
+          minutes_used: 0 // Reset usage for new period
+        })
+        .eq('store_id', storeId);
+
+      if (updateError) throw updateError;
+
+      // Log billing history
+      await supabase.from('billing_history').insert({
+        store_id: storeId,
+        subscription_id: currentSub?.id,
+        event_type: 'plan_changed',
+        currency: currentSub?.currency || 'HUF',
+        amount: 0, // UI only, no payment
+        plan_id: newPlanId,
+        description: `Changed plan to ${newPlan.plan_code}`
+      });
+
+      return true;
+    } catch (err) {
+      console.error('Error changing plan:', err);
+      setError(err instanceof Error ? err.message : 'Failed to change plan');
+      return false;
+    } finally {
+      setIsUpdating(false);
+    }
+  };
+
+  const updateAutoPurchase = async (
+    storeId: string,
+    enabled: boolean,
+    packageId?: string,
+    threshold?: number
+  ): Promise<boolean> => {
+    try {
+      setIsUpdating(true);
+      setError(null);
+
+      const { error: updateError } = await supabase
+        .from('store_subscriptions')
+        .update({
+          auto_purchase_enabled: enabled,
+          auto_purchase_package_id: packageId || null,
+          auto_purchase_threshold: threshold || 5
+        })
+        .eq('store_id', storeId);
+
+      if (updateError) throw updateError;
+
+      return true;
+    } catch (err) {
+      console.error('Error updating auto-purchase:', err);
+      setError(err instanceof Error ? err.message : 'Failed to update auto-purchase');
+      return false;
+    } finally {
+      setIsUpdating(false);
+    }
+  };
+
+  return {
+    changePlan,
+    updateAutoPurchase,
+    isUpdating,
+    error
+  };
+}

+ 26 - 14
shopcall.ai-main/src/i18n/locales/de.json

@@ -234,7 +234,11 @@
     "products": "Produkte",
     "products": "Produkte",
     "websiteContent": "Website-Inhalte",
     "websiteContent": "Website-Inhalte",
     "customContent": "Benutzerdefinierter Inhalt",
     "customContent": "Benutzerdefinierter Inhalt",
-    "inactive": "Inaktiv"
+    "inactive": "Inaktiv",
+    "subscription": "Abonnement",
+    "minutesUsed": "Verbrauchte Minuten",
+    "min": "Min",
+    "daysLeft": "Tage übrig"
   },
   },
   "settings": {
   "settings": {
     "title": "Kontoeinstellungen",
     "title": "Kontoeinstellungen",
@@ -1822,19 +1826,27 @@
   },
   },
   "billing": {
   "billing": {
     "title": "Abrechnung & Tarif",
     "title": "Abrechnung & Tarif",
-    "subtitle": "Verwalten Sie Ihr Abonnement und Ihre Rechnungsdetails",
-    "comingSoon": {
-      "badge": "Demnächst",
-      "title": "Abrechnungsfunktionen sind unterwegs",
-      "description": "Wir arbeiten hart daran, Ihnen ein nahtloses Abrechnungserlebnis zu bieten. Bleiben Sie dran für Abonnementverwaltung, Rechnungen und mehr.",
-      "features": {
-        "flexiblePlans": "Flexible Tarife",
-        "specialOffers": "Sonderangebote",
-        "easyPayments": "Einfache Zahlungen"
-      },
-      "notifyButton": "Benachrichtigen",
-      "note": "Wir informieren Sie, sobald die Abrechnungsfunktionen verfügbar sind."
-    }
+    "chooseYourPlan": "Wählen Sie Ihren Tarif",
+    "subtitle": "Starten Sie mit einer 14-tägigen kostenlosen Testversion oder wählen Sie ein Abonnement.",
+    "noShopSelected": "Kein Shop ausgewählt",
+    "selectShopFirst": "Bitte wählen Sie einen Shop aus der Seitenleiste, um sein Abonnement zu verwalten.",
+    "currentPlan": "Aktueller Tarif",
+    "selectPlan": "Tarif auswählen",
+    "additionalPackages": "Zusätzliche Minutenpakete",
+    "additionalPackagesDescription": "Benötigen Sie mehr Minuten? Kaufen Sie zusätzliche Pakete, um Ihr monatliches Kontingent zu erweitern.",
+    "purchasePackage": "Kaufen",
+    "autoPurchase": "Automatischer Kauf",
+    "autoPurchaseDescription": "Kaufen Sie automatisch zusätzliche Minuten, wenn Ihr Guthaben niedrig ist.",
+    "autoPurchasePackage": "Zu kaufendes Paket",
+    "autoPurchaseThreshold": "Kaufen wenn verbleibend",
+    "minutesRemaining": "Minuten verbleibend",
+    "howBillingWorks": "So funktioniert die Abrechnung:",
+    "phoneNumberInfo": "Die Telefonnummer:",
+    "phoneNumberInfoDesc": "Jedes Abonnement enthält die 1.000 Ft Kosten für eine dedizierte ungarische Festnetznummer. Wir kümmern uns um den Papierkram.",
+    "trialInfo": "Die Testversion:",
+    "trialInfoDesc": "Um Betrug zu verhindern und die Kosten für die Reservierung Ihrer Telefonnummer zu decken, benötigen wir eine Karte. Sie erhalten 10 Minuten kostenlos. Bei Überschreitung pausiert die KI bis zum Upgrade.",
+    "overageInfo": "Überschreitung:",
+    "overageInfoDesc": "Bei Überschreitung der monatlichen Minuten antworten wir weiter und berechnen zum \"Überschreitungstarif\". Sie können in den Einstellungen ein Limit setzen."
   },
   },
   "crawler": {
   "crawler": {
     "title": "ShopCall.ai Web-Crawler",
     "title": "ShopCall.ai Web-Crawler",

+ 26 - 14
shopcall.ai-main/src/i18n/locales/en.json

@@ -234,7 +234,11 @@
     "products": "Products",
     "products": "Products",
     "websiteContent": "Website Content",
     "websiteContent": "Website Content",
     "customContent": "Custom Content",
     "customContent": "Custom Content",
-    "inactive": "Inactive"
+    "inactive": "Inactive",
+    "subscription": "Subscription",
+    "minutesUsed": "Minutes used",
+    "min": "min",
+    "daysLeft": "days left"
   },
   },
   "settings": {
   "settings": {
     "title": "Account Settings",
     "title": "Account Settings",
@@ -2125,19 +2129,27 @@
   },
   },
   "billing": {
   "billing": {
     "title": "Billing & Plan",
     "title": "Billing & Plan",
-    "subtitle": "Manage your subscription and billing details",
-    "comingSoon": {
-      "badge": "Coming Soon",
-      "title": "Billing Features Are On The Way",
-      "description": "We're working hard to bring you a seamless billing experience. Stay tuned for subscription management, invoices, and more.",
-      "features": {
-        "flexiblePlans": "Flexible Plans",
-        "specialOffers": "Special Offers",
-        "easyPayments": "Easy Payments"
-      },
-      "notifyButton": "Get Notified",
-      "note": "We'll let you know as soon as billing features are available."
-    }
+    "chooseYourPlan": "Choose Your Plan",
+    "subtitle": "Start with a 14-day free trial or pick a subscription.",
+    "noShopSelected": "No Shop Selected",
+    "selectShopFirst": "Please select a shop from the sidebar to manage its subscription.",
+    "currentPlan": "Current Plan",
+    "selectPlan": "Select Plan",
+    "additionalPackages": "Additional Minutes Packages",
+    "additionalPackagesDescription": "Need more minutes? Purchase additional packages to extend your monthly quota.",
+    "purchasePackage": "Purchase",
+    "autoPurchase": "Auto-Purchase",
+    "autoPurchaseDescription": "Automatically purchase additional minutes when your balance runs low.",
+    "autoPurchasePackage": "Package to purchase",
+    "autoPurchaseThreshold": "Purchase when remaining",
+    "minutesRemaining": "minutes remaining",
+    "howBillingWorks": "How billing works:",
+    "phoneNumberInfo": "The Phone Number:",
+    "phoneNumberInfoDesc": "Every subscription includes the 1,000 Ft cost of a dedicated Hungarian landline. We handle the paperwork.",
+    "trialInfo": "The Trial:",
+    "trialInfoDesc": "To prevent fraud and cover the cost of reserving your phone number, we require a card on file. You get 10 minutes free. If you exceed 10 minutes, the AI pauses until upgrade.",
+    "overageInfo": "Overage:",
+    "overageInfoDesc": "If you exceed monthly minutes, we continue answering and bill at the \"Overage Rate\". You can set a hard cap in settings."
   },
   },
   "crawler": {
   "crawler": {
     "title": "ShopCall.ai Web Crawler",
     "title": "ShopCall.ai Web Crawler",

+ 26 - 14
shopcall.ai-main/src/i18n/locales/hu.json

@@ -234,7 +234,11 @@
     "products": "Termékek",
     "products": "Termékek",
     "websiteContent": "Weboldal Tartalom",
     "websiteContent": "Weboldal Tartalom",
     "customContent": "Egyedi Tartalom",
     "customContent": "Egyedi Tartalom",
-    "inactive": "Inaktív"
+    "inactive": "Inaktív",
+    "subscription": "Előfizetés",
+    "minutesUsed": "Felhasznált percek",
+    "min": "perc",
+    "daysLeft": "nap van hátra"
   },
   },
   "settings": {
   "settings": {
     "title": "Fiókbeállítások",
     "title": "Fiókbeállítások",
@@ -2003,19 +2007,27 @@
   },
   },
   "billing": {
   "billing": {
     "title": "Számlázás & Csomag",
     "title": "Számlázás & Csomag",
-    "subtitle": "Előfizetés és számlázási adatok kezelése",
-    "comingSoon": {
-      "badge": "Hamarosan",
-      "title": "Számlázási Funkciók Úton Vannak",
-      "description": "Keményen dolgozunk, hogy zökkenőmentes számlázási élményt nyújtsunk. Maradjon velünk az előfizetés-kezelésért, számlákért és még többért.",
-      "features": {
-        "flexiblePlans": "Rugalmas Csomagok",
-        "specialOffers": "Különleges Ajánlatok",
-        "easyPayments": "Egyszerű Fizetések"
-      },
-      "notifyButton": "Értesítést Kérek",
-      "note": "Értesítjük, amint a számlázási funkciók elérhetők lesznek."
-    }
+    "chooseYourPlan": "Válassz csomagot",
+    "subtitle": "Kezdd 14 napos ingyenes próbával vagy válassz előfizetést.",
+    "noShopSelected": "Nincs kiválasztott bolt",
+    "selectShopFirst": "Kérjük, válassz egy boltot az oldalsávból az előfizetés kezeléséhez.",
+    "currentPlan": "Jelenlegi csomag",
+    "selectPlan": "Csomag választása",
+    "additionalPackages": "További percek csomagok",
+    "additionalPackagesDescription": "Több percre van szükséged? Vásárolj további csomagokat a havi keretedhez.",
+    "purchasePackage": "Vásárlás",
+    "autoPurchase": "Automatikus vásárlás",
+    "autoPurchaseDescription": "Automatikusan vásárolj további perceket, ha alacsony az egyenleged.",
+    "autoPurchasePackage": "Vásárolandó csomag",
+    "autoPurchaseThreshold": "Vásárlás ha marad",
+    "minutesRemaining": "perc marad",
+    "howBillingWorks": "Hogyan működik a számlázás:",
+    "phoneNumberInfo": "A telefonszám:",
+    "phoneNumberInfoDesc": "Minden előfizetés tartalmazza a dedikált magyar vezetékes szám 1000 Ft költségét. Mi intézzük a papírmunkát.",
+    "trialInfo": "A próbaidőszak:",
+    "trialInfoDesc": "A csalás megelőzése és a telefonszám foglalási költségének fedezése érdekében bankkártyát kérünk. 10 perc ingyenes. Ha túlléped a 10 percet, az AI szünetel a frissítésig.",
+    "overageInfo": "Túlhasználat:",
+    "overageInfoDesc": "Ha túlléped a havi perceket, továbbra is válaszolunk és a \"Túlhasználati díj\" szerint számlázzuk. A beállításokban korlátozhatod."
   },
   },
   "crawler": {
   "crawler": {
     "title": "ShopCall.ai Web Crawler",
     "title": "ShopCall.ai Web Crawler",

+ 457 - 61
shopcall.ai-main/src/pages/Billing.tsx

@@ -1,12 +1,159 @@
+import { useState } from "react";
 import { SidebarProvider } from "@/components/ui/sidebar";
 import { SidebarProvider } from "@/components/ui/sidebar";
 import { AppSidebar } from "@/components/AppSidebar";
 import { AppSidebar } from "@/components/AppSidebar";
-import { Card, CardContent } from "@/components/ui/card";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
 import { Button } from "@/components/ui/button";
 import { Button } from "@/components/ui/button";
-import { CreditCard, Sparkles, Bell, Rocket, Zap, Gift } from "lucide-react";
+import { Badge } from "@/components/ui/badge";
+import { Switch } from "@/components/ui/switch";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
+import { useShop } from "@/components/context/ShopContext";
+import { useSubscription, useUpdateSubscription } from "@/hooks/useSubscription";
+import { usePlans, usePackages } from "@/hooks/usePlans";
+import {
+  Zap,
+  Star,
+  TrendingUp,
+  Crown,
+  Check,
+  Phone,
+  Clock,
+  AlertCircle,
+  Loader2,
+  Info
+} from "lucide-react";
+import {
+  getPlanPrice,
+  getPackagePrice,
+  formatCurrency,
+  getUsageProgressColor,
+  type Currency,
+  type PlanWithTranslation,
+  type PackageWithTranslation
+} from "@/types/subscription.types";
+
+// Icon mapping for plans
+const planIcons: Record<string, React.ElementType> = {
+  'Zap': Zap,
+  'Star': Star,
+  'TrendingUp': TrendingUp,
+  'Crown': Crown
+};
 
 
 export default function Billing() {
 export default function Billing() {
   const { t } = useTranslation();
   const { t } = useTranslation();
+  const { selectedShop } = useShop();
+  const [selectedPlanId, setSelectedPlanId] = useState<string | null>(null);
+  const [isChangingPlan, setIsChangingPlan] = useState(false);
+
+  // Hooks
+  const {
+    subscription,
+    isLoading: subLoading,
+    minutesRemaining,
+    packageMinutesRemaining,
+    totalMinutesRemaining,
+    usagePercentage,
+    isExpired,
+    daysRemaining,
+    refetch: refetchSubscription
+  } = useSubscription(selectedShop?.id);
+
+  const { plans, isLoading: plansLoading } = usePlans();
+  const { packages, purchasePackage, isLoading: packagesLoading } = usePackages();
+  const { changePlan, updateAutoPurchase, isUpdating } = useUpdateSubscription();
+
+  const currentCurrency: Currency = (subscription?.currency as Currency) || 'HUF';
+  const isLoading = subLoading || plansLoading;
+
+  // Handle plan selection
+  const handleSelectPlan = async (plan: PlanWithTranslation) => {
+    if (!selectedShop?.id || isUpdating) return;
+
+    // Don't allow selecting trial if already used
+    if (plan.is_trial && subscription && subscription.status !== 'trial') {
+      return;
+    }
+
+    // Don't allow selecting current plan
+    if (subscription?.plan_id === plan.id) {
+      return;
+    }
+
+    setSelectedPlanId(plan.id);
+    setIsChangingPlan(true);
+
+    const success = await changePlan(selectedShop.id, plan.id, plan);
+    if (success) {
+      await refetchSubscription();
+    }
+
+    setIsChangingPlan(false);
+    setSelectedPlanId(null);
+  };
+
+  // Handle package purchase
+  const handlePurchasePackage = async (pkg: PackageWithTranslation) => {
+    if (!selectedShop?.id || !subscription?.id) return;
+
+    const success = await purchasePackage(
+      selectedShop.id,
+      subscription.id,
+      pkg.id,
+      pkg,
+      currentCurrency
+    );
+
+    if (success) {
+      await refetchSubscription();
+    }
+  };
+
+  // Handle auto-purchase toggle
+  const handleAutoPurchaseToggle = async (enabled: boolean) => {
+    if (!selectedShop?.id) return;
+
+    await updateAutoPurchase(
+      selectedShop.id,
+      enabled,
+      subscription?.auto_purchase_package_id || packages[0]?.id,
+      subscription?.auto_purchase_threshold || 5
+    );
+
+    await refetchSubscription();
+  };
+
+  // Render loading state
+  if (isLoading) {
+    return (
+      <SidebarProvider>
+        <div className="min-h-screen flex w-full bg-slate-900">
+          <AppSidebar />
+          <main className="flex-1 bg-slate-900 text-white flex items-center justify-center">
+            <Loader2 className="w-8 h-8 animate-spin text-cyan-500" />
+          </main>
+        </div>
+      </SidebarProvider>
+    );
+  }
+
+  // No shop selected
+  if (!selectedShop) {
+    return (
+      <SidebarProvider>
+        <div className="min-h-screen flex w-full bg-slate-900">
+          <AppSidebar />
+          <main className="flex-1 bg-slate-900 text-white p-6">
+            <div className="text-center py-20">
+              <AlertCircle className="w-16 h-16 text-yellow-500 mx-auto mb-4" />
+              <h2 className="text-2xl font-bold mb-2">{t('billing.noShopSelected')}</h2>
+              <p className="text-slate-400">{t('billing.selectShopFirst')}</p>
+            </div>
+          </main>
+        </div>
+      </SidebarProvider>
+    );
+  }
 
 
   return (
   return (
     <SidebarProvider>
     <SidebarProvider>
@@ -14,81 +161,330 @@ export default function Billing() {
         <AppSidebar />
         <AppSidebar />
         <main className="flex-1 bg-slate-900 text-white">
         <main className="flex-1 bg-slate-900 text-white">
           <div className="flex flex-col gap-6 p-6">
           <div className="flex flex-col gap-6 p-6">
+            {/* Header */}
             <div>
             <div>
-              <h1 className="text-3xl font-bold text-white">{t('billing.title')}</h1>
+              <h1 className="text-3xl font-bold text-white">{t('billing.chooseYourPlan')}</h1>
               <p className="text-slate-400 mt-2">
               <p className="text-slate-400 mt-2">
                 {t('billing.subtitle')}
                 {t('billing.subtitle')}
               </p>
               </p>
             </div>
             </div>
 
 
-            {/* Coming Soon Card */}
-            <div className="flex items-center justify-center min-h-[60vh]">
-              <Card className="bg-gradient-to-br from-slate-800 via-slate-800 to-slate-900 border-slate-700 max-w-2xl w-full overflow-hidden relative">
-                {/* Animated background elements */}
-                <div className="absolute inset-0 overflow-hidden">
-                  <div className="absolute -top-20 -right-20 w-40 h-40 bg-cyan-500/10 rounded-full blur-3xl animate-pulse" />
-                  <div className="absolute -bottom-20 -left-20 w-40 h-40 bg-purple-500/10 rounded-full blur-3xl animate-pulse delay-1000" />
-                </div>
+            {/* Plan Cards Grid */}
+            <div className="space-y-6">
+              {/* Free Trial Card - Separate */}
+              {plans.filter(p => p.is_trial).map(plan => {
+                const Icon = planIcons[plan.icon_name || 'Zap'] || Zap;
+                const isCurrentPlan = subscription?.plan_id === plan.id;
+                const isDisabled = subscription && subscription.status !== 'trial';
 
 
-                <CardContent className="p-12 text-center relative z-10">
-                  {/* Icon */}
-                  <div className="mb-8 relative inline-block">
-                    <div className="w-24 h-24 rounded-full bg-gradient-to-br from-cyan-500/20 to-purple-500/20 flex items-center justify-center mx-auto">
-                      <CreditCard className="w-12 h-12 text-cyan-400" />
-                    </div>
-                    <div className="absolute -top-1 -right-1">
-                      <Sparkles className="w-6 h-6 text-yellow-400 animate-pulse" />
-                    </div>
-                  </div>
+                return (
+                  <Card
+                    key={plan.id}
+                    className={`relative border-2 border-dashed transition-all cursor-pointer ${
+                      isCurrentPlan
+                        ? 'border-cyan-500 bg-slate-800/80'
+                        : isDisabled
+                        ? 'border-slate-700 bg-slate-800/30 opacity-50 cursor-not-allowed'
+                        : 'border-slate-600 bg-slate-800/50 hover:border-cyan-500/50'
+                    }`}
+                    onClick={() => !isDisabled && handleSelectPlan(plan)}
+                  >
+                    <CardContent className="p-6">
+                      <div className="flex items-center justify-between">
+                        <div className="flex items-center gap-4">
+                          <div className="w-12 h-12 rounded-lg bg-slate-700/50 flex items-center justify-center">
+                            <Icon className="w-6 h-6 text-yellow-400" />
+                          </div>
+                          <div>
+                            <div className="flex items-center gap-2">
+                              <Zap className="w-4 h-4 text-yellow-400" />
+                              <span className="font-semibold text-white">
+                                {plan.translations.name}
+                              </span>
+                            </div>
+                            <p className="text-sm text-slate-400 mt-1">
+                              {plan.translations.description}
+                            </p>
+                          </div>
+                        </div>
+                        <div className="flex items-center gap-4">
+                          <div className="text-right">
+                            <div className="text-2xl font-bold text-white">
+                              {formatCurrency(getPlanPrice(plan, currentCurrency), currentCurrency)}
+                            </div>
+                            <div className="text-sm text-slate-400">
+                              {plan.translations.period_label}
+                            </div>
+                          </div>
+                          <div className={`w-6 h-6 rounded-full border-2 flex items-center justify-center ${
+                            isCurrentPlan ? 'border-cyan-500 bg-cyan-500' : 'border-slate-600'
+                          }`}>
+                            {isCurrentPlan && <Check className="w-4 h-4 text-white" />}
+                          </div>
+                        </div>
+                      </div>
+                    </CardContent>
+                  </Card>
+                );
+              })}
 
 
-                  {/* Coming Soon Badge */}
-                  <div className="inline-flex items-center gap-2 bg-cyan-500/10 border border-cyan-500/30 rounded-full px-4 py-2 mb-6">
-                    <Rocket className="w-4 h-4 text-cyan-400" />
-                    <span className="text-cyan-400 font-medium text-sm">{t('billing.comingSoon.badge')}</span>
-                  </div>
+              {/* Paid Plans Grid */}
+              <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
+                {plans.filter(p => !p.is_trial).map(plan => {
+                  const Icon = planIcons[plan.icon_name || 'Star'] || Star;
+                  const isCurrentPlan = subscription?.plan_id === plan.id;
+                  const isSelected = selectedPlanId === plan.id;
 
 
-                  {/* Title */}
-                  <h2 className="text-3xl font-bold text-white mb-4">
-                    {t('billing.comingSoon.title')}
-                  </h2>
+                  return (
+                    <Card
+                      key={plan.id}
+                      className={`relative transition-all cursor-pointer ${
+                        plan.is_popular
+                          ? 'border-2 border-cyan-500 bg-slate-800'
+                          : isCurrentPlan
+                          ? 'border-2 border-cyan-500/50 bg-slate-800'
+                          : 'border border-slate-700 bg-slate-800/50 hover:border-slate-600'
+                      }`}
+                      onClick={() => handleSelectPlan(plan)}
+                    >
+                      {/* Most Popular Badge */}
+                      {plan.translations.badge_text && (
+                        <div className="absolute -top-3 left-1/2 transform -translate-x-1/2 z-10">
+                          <Badge className="bg-cyan-500 text-white px-3 py-1 text-xs font-semibold">
+                            <Star className="w-3 h-3 mr-1 inline" />
+                            {plan.translations.badge_text}
+                          </Badge>
+                        </div>
+                      )}
 
 
-                  {/* Description */}
-                  <p className="text-slate-400 text-lg mb-8 max-w-md mx-auto">
-                    {t('billing.comingSoon.description')}
-                  </p>
+                      <CardHeader className="pt-6 pb-2">
+                        <CardTitle className="text-lg text-white font-semibold">
+                          {plan.translations.name}
+                        </CardTitle>
+                        <p className="text-sm text-slate-400">
+                          {plan.translations.description}
+                        </p>
+                      </CardHeader>
 
 
-                  {/* Features Preview */}
-                  <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
-                    <div className="bg-slate-700/30 rounded-lg p-4 border border-slate-600/50">
-                      <Zap className="w-6 h-6 text-yellow-400 mx-auto mb-2" />
-                      <p className="text-slate-300 text-sm font-medium">{t('billing.comingSoon.features.flexiblePlans')}</p>
-                    </div>
-                    <div className="bg-slate-700/30 rounded-lg p-4 border border-slate-600/50">
-                      <Gift className="w-6 h-6 text-pink-400 mx-auto mb-2" />
-                      <p className="text-slate-300 text-sm font-medium">{t('billing.comingSoon.features.specialOffers')}</p>
-                    </div>
-                    <div className="bg-slate-700/30 rounded-lg p-4 border border-slate-600/50">
-                      <CreditCard className="w-6 h-6 text-green-400 mx-auto mb-2" />
-                      <p className="text-slate-300 text-sm font-medium">{t('billing.comingSoon.features.easyPayments')}</p>
+                      <CardContent className="space-y-4">
+                        {/* Price */}
+                        <div className="flex items-baseline gap-1">
+                          <span className="text-3xl font-bold text-white">
+                            {formatCurrency(getPlanPrice(plan, currentCurrency), currentCurrency).replace(/[^\d,.\s]/g, '')}
+                          </span>
+                          <span className="text-lg text-slate-400">
+                            {currentCurrency === 'HUF' ? ' Ft' : currentCurrency === 'EUR' ? '€' : '$'}
+                          </span>
+                          <span className="text-slate-400">{plan.translations.period_label}</span>
+                        </div>
+
+                        <p className="text-xs text-slate-500">+ ÁFA</p>
+
+                        {/* Features List */}
+                        <div className="space-y-3 pt-2">
+                          {plan.features.map((feature, idx) => (
+                            <div key={feature.id} className="flex items-start gap-2">
+                              <Check className={`w-4 h-4 mt-0.5 flex-shrink-0 ${
+                                feature.is_highlighted ? 'text-cyan-400' : 'text-green-500'
+                              }`} />
+                              <span className={`text-sm ${
+                                feature.is_highlighted
+                                  ? 'text-white font-medium'
+                                  : 'text-slate-300'
+                              }`}>
+                                {feature.translations.label}
+                              </span>
+                            </div>
+                          ))}
+                        </div>
+
+                        {/* Overage Rate */}
+                        {plan.translations.overage_label && (
+                          <div className="pt-4 border-t border-slate-700">
+                            <p className="text-sm text-slate-400">
+                              {plan.translations.overage_label}
+                            </p>
+                          </div>
+                        )}
+
+                        {/* Select Button */}
+                        <Button
+                          className={`w-full mt-4 ${
+                            isCurrentPlan
+                              ? 'bg-slate-700 text-slate-300 cursor-default'
+                              : plan.is_popular
+                              ? 'bg-cyan-500 hover:bg-cyan-600 text-white'
+                              : 'bg-slate-700 hover:bg-slate-600 text-white'
+                          }`}
+                          disabled={isCurrentPlan || isChangingPlan}
+                        >
+                          {isSelected && isChangingPlan ? (
+                            <Loader2 className="w-4 h-4 animate-spin mr-2" />
+                          ) : null}
+                          {isCurrentPlan
+                            ? t('billing.currentPlan')
+                            : t('billing.selectPlan')}
+                        </Button>
+                      </CardContent>
+                    </Card>
+                  );
+                })}
+              </div>
+            </div>
+
+            {/* Additional Packages Section */}
+            {subscription && subscription.status !== 'trial' && (
+              <div className="mt-8">
+                <h2 className="text-xl font-semibold text-white mb-4">
+                  {t('billing.additionalPackages')}
+                </h2>
+                <p className="text-slate-400 mb-6">
+                  {t('billing.additionalPackagesDescription')}
+                </p>
+
+                <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
+                  {packages.map(pkg => (
+                    <Card
+                      key={pkg.id}
+                      className="border border-slate-700 bg-slate-800/50 hover:border-cyan-500/50 transition-all"
+                    >
+                      <CardContent className="p-4">
+                        <div className="flex items-center justify-between mb-3">
+                          <div className="flex items-center gap-2">
+                            <Clock className="w-5 h-5 text-cyan-400" />
+                            <span className="font-semibold text-white">
+                              {pkg.translations.name}
+                            </span>
+                          </div>
+                          <span className="text-lg font-bold text-white">
+                            {formatCurrency(getPackagePrice(pkg, currentCurrency), currentCurrency)}
+                          </span>
+                        </div>
+                        <p className="text-sm text-slate-400 mb-4">
+                          {pkg.translations.description}
+                        </p>
+                        <Button
+                          variant="outline"
+                          className="w-full border-cyan-500 text-cyan-400 hover:bg-cyan-500/10"
+                          onClick={() => handlePurchasePackage(pkg)}
+                        >
+                          {t('billing.purchasePackage')}
+                        </Button>
+                      </CardContent>
+                    </Card>
+                  ))}
+                </div>
+              </div>
+            )}
+
+            {/* Auto-Purchase Settings */}
+            {subscription && subscription.status !== 'trial' && (
+              <Card className="border border-slate-700 bg-slate-800/50 mt-4">
+                <CardContent className="p-6">
+                  <div className="flex items-center justify-between">
+                    <div>
+                      <h3 className="font-semibold text-white">
+                        {t('billing.autoPurchase')}
+                      </h3>
+                      <p className="text-sm text-slate-400 mt-1">
+                        {t('billing.autoPurchaseDescription')}
+                      </p>
                     </div>
                     </div>
+                    <Switch
+                      checked={subscription.auto_purchase_enabled}
+                      onCheckedChange={handleAutoPurchaseToggle}
+                    />
                   </div>
                   </div>
 
 
-                  {/* Notify Button */}
-                  <Button
-                    className="bg-cyan-500 hover:bg-cyan-600 text-white px-8 py-6 text-lg"
-                    disabled
-                  >
-                    <Bell className="w-5 h-5 mr-2" />
-                    {t('billing.comingSoon.notifyButton')}
-                  </Button>
-
-                  {/* Note */}
-                  <p className="text-slate-500 text-sm mt-6">
-                    {t('billing.comingSoon.note')}
-                  </p>
+                  {subscription.auto_purchase_enabled && (
+                    <div className="mt-4 pt-4 border-t border-slate-700 grid grid-cols-2 gap-4">
+                      <div>
+                        <label className="text-sm text-slate-400 mb-2 block">
+                          {t('billing.autoPurchasePackage')}
+                        </label>
+                        <Select
+                          value={subscription.auto_purchase_package_id || ''}
+                          onValueChange={(value) => {
+                            if (selectedShop?.id) {
+                              updateAutoPurchase(
+                                selectedShop.id,
+                                true,
+                                value,
+                                subscription.auto_purchase_threshold
+                              );
+                            }
+                          }}
+                        >
+                          <SelectTrigger className="bg-slate-700 border-slate-600">
+                            <SelectValue />
+                          </SelectTrigger>
+                          <SelectContent className="bg-slate-800 border-slate-600">
+                            {packages.map(pkg => (
+                              <SelectItem key={pkg.id} value={pkg.id} className="text-white">
+                                {pkg.translations.name} - {formatCurrency(getPackagePrice(pkg, currentCurrency), currentCurrency)}
+                              </SelectItem>
+                            ))}
+                          </SelectContent>
+                        </Select>
+                      </div>
+                      <div>
+                        <label className="text-sm text-slate-400 mb-2 block">
+                          {t('billing.autoPurchaseThreshold')}
+                        </label>
+                        <Select
+                          value={String(subscription.auto_purchase_threshold)}
+                          onValueChange={(value) => {
+                            if (selectedShop?.id) {
+                              updateAutoPurchase(
+                                selectedShop.id,
+                                true,
+                                subscription.auto_purchase_package_id || packages[0]?.id,
+                                parseInt(value)
+                              );
+                            }
+                          }}
+                        >
+                          <SelectTrigger className="bg-slate-700 border-slate-600">
+                            <SelectValue />
+                          </SelectTrigger>
+                          <SelectContent className="bg-slate-800 border-slate-600">
+                            {[1, 2, 5, 10, 15, 20].map(mins => (
+                              <SelectItem key={mins} value={String(mins)} className="text-white">
+                                {mins} {t('billing.minutesRemaining')}
+                              </SelectItem>
+                            ))}
+                          </SelectContent>
+                        </Select>
+                      </div>
+                    </div>
+                  )}
                 </CardContent>
                 </CardContent>
               </Card>
               </Card>
-            </div>
+            )}
+
+            {/* How Billing Works Section */}
+            <Card className="border border-slate-700 bg-slate-800/50 mt-4">
+              <CardContent className="p-6">
+                <h3 className="font-semibold text-white mb-4 flex items-center gap-2">
+                  <Info className="w-5 h-5 text-cyan-400" />
+                  {t('billing.howBillingWorks')}
+                </h3>
+                <div className="space-y-3 text-sm text-slate-400">
+                  <p>
+                    <strong className="text-slate-300">{t('billing.phoneNumberInfo')}</strong>{' '}
+                    {t('billing.phoneNumberInfoDesc')}
+                  </p>
+                  <p>
+                    <strong className="text-slate-300">{t('billing.trialInfo')}</strong>{' '}
+                    {t('billing.trialInfoDesc')}
+                  </p>
+                  <p>
+                    <strong className="text-slate-300">{t('billing.overageInfo')}</strong>{' '}
+                    {t('billing.overageInfoDesc')}
+                  </p>
+                </div>
+              </CardContent>
+            </Card>
           </div>
           </div>
         </main>
         </main>
       </div>
       </div>

+ 309 - 0
shopcall.ai-main/src/types/subscription.types.ts

@@ -0,0 +1,309 @@
+// Subscription Types for ShopCall.ai
+
+// Enum types matching database
+export type SubscriptionStatus = 'trial' | 'active' | 'past_due' | 'cancelled' | 'expired';
+export type BillingPeriod = 'trial' | 'monthly' | 'yearly';
+export type Currency = 'HUF' | 'EUR' | 'USD';
+
+// Subscription Plan
+export interface SubscriptionPlan {
+  id: string;
+  plan_code: string;
+  sort_order: number;
+  is_active: boolean;
+  is_trial: boolean;
+  is_popular: boolean;
+  price_huf: number;
+  price_eur: number;
+  price_usd: number;
+  overage_price_huf: number | null;
+  overage_price_eur: number | null;
+  overage_price_usd: number | null;
+  minutes_included: number;
+  trial_days: number | null;
+  max_products: number | null;
+  icon_name: string | null;
+  badge_color: string | null;
+  created_at: string;
+  updated_at: string;
+}
+
+// Plan Translation
+export interface SubscriptionPlanTranslation {
+  id: string;
+  plan_id: string;
+  language_code: string;
+  name: string;
+  description: string | null;
+  period_label: string | null;
+  overage_label: string | null;
+  badge_text: string | null;
+  created_at: string;
+  updated_at: string;
+}
+
+// Plan Feature
+export interface SubscriptionPlanFeature {
+  id: string;
+  plan_id: string;
+  feature_code: string;
+  sort_order: number;
+  is_highlighted: boolean;
+  created_at: string;
+}
+
+// Feature Translation
+export interface SubscriptionFeatureTranslation {
+  id: string;
+  feature_id: string;
+  language_code: string;
+  label: string;
+  created_at: string;
+}
+
+// Additional Package
+export interface AdditionalPackage {
+  id: string;
+  package_code: string;
+  sort_order: number;
+  is_active: boolean;
+  minutes: number;
+  price_huf: number;
+  price_eur: number;
+  price_usd: number;
+  created_at: string;
+  updated_at: string;
+}
+
+// Package Translation
+export interface AdditionalPackageTranslation {
+  id: string;
+  package_id: string;
+  language_code: string;
+  name: string;
+  description: string | null;
+  created_at: string;
+}
+
+// Store Subscription
+export interface StoreSubscription {
+  id: string;
+  store_id: string;
+  plan_id: string;
+  status: SubscriptionStatus;
+  billing_period: BillingPeriod;
+  period_start: string;
+  period_end: string;
+  minutes_used: number;
+  minutes_included: number;
+  auto_purchase_enabled: boolean;
+  auto_purchase_package_id: string | null;
+  auto_purchase_threshold: number;
+  currency: Currency;
+  cancelled_at: string | null;
+  cancellation_reason: string | null;
+  created_at: string;
+  updated_at: string;
+}
+
+// Package Purchase
+export interface PackagePurchase {
+  id: string;
+  store_id: string;
+  subscription_id: string;
+  package_id: string;
+  minutes_purchased: number;
+  minutes_remaining: number;
+  currency: Currency;
+  price_paid: number;
+  is_auto_purchase: boolean;
+  expires_at: string;
+  purchased_at: string;
+  is_active: boolean;
+}
+
+// Billing History
+export interface BillingHistoryItem {
+  id: string;
+  store_id: string;
+  subscription_id: string | null;
+  event_type: string;
+  currency: Currency;
+  amount: number;
+  plan_id: string | null;
+  package_id: string | null;
+  metadata: Record<string, unknown>;
+  description: string | null;
+  created_at: string;
+}
+
+// =============================================================================
+// Joined/Composite Types for Frontend Use
+// =============================================================================
+
+// Plan with translation (for display)
+export interface PlanWithTranslation extends SubscriptionPlan {
+  translations: SubscriptionPlanTranslation;
+  features: FeatureWithTranslation[];
+}
+
+// Feature with translation
+export interface FeatureWithTranslation extends SubscriptionPlanFeature {
+  translations: SubscriptionFeatureTranslation;
+}
+
+// Package with translation
+export interface PackageWithTranslation extends AdditionalPackage {
+  translations: AdditionalPackageTranslation;
+}
+
+// Subscription with plan details (for sidebar display)
+export interface SubscriptionWithPlan extends StoreSubscription {
+  plan: SubscriptionPlan;
+  plan_translation?: SubscriptionPlanTranslation;
+}
+
+// Full subscription data (for billing page)
+export interface FullSubscriptionData {
+  subscription: StoreSubscription;
+  plan: PlanWithTranslation;
+  active_packages: PackagePurchase[];
+  total_minutes_remaining: number;
+  package_minutes_remaining: number;
+  is_expired: boolean;
+  days_remaining: number;
+}
+
+// =============================================================================
+// Helper Functions
+// =============================================================================
+
+/**
+ * Get price for a plan based on currency
+ */
+export function getPlanPrice(plan: SubscriptionPlan, currency: Currency): number {
+  switch (currency) {
+    case 'HUF':
+      return plan.price_huf;
+    case 'EUR':
+      return plan.price_eur;
+    case 'USD':
+      return plan.price_usd;
+    default:
+      return plan.price_huf;
+  }
+}
+
+/**
+ * Get overage price for a plan based on currency
+ */
+export function getOveragePrice(plan: SubscriptionPlan, currency: Currency): number | null {
+  switch (currency) {
+    case 'HUF':
+      return plan.overage_price_huf;
+    case 'EUR':
+      return plan.overage_price_eur;
+    case 'USD':
+      return plan.overage_price_usd;
+    default:
+      return plan.overage_price_huf;
+  }
+}
+
+/**
+ * Get package price based on currency
+ */
+export function getPackagePrice(pkg: AdditionalPackage, currency: Currency): number {
+  switch (currency) {
+    case 'HUF':
+      return pkg.price_huf;
+    case 'EUR':
+      return pkg.price_eur;
+    case 'USD':
+      return pkg.price_usd;
+    default:
+      return pkg.price_huf;
+  }
+}
+
+/**
+ * Format currency amount
+ */
+export function formatCurrency(amount: number, currency: Currency): string {
+  switch (currency) {
+    case 'HUF':
+      return `${amount.toLocaleString('hu-HU')} Ft`;
+    case 'EUR':
+      return `€${amount.toLocaleString('de-DE')}`;
+    case 'USD':
+      return `$${amount.toLocaleString('en-US')}`;
+    default:
+      return `${amount}`;
+  }
+}
+
+/**
+ * Calculate minutes remaining (included + packages)
+ */
+export function calculateMinutesRemaining(
+  subscription: StoreSubscription,
+  activePackages: PackagePurchase[]
+): { included: number; packages: number; total: number } {
+  const includedRemaining = Math.max(subscription.minutes_included - subscription.minutes_used, 0);
+  const packageRemaining = activePackages.reduce((sum, pkg) => sum + pkg.minutes_remaining, 0);
+
+  return {
+    included: includedRemaining,
+    packages: packageRemaining,
+    total: includedRemaining + packageRemaining
+  };
+}
+
+/**
+ * Check if subscription is expired (by time or minutes for trial)
+ */
+export function isSubscriptionExpired(subscription: StoreSubscription): boolean {
+  const now = new Date();
+  const periodEnd = new Date(subscription.period_end);
+
+  // Time expired
+  if (now > periodEnd) {
+    return true;
+  }
+
+  // For trial, also check minutes
+  if (subscription.status === 'trial') {
+    const minutesRemaining = subscription.minutes_included - subscription.minutes_used;
+    if (minutesRemaining <= 0) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+/**
+ * Get usage percentage (0-100)
+ */
+export function getUsagePercentage(subscription: StoreSubscription): number {
+  if (subscription.minutes_included === 0) return 100;
+  return Math.min((subscription.minutes_used / subscription.minutes_included) * 100, 100);
+}
+
+/**
+ * Get usage status color class
+ */
+export function getUsageStatusColor(percentage: number): string {
+  if (percentage >= 90) return 'text-red-500';
+  if (percentage >= 75) return 'text-yellow-500';
+  return 'text-green-500';
+}
+
+/**
+ * Get usage progress bar color class
+ */
+export function getUsageProgressColor(percentage: number): string {
+  if (percentage >= 90) return 'bg-red-500';
+  if (percentage >= 75) return 'bg-yellow-500';
+  return 'bg-cyan-500';
+}

+ 905 - 0
supabase/migrations/20251210_subscription_plans.sql

@@ -0,0 +1,905 @@
+-- Migration: Subscription Plans and Billing System
+-- Date: 2025-12-10
+-- Purpose: Create tables for subscription plans, packages, and usage tracking
+
+-- =============================================================================
+-- ENUM TYPES
+-- =============================================================================
+
+-- Subscription status
+CREATE TYPE subscription_status AS ENUM (
+  'trial',           -- Free trial period
+  'active',          -- Active paid subscription
+  'past_due',        -- Payment failed, grace period
+  'cancelled',       -- User cancelled
+  'expired'          -- Trial or subscription expired
+);
+
+-- Billing period type
+CREATE TYPE billing_period AS ENUM (
+  'trial',           -- Trial period (14 days)
+  'monthly',         -- Monthly billing
+  'yearly'           -- Yearly billing (future)
+);
+
+-- =============================================================================
+-- TABLE: subscription_plans
+-- Master table for all available plans
+-- =============================================================================
+
+CREATE TABLE subscription_plans (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+
+  -- Plan identification
+  plan_code TEXT UNIQUE NOT NULL,  -- 'free_trial', 'starter', 'growth', 'scale'
+  sort_order INTEGER NOT NULL DEFAULT 0,
+  is_active BOOLEAN DEFAULT true,
+  is_trial BOOLEAN DEFAULT false,
+  is_popular BOOLEAN DEFAULT false,  -- "Most Popular" badge
+
+  -- Pricing (multi-currency: store all currencies)
+  price_huf DECIMAL(10,2) NOT NULL DEFAULT 0,
+  price_eur DECIMAL(10,2) NOT NULL DEFAULT 0,
+  price_usd DECIMAL(10,2) NOT NULL DEFAULT 0,
+
+  -- Overage pricing per minute (multi-currency)
+  overage_price_huf DECIMAL(10,2) DEFAULT NULL,  -- NULL means no overage allowed
+  overage_price_eur DECIMAL(10,2) DEFAULT NULL,
+  overage_price_usd DECIMAL(10,2) DEFAULT NULL,
+
+  -- Limits
+  minutes_included INTEGER NOT NULL,  -- Included minutes per period
+  trial_days INTEGER DEFAULT NULL,    -- Only for trial plan
+  max_products INTEGER DEFAULT NULL,  -- Product limit (NULL = unlimited)
+
+  -- Plan metadata (for display)
+  icon_name TEXT DEFAULT NULL,        -- Lucide icon name
+  badge_color TEXT DEFAULT NULL,      -- Tailwind color class
+
+  -- Timestamps
+  created_at TIMESTAMPTZ DEFAULT NOW(),
+  updated_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- =============================================================================
+-- TABLE: subscription_plan_translations
+-- i18n translations for plan names, descriptions, features
+-- =============================================================================
+
+CREATE TABLE subscription_plan_translations (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  plan_id UUID NOT NULL REFERENCES subscription_plans(id) ON DELETE CASCADE,
+
+  -- Language code (en, hu, de)
+  language_code TEXT NOT NULL,
+
+  -- Translatable content
+  name TEXT NOT NULL,
+  description TEXT,
+  period_label TEXT,           -- e.g., "/mo" or "/14 days"
+  overage_label TEXT,          -- e.g., "160 Ft/min" or "Save 10%!"
+  badge_text TEXT,             -- e.g., "Most Popular", "Best Rate"
+
+  -- Timestamps
+  created_at TIMESTAMPTZ DEFAULT NOW(),
+  updated_at TIMESTAMPTZ DEFAULT NOW(),
+
+  UNIQUE(plan_id, language_code)
+);
+
+-- =============================================================================
+-- TABLE: subscription_plan_features
+-- Features list per plan (normalized, with i18n)
+-- =============================================================================
+
+CREATE TABLE subscription_plan_features (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  plan_id UUID NOT NULL REFERENCES subscription_plans(id) ON DELETE CASCADE,
+
+  -- Feature metadata
+  feature_code TEXT NOT NULL,   -- e.g., 'dedicated_number', 'transfer_to_human'
+  sort_order INTEGER NOT NULL DEFAULT 0,
+  is_highlighted BOOLEAN DEFAULT false,  -- Show as highlighted/premium
+
+  -- Timestamps
+  created_at TIMESTAMPTZ DEFAULT NOW(),
+
+  UNIQUE(plan_id, feature_code)
+);
+
+-- =============================================================================
+-- TABLE: subscription_feature_translations
+-- i18n translations for feature labels
+-- =============================================================================
+
+CREATE TABLE subscription_feature_translations (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  feature_id UUID NOT NULL REFERENCES subscription_plan_features(id) ON DELETE CASCADE,
+
+  language_code TEXT NOT NULL,
+  label TEXT NOT NULL,          -- Display text for the feature
+
+  created_at TIMESTAMPTZ DEFAULT NOW(),
+
+  UNIQUE(feature_id, language_code)
+);
+
+-- =============================================================================
+-- TABLE: additional_packages
+-- Minute top-up packages (purchased separately)
+-- =============================================================================
+
+CREATE TABLE additional_packages (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+
+  -- Package identification
+  package_code TEXT UNIQUE NOT NULL,  -- e.g., '50_min', '100_min', '250_min'
+  sort_order INTEGER NOT NULL DEFAULT 0,
+  is_active BOOLEAN DEFAULT true,
+
+  -- Minutes included
+  minutes INTEGER NOT NULL,
+
+  -- Pricing (multi-currency)
+  price_huf DECIMAL(10,2) NOT NULL,
+  price_eur DECIMAL(10,2) NOT NULL,
+  price_usd DECIMAL(10,2) NOT NULL,
+
+  -- Timestamps
+  created_at TIMESTAMPTZ DEFAULT NOW(),
+  updated_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- =============================================================================
+-- TABLE: additional_package_translations
+-- i18n translations for package names/descriptions
+-- =============================================================================
+
+CREATE TABLE additional_package_translations (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  package_id UUID NOT NULL REFERENCES additional_packages(id) ON DELETE CASCADE,
+
+  language_code TEXT NOT NULL,
+  name TEXT NOT NULL,
+  description TEXT,
+
+  created_at TIMESTAMPTZ DEFAULT NOW(),
+
+  UNIQUE(package_id, language_code)
+);
+
+-- =============================================================================
+-- TABLE: store_subscriptions
+-- Active subscription for each store
+-- =============================================================================
+
+CREATE TABLE store_subscriptions (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  store_id UUID NOT NULL REFERENCES stores(id) ON DELETE CASCADE,
+  plan_id UUID NOT NULL REFERENCES subscription_plans(id),
+
+  -- Subscription status
+  status subscription_status NOT NULL DEFAULT 'trial',
+  billing_period billing_period NOT NULL DEFAULT 'trial',
+
+  -- Period dates
+  period_start TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+  period_end TIMESTAMPTZ NOT NULL,  -- Trial: +14 days, Monthly: +1 month
+
+  -- Usage tracking for current period
+  minutes_used DECIMAL(10,2) NOT NULL DEFAULT 0,
+  minutes_included INTEGER NOT NULL,  -- Snapshot from plan at subscription time
+
+  -- Auto-purchase settings
+  auto_purchase_enabled BOOLEAN DEFAULT false,
+  auto_purchase_package_id UUID REFERENCES additional_packages(id),
+  auto_purchase_threshold INTEGER DEFAULT 5,  -- Trigger when X minutes remaining
+
+  -- Currency preference (set at first subscription)
+  currency TEXT NOT NULL DEFAULT 'HUF',  -- 'HUF', 'EUR', 'USD'
+
+  -- Cancellation tracking
+  cancelled_at TIMESTAMPTZ DEFAULT NULL,
+  cancellation_reason TEXT DEFAULT NULL,
+
+  -- Timestamps
+  created_at TIMESTAMPTZ DEFAULT NOW(),
+  updated_at TIMESTAMPTZ DEFAULT NOW(),
+
+  -- Only one active subscription per store
+  UNIQUE(store_id)
+);
+
+-- =============================================================================
+-- TABLE: subscription_usage_log
+-- Detailed usage tracking (minute consumption)
+-- =============================================================================
+
+CREATE TABLE subscription_usage_log (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  subscription_id UUID NOT NULL REFERENCES store_subscriptions(id) ON DELETE CASCADE,
+  store_id UUID NOT NULL REFERENCES stores(id) ON DELETE CASCADE,
+
+  -- Usage details
+  call_log_id UUID REFERENCES call_logs(id),  -- Link to actual call
+  minutes_consumed DECIMAL(10,4) NOT NULL,
+
+  -- Source of minutes
+  source_type TEXT NOT NULL,  -- 'included', 'overage', 'package'
+  source_package_id UUID REFERENCES additional_packages(id),  -- If from package
+
+  -- Timestamps
+  created_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- =============================================================================
+-- TABLE: package_purchases
+-- Record of additional package purchases
+-- =============================================================================
+
+CREATE TABLE package_purchases (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  store_id UUID NOT NULL REFERENCES stores(id) ON DELETE CASCADE,
+  subscription_id UUID NOT NULL REFERENCES store_subscriptions(id),
+  package_id UUID NOT NULL REFERENCES additional_packages(id),
+
+  -- Purchase details
+  minutes_purchased INTEGER NOT NULL,
+  minutes_remaining DECIMAL(10,2) NOT NULL,
+
+  -- Price at time of purchase (snapshot)
+  currency TEXT NOT NULL,
+  price_paid DECIMAL(10,2) NOT NULL,
+
+  -- Auto vs manual
+  is_auto_purchase BOOLEAN DEFAULT false,
+
+  -- Expiry (packages expire with billing period)
+  expires_at TIMESTAMPTZ NOT NULL,
+
+  -- Timestamps
+  purchased_at TIMESTAMPTZ DEFAULT NOW(),
+
+  -- Status
+  is_active BOOLEAN DEFAULT true  -- False when fully consumed or expired
+);
+
+-- =============================================================================
+-- TABLE: billing_history
+-- Record of all billing events (for future payment integration)
+-- =============================================================================
+
+CREATE TABLE billing_history (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  store_id UUID NOT NULL REFERENCES stores(id) ON DELETE CASCADE,
+  subscription_id UUID REFERENCES store_subscriptions(id),
+
+  -- Event type
+  event_type TEXT NOT NULL,  -- 'subscription_started', 'subscription_renewed',
+                             -- 'package_purchased', 'overage_charged',
+                             -- 'subscription_cancelled', 'plan_changed'
+
+  -- Amount details
+  currency TEXT NOT NULL,
+  amount DECIMAL(10,2) NOT NULL,
+
+  -- Reference to related entities
+  plan_id UUID REFERENCES subscription_plans(id),
+  package_id UUID REFERENCES additional_packages(id),
+
+  -- Event metadata (JSON for flexibility)
+  metadata JSONB DEFAULT '{}',
+
+  -- Description for display
+  description TEXT,
+
+  -- Timestamps
+  created_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- =============================================================================
+-- INDEXES
+-- =============================================================================
+
+CREATE INDEX idx_store_subscriptions_store_id ON store_subscriptions(store_id);
+CREATE INDEX idx_store_subscriptions_status ON store_subscriptions(status);
+CREATE INDEX idx_store_subscriptions_period_end ON store_subscriptions(period_end);
+CREATE INDEX idx_subscription_usage_log_subscription ON subscription_usage_log(subscription_id);
+CREATE INDEX idx_subscription_usage_log_store ON subscription_usage_log(store_id);
+CREATE INDEX idx_package_purchases_store ON package_purchases(store_id);
+CREATE INDEX idx_package_purchases_active ON package_purchases(store_id, is_active) WHERE is_active = true;
+CREATE INDEX idx_billing_history_store ON billing_history(store_id);
+CREATE INDEX idx_plan_translations_plan_lang ON subscription_plan_translations(plan_id, language_code);
+CREATE INDEX idx_feature_translations_feature_lang ON subscription_feature_translations(feature_id, language_code);
+
+-- =============================================================================
+-- ROW LEVEL SECURITY
+-- =============================================================================
+
+ALTER TABLE subscription_plans ENABLE ROW LEVEL SECURITY;
+ALTER TABLE subscription_plan_translations ENABLE ROW LEVEL SECURITY;
+ALTER TABLE subscription_plan_features ENABLE ROW LEVEL SECURITY;
+ALTER TABLE subscription_feature_translations ENABLE ROW LEVEL SECURITY;
+ALTER TABLE additional_packages ENABLE ROW LEVEL SECURITY;
+ALTER TABLE additional_package_translations ENABLE ROW LEVEL SECURITY;
+ALTER TABLE store_subscriptions ENABLE ROW LEVEL SECURITY;
+ALTER TABLE subscription_usage_log ENABLE ROW LEVEL SECURITY;
+ALTER TABLE package_purchases ENABLE ROW LEVEL SECURITY;
+ALTER TABLE billing_history ENABLE ROW LEVEL SECURITY;
+
+-- Plans and packages: readable by all authenticated users
+CREATE POLICY "Plans are readable by authenticated users"
+  ON subscription_plans FOR SELECT TO authenticated USING (is_active = true);
+
+CREATE POLICY "Plan translations are readable by authenticated users"
+  ON subscription_plan_translations FOR SELECT TO authenticated USING (true);
+
+CREATE POLICY "Plan features are readable by authenticated users"
+  ON subscription_plan_features FOR SELECT TO authenticated USING (true);
+
+CREATE POLICY "Feature translations are readable by authenticated users"
+  ON subscription_feature_translations FOR SELECT TO authenticated USING (true);
+
+CREATE POLICY "Packages are readable by authenticated users"
+  ON additional_packages FOR SELECT TO authenticated USING (is_active = true);
+
+CREATE POLICY "Package translations are readable by authenticated users"
+  ON additional_package_translations FOR SELECT TO authenticated USING (true);
+
+-- Store-specific data: only owner can access
+CREATE POLICY "Users can view own store subscriptions"
+  ON store_subscriptions FOR SELECT TO authenticated
+  USING (store_id IN (SELECT id FROM stores WHERE user_id = auth.uid()));
+
+CREATE POLICY "Users can update own store subscriptions"
+  ON store_subscriptions FOR UPDATE TO authenticated
+  USING (store_id IN (SELECT id FROM stores WHERE user_id = auth.uid()))
+  WITH CHECK (store_id IN (SELECT id FROM stores WHERE user_id = auth.uid()));
+
+CREATE POLICY "Users can view own usage logs"
+  ON subscription_usage_log FOR SELECT TO authenticated
+  USING (store_id IN (SELECT id FROM stores WHERE user_id = auth.uid()));
+
+CREATE POLICY "Users can view own package purchases"
+  ON package_purchases FOR SELECT TO authenticated
+  USING (store_id IN (SELECT id FROM stores WHERE user_id = auth.uid()));
+
+CREATE POLICY "Users can insert own package purchases"
+  ON package_purchases FOR INSERT TO authenticated
+  WITH CHECK (store_id IN (SELECT id FROM stores WHERE user_id = auth.uid()));
+
+CREATE POLICY "Users can view own billing history"
+  ON billing_history FOR SELECT TO authenticated
+  USING (store_id IN (SELECT id FROM stores WHERE user_id = auth.uid()));
+
+-- Service role can manage all
+CREATE POLICY "Service role full access to subscriptions"
+  ON store_subscriptions FOR ALL TO service_role USING (true) WITH CHECK (true);
+
+CREATE POLICY "Service role full access to usage logs"
+  ON subscription_usage_log FOR ALL TO service_role USING (true) WITH CHECK (true);
+
+CREATE POLICY "Service role full access to purchases"
+  ON package_purchases FOR ALL TO service_role USING (true) WITH CHECK (true);
+
+CREATE POLICY "Service role full access to billing"
+  ON billing_history FOR ALL TO service_role USING (true) WITH CHECK (true);
+
+CREATE POLICY "Service role full access to plans"
+  ON subscription_plans FOR ALL TO service_role USING (true) WITH CHECK (true);
+
+CREATE POLICY "Service role full access to plan translations"
+  ON subscription_plan_translations FOR ALL TO service_role USING (true) WITH CHECK (true);
+
+CREATE POLICY "Service role full access to plan features"
+  ON subscription_plan_features FOR ALL TO service_role USING (true) WITH CHECK (true);
+
+CREATE POLICY "Service role full access to feature translations"
+  ON subscription_feature_translations FOR ALL TO service_role USING (true) WITH CHECK (true);
+
+CREATE POLICY "Service role full access to packages"
+  ON additional_packages FOR ALL TO service_role USING (true) WITH CHECK (true);
+
+CREATE POLICY "Service role full access to package translations"
+  ON additional_package_translations FOR ALL TO service_role USING (true) WITH CHECK (true);
+
+-- =============================================================================
+-- ENABLE REALTIME for store_subscriptions
+-- =============================================================================
+
+ALTER PUBLICATION supabase_realtime ADD TABLE store_subscriptions;
+
+-- =============================================================================
+-- HELPER FUNCTIONS
+-- =============================================================================
+
+-- Function to auto-assign free trial when store is created
+CREATE OR REPLACE FUNCTION auto_assign_free_trial()
+RETURNS TRIGGER AS $$
+DECLARE
+  trial_plan_id UUID;
+  trial_minutes INTEGER;
+  trial_days INTEGER;
+BEGIN
+  -- Get the free trial plan
+  SELECT id, minutes_included, subscription_plans.trial_days
+  INTO trial_plan_id, trial_minutes, trial_days
+  FROM subscription_plans
+  WHERE plan_code = 'free_trial' AND is_active = true
+  LIMIT 1;
+
+  IF trial_plan_id IS NOT NULL THEN
+    -- Create subscription for the new store
+    INSERT INTO store_subscriptions (
+      store_id,
+      plan_id,
+      status,
+      billing_period,
+      period_start,
+      period_end,
+      minutes_included,
+      currency
+    ) VALUES (
+      NEW.id,
+      trial_plan_id,
+      'trial',
+      'trial',
+      NOW(),
+      NOW() + (COALESCE(trial_days, 14) || ' days')::INTERVAL,
+      trial_minutes,
+      'HUF'  -- Default currency
+    );
+
+    -- Log billing event
+    INSERT INTO billing_history (
+      store_id,
+      event_type,
+      currency,
+      amount,
+      plan_id,
+      description
+    ) VALUES (
+      NEW.id,
+      'subscription_started',
+      'HUF',
+      0,
+      trial_plan_id,
+      'Free trial started'
+    );
+  END IF;
+
+  RETURN NEW;
+END;
+$$ LANGUAGE plpgsql SECURITY DEFINER;
+
+-- Trigger to auto-assign trial on store creation
+CREATE TRIGGER trigger_auto_assign_free_trial
+  AFTER INSERT ON stores
+  FOR EACH ROW
+  EXECUTE FUNCTION auto_assign_free_trial();
+
+-- Function to get current subscription status for a store
+CREATE OR REPLACE FUNCTION get_store_subscription_status(p_store_id UUID)
+RETURNS TABLE (
+  subscription_id UUID,
+  plan_code TEXT,
+  plan_name TEXT,
+  status subscription_status,
+  minutes_used DECIMAL,
+  minutes_included INTEGER,
+  minutes_remaining DECIMAL,
+  package_minutes_remaining DECIMAL,
+  total_minutes_remaining DECIMAL,
+  period_start TIMESTAMPTZ,
+  period_end TIMESTAMPTZ,
+  is_expired BOOLEAN,
+  days_remaining INTEGER
+) AS $$
+BEGIN
+  RETURN QUERY
+  SELECT
+    ss.id as subscription_id,
+    sp.plan_code,
+    spt.name as plan_name,
+    ss.status,
+    ss.minutes_used,
+    ss.minutes_included,
+    GREATEST(ss.minutes_included - ss.minutes_used, 0)::DECIMAL as minutes_remaining,
+    COALESCE(
+      (SELECT SUM(pp.minutes_remaining) FROM package_purchases pp
+       WHERE pp.subscription_id = ss.id AND pp.is_active = true),
+      0
+    )::DECIMAL as package_minutes_remaining,
+    (GREATEST(ss.minutes_included - ss.minutes_used, 0) + COALESCE(
+      (SELECT SUM(pp.minutes_remaining) FROM package_purchases pp
+       WHERE pp.subscription_id = ss.id AND pp.is_active = true),
+      0
+    ))::DECIMAL as total_minutes_remaining,
+    ss.period_start,
+    ss.period_end,
+    ss.period_end < NOW() as is_expired,
+    GREATEST(EXTRACT(DAY FROM ss.period_end - NOW())::INTEGER, 0) as days_remaining
+  FROM store_subscriptions ss
+  JOIN subscription_plans sp ON ss.plan_id = sp.id
+  LEFT JOIN subscription_plan_translations spt ON sp.id = spt.plan_id AND spt.language_code = 'en'
+  WHERE ss.store_id = p_store_id;
+END;
+$$ LANGUAGE plpgsql SECURITY DEFINER;
+
+-- Function to update subscription timestamps
+CREATE OR REPLACE FUNCTION update_store_subscription_timestamp()
+RETURNS TRIGGER AS $$
+BEGIN
+  NEW.updated_at = NOW();
+  RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER trigger_update_store_subscription_timestamp
+  BEFORE UPDATE ON store_subscriptions
+  FOR EACH ROW
+  EXECUTE FUNCTION update_store_subscription_timestamp();
+
+-- =============================================================================
+-- SEED DATA: Initial Plans
+-- =============================================================================
+
+-- Insert the 4 plans
+INSERT INTO subscription_plans (
+  plan_code, sort_order, is_active, is_trial, is_popular,
+  price_huf, price_eur, price_usd,
+  overage_price_huf, overage_price_eur, overage_price_usd,
+  minutes_included, trial_days, max_products,
+  icon_name, badge_color
+) VALUES
+  ('free_trial', 1, true, true, false, 0, 0, 0, NULL, NULL, NULL, 10, 14, NULL, 'Zap', NULL),
+  ('starter', 2, true, false, false, 8990, 25, 29, 160, 0.45, 0.50, 40, NULL, 2500, 'Star', NULL),
+  ('growth', 3, true, false, true, 24900, 70, 79, 150, 0.42, 0.47, 130, NULL, 5000, 'TrendingUp', 'cyan'),
+  ('scale', 4, true, false, false, 59900, 170, 189, 140, 0.40, 0.44, 350, NULL, 15000, 'Crown', NULL);
+
+-- =============================================================================
+-- SEED DATA: English Translations
+-- =============================================================================
+
+INSERT INTO subscription_plan_translations (plan_id, language_code, name, description, period_label, overage_label, badge_text)
+SELECT id, 'en',
+  CASE plan_code
+    WHEN 'free_trial' THEN '14-Day Free Trial (Sandbox)'
+    WHEN 'starter' THEN 'Starter'
+    WHEN 'growth' THEN 'Growth'
+    WHEN 'scale' THEN 'Scale'
+  END,
+  CASE plan_code
+    WHEN 'free_trial' THEN 'Test the AI risk-free. 10 Minutes of AI Talk Time included.'
+    WHEN 'starter' THEN 'Small shops, <5 calls/day'
+    WHEN 'growth' THEN 'Growing brands, daily calls'
+    WHEN 'scale' THEN 'High volume / Seasonal'
+  END,
+  CASE plan_code
+    WHEN 'free_trial' THEN '/ 14 days'
+    WHEN 'starter' THEN '/mo'
+    WHEN 'growth' THEN '/mo'
+    WHEN 'scale' THEN '/mo'
+  END,
+  CASE plan_code
+    WHEN 'free_trial' THEN NULL
+    WHEN 'starter' THEN 'Overage: 160 Ft / min'
+    WHEN 'growth' THEN 'Overage: 150 Ft / min (Save 10!)'
+    WHEN 'scale' THEN 'Overage: 140 Ft / min (Best Rate)'
+  END,
+  CASE plan_code
+    WHEN 'growth' THEN 'MOST POPULAR'
+    ELSE NULL
+  END
+FROM subscription_plans;
+
+-- =============================================================================
+-- SEED DATA: Hungarian Translations
+-- =============================================================================
+
+INSERT INTO subscription_plan_translations (plan_id, language_code, name, description, period_label, overage_label, badge_text)
+SELECT id, 'hu',
+  CASE plan_code
+    WHEN 'free_trial' THEN '14 napos ingyenes próba (Sandbox)'
+    WHEN 'starter' THEN 'Starter'
+    WHEN 'growth' THEN 'Growth'
+    WHEN 'scale' THEN 'Scale'
+  END,
+  CASE plan_code
+    WHEN 'free_trial' THEN 'Teszteld az AI-t kockázatmentesen. 10 perc AI beszélgetési idő.'
+    WHEN 'starter' THEN 'Kis boltok, <5 hívás/nap'
+    WHEN 'growth' THEN 'Növekvő márkák, napi hívások'
+    WHEN 'scale' THEN 'Nagy forgalom / Szezonális'
+  END,
+  CASE plan_code
+    WHEN 'free_trial' THEN '/ 14 nap'
+    WHEN 'starter' THEN '/hó'
+    WHEN 'growth' THEN '/hó'
+    WHEN 'scale' THEN '/hó'
+  END,
+  CASE plan_code
+    WHEN 'free_trial' THEN NULL
+    WHEN 'starter' THEN 'Túlhasználat: 160 Ft / perc'
+    WHEN 'growth' THEN 'Túlhasználat: 150 Ft / perc (10 Ft megtakarítás!)'
+    WHEN 'scale' THEN 'Túlhasználat: 140 Ft / perc (Legjobb ár)'
+  END,
+  CASE plan_code
+    WHEN 'growth' THEN 'LEGNÉPSZERŰBB'
+    ELSE NULL
+  END
+FROM subscription_plans;
+
+-- =============================================================================
+-- SEED DATA: German Translations
+-- =============================================================================
+
+INSERT INTO subscription_plan_translations (plan_id, language_code, name, description, period_label, overage_label, badge_text)
+SELECT id, 'de',
+  CASE plan_code
+    WHEN 'free_trial' THEN '14-Tage kostenlose Testversion (Sandbox)'
+    WHEN 'starter' THEN 'Starter'
+    WHEN 'growth' THEN 'Growth'
+    WHEN 'scale' THEN 'Scale'
+  END,
+  CASE plan_code
+    WHEN 'free_trial' THEN 'Testen Sie die KI risikofrei. 10 Minuten KI-Gesprächszeit inklusive.'
+    WHEN 'starter' THEN 'Kleine Shops, <5 Anrufe/Tag'
+    WHEN 'growth' THEN 'Wachsende Marken, tägliche Anrufe'
+    WHEN 'scale' THEN 'Hohes Volumen / Saisonal'
+  END,
+  CASE plan_code
+    WHEN 'free_trial' THEN '/ 14 Tage'
+    WHEN 'starter' THEN '/Monat'
+    WHEN 'growth' THEN '/Monat'
+    WHEN 'scale' THEN '/Monat'
+  END,
+  CASE plan_code
+    WHEN 'free_trial' THEN NULL
+    WHEN 'starter' THEN 'Überschreitung: 0,45 € / Min'
+    WHEN 'growth' THEN 'Überschreitung: 0,42 € / Min (10% sparen!)'
+    WHEN 'scale' THEN 'Überschreitung: 0,40 € / Min (Bester Preis)'
+  END,
+  CASE plan_code
+    WHEN 'growth' THEN 'AM BELIEBTESTEN'
+    ELSE NULL
+  END
+FROM subscription_plans;
+
+-- =============================================================================
+-- SEED DATA: Plan Features
+-- =============================================================================
+
+-- Free Trial features
+INSERT INTO subscription_plan_features (plan_id, feature_code, sort_order, is_highlighted)
+SELECT id, 'ai_minutes_10', 1, true
+FROM subscription_plans WHERE plan_code = 'free_trial';
+
+-- Starter features
+INSERT INTO subscription_plan_features (plan_id, feature_code, sort_order, is_highlighted)
+SELECT sp.id, f.feature_code, f.sort_order, f.is_highlighted
+FROM subscription_plans sp
+CROSS JOIN (VALUES
+  ('minutes_40', 1, true),
+  ('dedicated_number', 2, false),
+  ('transfer_to_human', 3, false),
+  ('max_2500_products', 4, false),
+  ('availability_24_7', 5, false),
+  ('email_summaries', 6, false)
+) AS f(feature_code, sort_order, is_highlighted)
+WHERE sp.plan_code = 'starter';
+
+-- Growth features
+INSERT INTO subscription_plan_features (plan_id, feature_code, sort_order, is_highlighted)
+SELECT sp.id, f.feature_code, f.sort_order, f.is_highlighted
+FROM subscription_plans sp
+CROSS JOIN (VALUES
+  ('minutes_130', 1, true),
+  ('dedicated_number', 2, false),
+  ('transfer_to_human', 3, false),
+  ('working_hours', 4, false),
+  ('max_5000_products', 5, false),
+  ('order_status_lookup', 6, false),
+  ('call_analytics', 7, false),
+  ('custom_voice', 8, false)
+) AS f(feature_code, sort_order, is_highlighted)
+WHERE sp.plan_code = 'growth';
+
+-- Scale features
+INSERT INTO subscription_plan_features (plan_id, feature_code, sort_order, is_highlighted)
+SELECT sp.id, f.feature_code, f.sort_order, f.is_highlighted
+FROM subscription_plans sp
+CROSS JOIN (VALUES
+  ('minutes_350', 1, true),
+  ('transfer_to_human', 2, false),
+  ('working_hours', 3, false),
+  ('max_15000_products', 4, false),
+  ('order_status_lookup', 5, false),
+  ('call_analytics', 6, false),
+  ('priority_support', 7, false),
+  ('dedicated_account_manager', 8, false)
+) AS f(feature_code, sort_order, is_highlighted)
+WHERE sp.plan_code = 'scale';
+
+-- =============================================================================
+-- SEED DATA: Feature Translations - English
+-- =============================================================================
+
+INSERT INTO subscription_feature_translations (feature_id, language_code, label)
+SELECT spf.id, 'en',
+  CASE spf.feature_code
+    WHEN 'ai_minutes_10' THEN '10 Minutes of AI Talk Time'
+    WHEN 'minutes_40' THEN '40 Mins (approx 20 calls)'
+    WHEN 'minutes_130' THEN '130 Mins (approx 65 calls)'
+    WHEN 'minutes_350' THEN '350 Mins (approx 175 calls)'
+    WHEN 'dedicated_number' THEN 'Dedicated Number'
+    WHEN 'transfer_to_human' THEN 'Transfer call to human'
+    WHEN 'max_2500_products' THEN 'Max. 2500 products'
+    WHEN 'max_5000_products' THEN 'Max. 5000 products'
+    WHEN 'max_15000_products' THEN 'Max. 15000 products'
+    WHEN 'availability_24_7' THEN '24/7 Availability'
+    WHEN 'email_summaries' THEN 'Email Summaries'
+    WHEN 'working_hours' THEN 'Working hours setup'
+    WHEN 'order_status_lookup' THEN 'Order Status Lookup'
+    WHEN 'call_analytics' THEN 'Call Analytics Dashboard'
+    WHEN 'custom_voice' THEN 'Custom Voice Personality'
+    WHEN 'priority_support' THEN 'Priority support'
+    WHEN 'dedicated_account_manager' THEN 'Dedicated Account Manager'
+    ELSE spf.feature_code
+  END
+FROM subscription_plan_features spf;
+
+-- =============================================================================
+-- SEED DATA: Feature Translations - Hungarian
+-- =============================================================================
+
+INSERT INTO subscription_feature_translations (feature_id, language_code, label)
+SELECT spf.id, 'hu',
+  CASE spf.feature_code
+    WHEN 'ai_minutes_10' THEN '10 perc AI beszélgetési idő'
+    WHEN 'minutes_40' THEN '40 perc (kb. 20 hívás)'
+    WHEN 'minutes_130' THEN '130 perc (kb. 65 hívás)'
+    WHEN 'minutes_350' THEN '350 perc (kb. 175 hívás)'
+    WHEN 'dedicated_number' THEN 'Dedikált telefonszám'
+    WHEN 'transfer_to_human' THEN 'Hívás átirányítása emberhez'
+    WHEN 'max_2500_products' THEN 'Max. 2500 termék'
+    WHEN 'max_5000_products' THEN 'Max. 5000 termék'
+    WHEN 'max_15000_products' THEN 'Max. 15000 termék'
+    WHEN 'availability_24_7' THEN '0-24 elérhetőség'
+    WHEN 'email_summaries' THEN 'Email összefoglalók'
+    WHEN 'working_hours' THEN 'Munkaidő beállítás'
+    WHEN 'order_status_lookup' THEN 'Rendelés állapot lekérdezés'
+    WHEN 'call_analytics' THEN 'Hívás analitika irányítópult'
+    WHEN 'custom_voice' THEN 'Egyedi hang személyiség'
+    WHEN 'priority_support' THEN 'Elsőbbségi támogatás'
+    WHEN 'dedicated_account_manager' THEN 'Dedikált fiókmenedzser'
+    ELSE spf.feature_code
+  END
+FROM subscription_plan_features spf;
+
+-- =============================================================================
+-- SEED DATA: Feature Translations - German
+-- =============================================================================
+
+INSERT INTO subscription_feature_translations (feature_id, language_code, label)
+SELECT spf.id, 'de',
+  CASE spf.feature_code
+    WHEN 'ai_minutes_10' THEN '10 Minuten KI-Gesprächszeit'
+    WHEN 'minutes_40' THEN '40 Min (ca. 20 Anrufe)'
+    WHEN 'minutes_130' THEN '130 Min (ca. 65 Anrufe)'
+    WHEN 'minutes_350' THEN '350 Min (ca. 175 Anrufe)'
+    WHEN 'dedicated_number' THEN 'Dedizierte Nummer'
+    WHEN 'transfer_to_human' THEN 'Weiterleitung an Menschen'
+    WHEN 'max_2500_products' THEN 'Max. 2500 Produkte'
+    WHEN 'max_5000_products' THEN 'Max. 5000 Produkte'
+    WHEN 'max_15000_products' THEN 'Max. 15000 Produkte'
+    WHEN 'availability_24_7' THEN '24/7 Verfügbarkeit'
+    WHEN 'email_summaries' THEN 'E-Mail-Zusammenfassungen'
+    WHEN 'working_hours' THEN 'Arbeitszeiten-Einrichtung'
+    WHEN 'order_status_lookup' THEN 'Bestellstatus-Abfrage'
+    WHEN 'call_analytics' THEN 'Anruf-Analyse-Dashboard'
+    WHEN 'custom_voice' THEN 'Personalisierte Stimme'
+    WHEN 'priority_support' THEN 'Prioritäts-Support'
+    WHEN 'dedicated_account_manager' THEN 'Dedizierter Account Manager'
+    ELSE spf.feature_code
+  END
+FROM subscription_plan_features spf;
+
+-- =============================================================================
+-- SEED DATA: Additional Packages
+-- =============================================================================
+
+INSERT INTO additional_packages (package_code, sort_order, minutes, price_huf, price_eur, price_usd)
+VALUES
+  ('50_min', 1, 50, 7500, 21, 24),
+  ('100_min', 2, 100, 14000, 39, 45),
+  ('250_min', 3, 250, 32500, 91, 105);
+
+-- Package translations - English
+INSERT INTO additional_package_translations (package_id, language_code, name, description)
+SELECT id, 'en',
+  CASE package_code
+    WHEN '50_min' THEN '50 Minutes'
+    WHEN '100_min' THEN '100 Minutes'
+    WHEN '250_min' THEN '250 Minutes'
+  END,
+  CASE package_code
+    WHEN '50_min' THEN 'Add 50 extra minutes to your account'
+    WHEN '100_min' THEN 'Add 100 extra minutes to your account'
+    WHEN '250_min' THEN 'Best value - Add 250 extra minutes'
+  END
+FROM additional_packages;
+
+-- Package translations - Hungarian
+INSERT INTO additional_package_translations (package_id, language_code, name, description)
+SELECT id, 'hu',
+  CASE package_code
+    WHEN '50_min' THEN '50 perc'
+    WHEN '100_min' THEN '100 perc'
+    WHEN '250_min' THEN '250 perc'
+  END,
+  CASE package_code
+    WHEN '50_min' THEN 'Adj hozzá 50 extra percet a fiókodhoz'
+    WHEN '100_min' THEN 'Adj hozzá 100 extra percet a fiókodhoz'
+    WHEN '250_min' THEN 'Legjobb érték - Adj hozzá 250 extra percet'
+  END
+FROM additional_packages;
+
+-- Package translations - German
+INSERT INTO additional_package_translations (package_id, language_code, name, description)
+SELECT id, 'de',
+  CASE package_code
+    WHEN '50_min' THEN '50 Minuten'
+    WHEN '100_min' THEN '100 Minuten'
+    WHEN '250_min' THEN '250 Minuten'
+  END,
+  CASE package_code
+    WHEN '50_min' THEN 'Fügen Sie 50 zusätzliche Minuten hinzu'
+    WHEN '100_min' THEN 'Fügen Sie 100 zusätzliche Minuten hinzu'
+    WHEN '250_min' THEN 'Bester Wert - 250 zusätzliche Minuten'
+  END
+FROM additional_packages;
+
+-- =============================================================================
+-- ASSIGN FREE TRIAL TO EXISTING STORES
+-- =============================================================================
+
+-- For existing stores that don't have a subscription yet
+INSERT INTO store_subscriptions (
+  store_id,
+  plan_id,
+  status,
+  billing_period,
+  period_start,
+  period_end,
+  minutes_included,
+  currency
+)
+SELECT
+  s.id,
+  sp.id,
+  'trial'::subscription_status,
+  'trial'::billing_period,
+  COALESCE(s.connected_at, s.created_at, NOW()),
+  COALESCE(s.connected_at, s.created_at, NOW()) + INTERVAL '14 days',
+  sp.minutes_included,
+  'HUF'
+FROM stores s
+CROSS JOIN subscription_plans sp
+WHERE sp.plan_code = 'free_trial'
+  AND sp.is_active = true
+  AND NOT EXISTS (
+    SELECT 1 FROM store_subscriptions ss WHERE ss.store_id = s.id
+  );
+
+-- =============================================================================
+-- Migration Complete Notice
+-- =============================================================================
+
+DO $$
+BEGIN
+  RAISE NOTICE 'Subscription plans migration completed successfully';
+  RAISE NOTICE 'Created tables: subscription_plans, store_subscriptions, additional_packages, etc.';
+  RAISE NOTICE 'Seeded 4 plans with translations in en, hu, de';
+  RAISE NOTICE 'Auto-assign trigger will assign free trial to new stores';
+  RAISE NOTICE 'Existing stores have been assigned free trial';
+END $$;