orderBy('price') ->get() ->map(fn (Package $package) => $this->presentPackage($package)) ->values() ->all(); return Inertia::render('marketing/Home', compact('packages')); } public function contact(Request $request) { $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|email|max:255', 'message' => 'required|string|max:1000', ]); $locale = app()->getLocale(); $contactAddress = config('mail.contact_address', config('mail.from.address')) ?: 'admin@fotospiel.de'; Mail::raw( __('emails.contact.body', [ 'name' => $request->name, 'email' => $request->email, 'message' => $request->message, ], $locale), function ($message) use ($request, $contactAddress, $locale) { $message->to($contactAddress) ->subject(__('emails.contact.subject', [], $locale)); } ); Mail::to($request->email) ->locale($locale) ->queue(new ContactConfirmation($request->name)); return redirect() ->back() ->with('success', __('marketing.contact.success', [], $locale)); } public function contactView() { return Inertia::render('marketing.Kontakt'); } /** * Handle package purchase flow. */ public function buyPackages(Request $request, $packageId) { Log::info('Buy packages called', ['auth' => Auth::check(), 'package_id' => $packageId, 'provider' => $request->input('provider', 'stripe')]); $package = Package::findOrFail($packageId); if (!Auth::check()) { return redirect()->route('register', ['package_id' => $package->id]) ->with('message', __('marketing.packages.register_required')); } $user = Auth::user(); if (!$user->email_verified_at) { return redirect()->route('verification.notice') ->with('message', __('auth.verification_required')); } $tenant = $user->tenant; if (!$tenant) { abort(500, 'Tenant not found'); } if ($package->price == 0) { TenantPackage::updateOrCreate( [ 'tenant_id' => $tenant->id, 'package_id' => $package->id, ], [ 'price' => $package->price, 'active' => true, 'purchased_at' => now(), 'expires_at' => now()->addYear(), ] ); PackagePurchase::create([ 'tenant_id' => $tenant->id, 'package_id' => $package->id, 'provider_id' => 'free', 'price' => $package->price, 'type' => $package->type === 'endcustomer' ? 'endcustomer_event' : 'reseller_subscription', 'purchased_at' => now(), 'refunded' => false, ]); return redirect('/event-admin')->with('success', __('marketing.packages.free_assigned')); } if ($package->type === 'reseller') { return $this->stripeSubscription($request, $packageId); } if ($request->input('provider') === 'paypal') { return $this->paypalCheckout($request, $packageId); } return $this->checkout($request, $packageId); } /** * Checkout for Stripe with auth metadata. */ public function checkout(Request $request, $packageId) { $package = Package::findOrFail($packageId); $user = Auth::user(); $tenant = $user->tenant; $stripe = new StripeClient(config('services.stripe.secret')); $session = $stripe->checkout->sessions->create([ 'payment_method_types' => ['card'], 'line_items' => [[ 'price_data' => [ 'currency' => 'eur', 'product_data' => [ 'name' => $package->name, ], 'unit_amount' => $package->price * 100, ], 'quantity' => 1, ]], 'mode' => 'payment', 'success_url' => route('marketing.success', $packageId), 'cancel_url' => route('packages'), 'metadata' => [ 'user_id' => $user->id, 'tenant_id' => $tenant->id, 'package_id' => $package->id, 'type' => $package->type, ], ]); Log::info('Stripe Checkout initiated', ['package_id' => $packageId, 'session_id' => $session->id, 'tenant_id' => $tenant->id]); return redirect($session->url, 303); } /** * PayPal checkout with v2 Orders API (one-time payment). */ public function paypalCheckout(Request $request, $packageId) { $package = Package::findOrFail($packageId); $user = Auth::user(); $tenant = $user->tenant; $client = Client::create([ 'clientId' => config('services.paypal.client_id'), 'clientSecret' => config('services.paypal.secret'), 'environment' => config('services.paypal.sandbox', true) ? 'sandbox' : 'live', ]); $ordersController = $client->orders(); $metadata = json_encode([ 'user_id' => $user->id, 'tenant_id' => $tenant->id, 'package_id' => $package->id, 'type' => $package->type, ]); $createRequest = new OrdersCreateRequest(); $createRequest->prefer('return=representation'); $createRequest->body = [ "intent" => "CAPTURE", "purchase_units" => [[ "amount" => [ "currency_code" => "EUR", "value" => number_format($package->price, 2, '.', ''), ], "description" => "Package: " . $package->name, "custom_id" => $metadata, ]], "application_context" => [ "return_url" => route('marketing.success', $packageId), "cancel_url" => route('packages'), ], ]; try { $response = $ordersController->createOrder($createRequest); $order = $response->result; Log::info('PayPal Checkout initiated', ['package_id' => $packageId, 'order_id' => $order->id, 'tenant_id' => $tenant->id]); session(['paypal_order_id' => $order->id]); foreach ($order->links as $link) { if ($link->rel === 'approve') { return redirect($link->href); } } throw new Exception('No approve link found'); } catch (HttpException $e) { Log::error('PayPal Orders API error: ' . $e->getMessage()); return back()->with('error', 'Zahlung fehlgeschlagen'); } catch (Exception $e) { Log::error('PayPal checkout error: ' . $e->getMessage()); return back()->with('error', 'Zahlung fehlgeschlagen'); } } /** * Stripe subscription checkout for reseller packages. */ public function stripeSubscription(Request $request, $packageId) { $package = Package::findOrFail($packageId); $user = Auth::user(); $tenant = $user->tenant; $stripe = new StripeClient(config('services.stripe.secret')); $session = $stripe->checkout->sessions->create([ 'payment_method_types' => ['card'], 'line_items' => [[ 'price_data' => [ 'currency' => 'eur', 'product_data' => [ 'name' => $package->name . ' (Annual Subscription)', ], 'unit_amount' => $package->price * 100, 'recurring' => [ 'interval' => 'year', 'interval_count' => 1, ], ], 'quantity' => 1, ]], 'mode' => 'subscription', 'success_url' => route('marketing.success', $packageId), 'cancel_url' => route('packages'), 'metadata' => [ 'user_id' => $user->id, 'tenant_id' => $tenant->id, 'package_id' => $package->id, 'type' => $package->type, 'subscription' => 'true', ], ]); return redirect($session->url, 303); } public function stripeCheckout($sessionId) { // Handle Stripe success return view('marketing.success', ['provider' => 'Stripe']); } /** * Handle success after payment (capture PayPal, redirect if verified). */ public function success(Request $request, $packageId = null) { $provider = session('paypal_order_id') ? 'paypal' : 'stripe'; Log::info('Payment Success: Provider processed', ['provider' => $provider, 'package_id' => $packageId]); if (session('paypal_order_id')) { $orderId = session('paypal_order_id'); $client = Client::create([ 'clientId' => config('services.paypal.client_id'), 'clientSecret' => config('services.paypal.secret'), 'environment' => config('services.paypal.sandbox', true) ? 'sandbox' : 'live', ]); $ordersController = $client->orders(); $captureRequest = new OrdersCaptureRequest($orderId); $captureRequest->prefer('return=minimal'); try { $captureResponse = $ordersController->captureOrder($captureRequest); $capture = $captureResponse->result; Log::info('PayPal Capture completed', ['order_id' => $orderId, 'status' => $capture->status]); if ($capture->status === 'COMPLETED') { $customId = $capture->purchaseUnits[0]->customId ?? null; if ($customId) { $metadata = json_decode($customId, true); $package = Package::find($metadata['package_id']); $tenant = Tenant::find($metadata['tenant_id']); if ($package && $tenant) { TenantPackage::updateOrCreate( [ 'tenant_id' => $tenant->id, 'package_id' => $package->id, ], [ 'price' => $package->price, 'active' => true, 'purchased_at' => now(), 'expires_at' => now()->addYear(), // One-time as annual for reseller too ] ); PackagePurchase::create([ 'tenant_id' => $tenant->id, 'package_id' => $package->id, 'provider_id' => 'paypal', 'price' => $package->price, 'type' => $package->type === 'endcustomer' ? 'endcustomer_event' : 'reseller_subscription', 'purchased_at' => now(), 'refunded' => false, ]); session()->forget('paypal_order_id'); $request->session()->flash('success', __('marketing.packages.purchased_successfully', ['name' => $package->name])); } } } else { Log::error('PayPal capture failed: ' . $capture->status); $request->session()->flash('error', 'Zahlung konnte nicht abgeschlossen werden.'); } } catch (HttpException $e) { Log::error('PayPal capture error: ' . $e->getMessage()); $request->session()->flash('error', 'Zahlung konnte nicht abgeschlossen werden.'); } catch (\Exception $e) { Log::error('PayPal success error: ' . $e->getMessage()); $request->session()->flash('error', 'Fehler beim Abschliessen der Zahlung.'); } } // Common logic: Redirect to admin if verified if (Auth::check() && Auth::user()->email_verified_at) { return redirect('/event-admin')->with('success', __('marketing.success.welcome')); } return Inertia::render('marketing/Success', compact('packageId')); } public function blogIndex(Request $request) { $locale = $request->get('locale', app()->getLocale()); Log::info('Blog Index Debug - Initial', [ 'locale' => $locale, 'full_url' => $request->fullUrl() ]); $query = BlogPost::query() ->with('author') ->whereHas('category', function ($query) { $query->where('slug', 'blog'); }); $totalWithCategory = $query->count(); Log::info('Blog Index Debug - With Category', ['count' => $totalWithCategory]); $query->where('is_published', true) ->whereNotNull('published_at') ->where('published_at', '<=', now()); $totalPublished = $query->count(); Log::info('Blog Index Debug - Published', ['count' => $totalPublished]); // Removed translation filter for now $totalWithTranslation = $query->count(); Log::info('Blog Index Debug - With Translation', ['count' => $totalWithTranslation, 'locale' => $locale]); $posts = $query->orderBy('published_at', 'desc') ->paginate(8); // Transform posts to include translated strings for the current locale $posts->getCollection()->transform(function ($post) use ($locale) { $post->title = $post->getTranslation('title', $locale) ?? $post->getTranslation('title', 'de') ?? ''; $post->excerpt = $post->getTranslation('excerpt', $locale) ?? $post->getTranslation('excerpt', 'de') ?? ''; $post->content = $post->getTranslation('content', $locale) ?? $post->getTranslation('content', 'de') ?? ''; // Author name is a string, no translation needed; author is loaded via with('author') return $post; }); Log::info('Blog Index Debug - Final Posts', [ 'count' => $posts->count(), 'total' => $posts->total(), 'posts_data' => $posts->toArray(), 'first_post_title' => $posts->count() > 0 ? $posts->first()->title : 'No posts' ]); return Inertia::render('marketing/Blog', compact('posts')); } public function blogShow($slug) { $locale = app()->getLocale(); $postModel = BlogPost::query() ->with('author') ->whereHas('category', function ($query) { $query->where('slug', 'blog'); }) ->where('slug', $slug) ->where('is_published', true) ->whereNotNull('published_at') ->where('published_at', '<=', now()) // Removed translation filter for now ->firstOrFail(); // Transform to array with translated strings for the current locale $markdown = $postModel->getTranslation('content', $locale) ?? $postModel->getTranslation('content', 'de') ?? ''; $environment = new Environment(); $environment->addExtension(new CommonMarkCoreExtension()); $environment->addExtension(new TableExtension()); $environment->addExtension(new AutolinkExtension()); $environment->addExtension(new StrikethroughExtension()); $environment->addExtension(new TaskListExtension()); $converter = new MarkdownConverter($environment); $contentHtml = (string) $converter->convert($markdown); // Debug log for content_html \Log::info('BlogShow Debug: content_html type and preview', [ 'type' => gettype($contentHtml), 'is_string' => is_string($contentHtml), 'length' => strlen($contentHtml ?? ''), 'preview' => substr((string)$contentHtml, 0, 200) . '...' ]); $post = [ 'id' => $postModel->id, 'title' => $postModel->getTranslation('title', $locale) ?? $postModel->getTranslation('title', 'de') ?? '', 'excerpt' => $postModel->getTranslation('excerpt', $locale) ?? $postModel->getTranslation('excerpt', 'de') ?? '', 'content' => $markdown, 'content_html' => $contentHtml, 'featured_image' => $postModel->featured_image ?? $postModel->banner_url ?? null, 'published_at' => $postModel->published_at->toDateString(), 'slug' => $postModel->slug, 'author' => $postModel->author ? [ 'name' => $postModel->author->name ] : null, ]; // Debug log for final postArray \Log::info('BlogShow Debug: Final post content_html', [ 'type' => gettype($post['content_html']), 'is_string' => is_string($post['content_html']), 'length' => strlen($post['content_html'] ?? ''), ]); return Inertia::render('marketing/BlogShow', compact('post')); } public function packagesIndex() { $endcustomerPackages = Package::where('type', 'endcustomer') ->orderBy('price') ->get() ->map(fn (Package $package) => $this->presentPackage($package)) ->values() ->all(); $resellerPackages = Package::where('type', 'reseller') ->orderBy('price') ->get() ->map(fn (Package $package) => $this->presentPackage($package)) ->values() ->all(); return Inertia::render('marketing/Packages', [ 'endcustomerPackages' => $endcustomerPackages, 'resellerPackages' => $resellerPackages, ]); } public function occasionsType($type) { Log::info('OccasionsType hit', [ 'type' => $type, 'locale' => app()->getLocale(), 'url' => request()->fullUrl(), 'route' => request()->route()->getName(), 'isInertia' => request()->header('X-Inertia') ]); $validTypes = ['hochzeit', 'geburtstag', 'firmenevent']; if (!in_array($type, $validTypes)) { Log::warning('Invalid occasion type accessed', ['type' => $type]); abort(404, 'Invalid occasion type'); } return Inertia::render('marketing/Occasions', ['type' => $type]); } }