diff --git a/components/OrderForm.tsx b/components/OrderForm.tsx index 98a8581..9837f48 100644 --- a/components/OrderForm.tsx +++ b/components/OrderForm.tsx @@ -230,7 +230,8 @@ export const OrderForm: React.FC = () => { if (error) throw error; - // 2. Initiate Stripe Payment if not custom price + // 2. Initiate Stripe Payment if NOT custom price and is a standard package + // Enterprise csomagnál ez a feltétel hamis lesz, így a fizetés kimarad. if (!isCustomPrice && (formData.package === 'Landing Page' || formData.package === 'Pro Web')) { try { const { data: checkoutData, error: checkoutError } = await supabase.functions.invoke('create-checkout-session', { @@ -242,19 +243,27 @@ export const OrderForm: React.FC = () => { } }); - if (checkoutError) throw checkoutError; + if (checkoutError) { + console.error("Invoke Error:", checkoutError); + throw new Error("A fizetési szerver nem válaszolt megfelelően. (Edge Function hiba)"); + } if (checkoutData?.url) { window.location.href = checkoutData.url; return; // Stop execution to redirect + } else { + if(checkoutData?.error) throw new Error(checkoutData.error); + throw new Error("Nem sikerült létrehozni a fizetési linket."); } } catch (stripeErr: any) { - console.error("Stripe error:", stripeErr); - alert("Rendelés rögzítve, de a fizetési rendszer átmenetileg nem elérhető. Kérjük vegye fel velünk a kapcsolatot."); + console.error("Stripe/Network error:", stripeErr); + const msg = stripeErr?.message || "Hálózati vagy konfigurációs hiba."; + alert(`Rendelés rögzítve (ID: ${orderData.id.slice(0,8)}), de a fizetési kapu megnyitása nem sikerült.\n\nHibaüzenet: ${msg}\n\nKérjük, ellenőrizze internetkapcsolatát, vagy vegye fel velünk a kapcsolatot.`); setIsSubmitted(true); } } else { - // Custom price or demo mode + // Enterprise / Custom price or demo mode + // Itt nincs Stripe hívás, csak simán befejezzük a folyamatot. setIsSubmitted(true); } diff --git a/motion-web-stúdió2.0.zip b/motion-web-stúdió2.0.zip deleted file mode 100644 index dc00cae..0000000 Binary files a/motion-web-stúdió2.0.zip and /dev/null differ diff --git a/pages/Dashboard.tsx b/pages/Dashboard.tsx index fb403c0..f26b362 100644 --- a/pages/Dashboard.tsx +++ b/pages/Dashboard.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react'; import { useAuth } from '../context/AuthContext'; import { Button } from '../components/Button'; import { LogOut, User, Settings as SettingsIcon, CreditCard, Layout, Cake, ShieldAlert, Clock, Activity, CheckCircle, XCircle, MessageSquare, ArrowRight, Edit2, Download, FileText, ExternalLink, History, RefreshCw, FileDown, ShieldCheck, Calendar } from 'lucide-react'; -import { Link, useNavigate } from 'react-router-dom'; +import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import { supabase, isSupabaseConfigured } from '../lib/supabaseClient'; import { SettingsModal } from '../components/SettingsModal'; import { FeedbackModal } from '../components/FeedbackModal'; @@ -47,6 +47,7 @@ interface UserOrder { export const Dashboard: React.FC = () => { const { user, signOut, isAdmin } = useAuth(); const navigate = useNavigate(); + const [searchParams] = useSearchParams(); const [profile, setProfile] = useState(null); const [orders, setOrders] = useState([]); const [invoices, setInvoices] = useState([]); @@ -57,6 +58,22 @@ export const Dashboard: React.FC = () => { const [selectedOrderId, setSelectedOrderId] = useState(null); const [feedbackLoading, setFeedbackLoading] = useState(false); + // Check for payment success/cancel query params + useEffect(() => { + const paymentSuccess = searchParams.get('payment_success'); + const orderIdParam = searchParams.get('order_id'); + + if (paymentSuccess === 'true' && orderIdParam) { + // Opcionális: Itt lehetne egy sikeres fizetés modált megjeleníteni vagy toast üzenetet + // A státusz frissítést (pl. 'pending_feedback' -> 'in_progress') a webhook végezné ideális esetben, + // de kliens oldalon is jelezhetjük a sikert. + // Mivel a redirect után újratölt az oldal, a fetchData friss adatokat hoz. + // Töröljük a query paramokat, hogy ne maradjanak ott + window.history.replaceState({}, '', '#/dashboard'); + alert("Sikeres fizetés! Köszönjük."); + } + }, [searchParams]); + const fetchOrderHistory = async (orderId: string) => { if (!isSupabaseConfigured) return []; try { @@ -188,22 +205,82 @@ export const Dashboard: React.FC = () => { return; } - const { data: currentOrder } = await supabase.from('orders').select('details').eq('id', selectedOrderId).single(); - const updatedDetails = { ...(currentOrder?.details || {}), latestFeedback: feedbackData, feedbackDate: new Date().toISOString() }; + // 1. Fetch fresh order data to get payment details + const { data: currentOrder } = await supabase.from('orders').select('*').eq('id', selectedOrderId).single(); + if (!currentOrder) throw new Error("Rendelés nem található"); + + const isApproved = feedbackData.decision === 'approved'; + const paymentSummary = currentOrder.details?.payment_summary; + const isStandardPackage = ['Landing Page', 'Pro Web'].includes(currentOrder.package); + + // Check if payment is needed: Approved + Standard Package + Not Custom Price + Has Remaining Balance + // Note: paymentSummary.remaining > 0 check is implicitly handled by Logic in FeedbackModal (it shows payment button only if true) + // But we double check here for security. + const needsPayment = isApproved && isStandardPackage && !paymentSummary?.is_custom && (paymentSummary?.remaining > 0); + + if (needsPayment) { + // Initiate Stripe Checkout for Final Payment + const { data: checkoutData, error: checkoutError } = await supabase.functions.invoke('create-checkout-session', { + body: { + order_id: selectedOrderId, + package_name: currentOrder.package, + payment_type: 'final', + customer_email: user?.email + } + }); + + if (checkoutError) { + console.error("Supabase Invoke Error:", checkoutError); + let msg = "Ismeretlen hiba"; + if (checkoutError && typeof checkoutError === 'object' && 'message' in checkoutError) { + msg = checkoutError.message; + } + throw new Error(`Fizetési rendszer hiba: ${msg}`); + } + + if (checkoutData?.url) { + // Update details with feedback content BEFORE redirecting + // This ensures we save the "Approval" state even if they drop off payment (status remains pending_feedback though) + const updatedDetails = { + ...(currentOrder.details || {}), + latestFeedback: feedbackData, + feedbackDate: new Date().toISOString() + }; + await supabase.from('orders').update({ details: updatedDetails }).eq('id', selectedOrderId); + + // Redirect to Stripe + window.location.href = checkoutData.url; + return; // Stop execution to allow redirect + } else { + throw new Error("Nem sikerült létrehozni a fizetési linket."); + } + } + + // If no payment needed (e.g. revision request, or custom price, or enterprise), update status directly + const updatedDetails = { + ...(currentOrder.details || {}), + latestFeedback: feedbackData, + feedbackDate: new Date().toISOString() + }; + + const newStatus = 'in_progress'; // Send back to dev in both cases (to finalize or revise) const { error: updateError } = await supabase.from('orders').update({ - status: 'in_progress', + status: newStatus, details: updatedDetails }).eq('id', selectedOrderId); if (updateError) throw updateError; - await supabase.from('order_status_history').insert({ order_id: selectedOrderId, status: 'in_progress' }); + await supabase.from('order_status_history').insert({ order_id: selectedOrderId, status: newStatus }); await fetchData(); setFeedbackModalOpen(false); + alert("Visszajelzés sikeresen elküldve!"); + } catch (err: any) { - alert("Hiba: " + err.message); + console.error("Feedback submit error:", err); + alert("Hiba: " + (err.message || "Ismeretlen hiba történt.")); } finally { setFeedbackLoading(false); } diff --git a/supabase/functions/create-checkout-session/index.ts b/supabase/functions/create-checkout-session/index.ts index 5c62ce5..a17e216 100644 --- a/supabase/functions/create-checkout-session/index.ts +++ b/supabase/functions/create-checkout-session/index.ts @@ -8,14 +8,17 @@ const corsHeaders = { } serve(async (req) => { + // Handle CORS preflight requests if (req.method === 'OPTIONS') { return new Response('ok', { headers: corsHeaders }) } try { + // 1. Check for Secret Key const STRIPE_SECRET_KEY = Deno.env.get('STRIPE_SECRET_KEY') if (!STRIPE_SECRET_KEY) { - throw new Error('STRIPE_SECRET_KEY not set in environment variables') + console.error('CRITICAL: STRIPE_SECRET_KEY is missing from Edge Function Secrets.') + throw new Error('Server configuration error: Missing Stripe Key. Please set it in Supabase Secrets.') } const stripe = new Stripe(STRIPE_SECRET_KEY, { @@ -23,40 +26,50 @@ serve(async (req) => { httpClient: Stripe.createFetchHttpClient(), }) + // 2. Parse Request Body const { order_id, package_name, payment_type, customer_email } = await req.json() - // Árazási logika (HUF-ban) - let priceAmount = 0 - - // Landing Page: 190.000 Ft (Előleg: 40.000, Vég: 150.000) - if (package_name === 'Landing Page') { - if (payment_type === 'deposit') priceAmount = 40000; - else if (payment_type === 'final') priceAmount = 150000; - } - // Pro Web: 350.000 Ft (Előleg: 80.000, Vég: 270.000) - else if (package_name === 'Pro Web') { - if (payment_type === 'deposit') priceAmount = 80000; - else if (payment_type === 'final') priceAmount = 270000; + console.log(`Processing checkout: Order ${order_id}, Package: ${package_name}, Type: ${payment_type}`); + + // 3. Map to Stripe Price IDs + // Ezeket a Price ID-kat a Stripe Dashboard-on hoztad létre a termékekhez. + // Ha még nincsenek, hozd létre őket, vagy használd a lenti kódokat teszteléshez. + let priceId = ''; + const pkg = package_name?.trim(); + const type = payment_type?.trim(); + + if (pkg === 'Landing Page') { + if (type === 'deposit') { + priceId = 'price_1SiIf725dc768V7U1zW8kBuN'; // Landing Page - Előleg + } else if (type === 'final') { + priceId = 'price_1SiIgw25dc768V7UrmEe2XRo'; // Landing Page - Fennmaradó + } + } else if (pkg === 'Pro Web') { + if (type === 'deposit') { + priceId = 'price_1SiIhq25dc768V7UlD8dx1I9'; // Pro Web - Előleg + } else if (type === 'final') { + priceId = 'price_1SiIif25dc768V7UqsfSi345'; // Pro Web - Fennmaradó + } + } else if (pkg === 'Maintenance' || pkg === 'Fenntartás') { + priceId = 'price_1SiIm025dc768V7UDFMZHDox'; // Éves fenntartás } - if (priceAmount === 0) { - throw new Error('Invalid package or payment type') + // 4. Validate Logic + if (!priceId) { + // Ha Enterprise csomag vagy ismeretlen kombináció érkezik, dobjunk hibát, + // de a frontendnek ezt elvileg nem szabadna meghívnia Enterprise-ra. + console.error(`No Price ID found for: ${pkg} - ${type}`); + throw new Error(`Nem található árazás ehhez a kombinációhoz: ${pkg} - ${type === 'deposit' ? 'Előleg' : 'Végszámla'}. (Enterprise esetén nem kellene itt lennünk)`); } - // Origin meghatározása a visszairányításhoz const origin = req.headers.get('origin') || 'https://motionweb.hu' + // 5. Create Session const session = await stripe.checkout.sessions.create({ payment_method_types: ['card'], line_items: [ { - price_data: { - currency: 'huf', - product_data: { - name: `${package_name} - ${payment_type === 'deposit' ? 'Előleg' : 'Fennmaradó összeg'}`, - }, - unit_amount: priceAmount * 100, // Fillérben kell megadni - }, + price: priceId, quantity: 1, }, ], @@ -66,7 +79,8 @@ serve(async (req) => { customer_email: customer_email, metadata: { order_id: order_id, - payment_type: payment_type + payment_type: payment_type, + package_name: package_name }, }) @@ -78,9 +92,9 @@ serve(async (req) => { } ) } catch (error: any) { - console.error(error) + console.error('Stripe Edge Function Error:', error) return new Response( - JSON.stringify({ error: error.message }), + JSON.stringify({ error: error.message || 'Unknown error in checkout session creation' }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, status: 400,