orderBy('price')->get()->map(function ($p) { return $p->append(['features', 'limits']); }); 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', ]); Mail::raw("Kontakt-Anfrage von {$request->name} ({$request->email}): {$request->message}", function ($message) use ($request) { $message->to('admin@fotospiel.de') ->subject('Neue Kontakt-Anfrage'); }); return redirect()->back()->with('success', 'Nachricht gesendet!'); } 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]); $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('/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, ], ]); 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; 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) { 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; 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 Abschließen der Zahlung.'); } } // Common logic: Redirect to admin if verified if (Auth::check() && Auth::user()->email_verified_at) { return redirect('/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() ->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]); $query->whereJsonContains("translations->locale->title->{$locale}", true); $totalWithTranslation = $query->count(); Log::info('Blog Index Debug - With Translation', ['count' => $totalWithTranslation, 'locale' => $locale]); $posts = $query->orderBy('published_at', 'desc') ->paginate(8); Log::info('Blog Index Debug - Final Posts', ['count' => $posts->count(), 'total' => $posts->total()]); return Inertia::render('marketing/Blog', compact('posts')); } public function blogShow($slug) { $locale = app()->getLocale(); $post = BlogPost::query() ->whereHas('category', function ($query) { $query->where('slug', 'blog'); }) ->where('slug', $slug) ->where('is_published', true) ->whereNotNull('published_at') ->where('published_at', '<=', now()) ->whereJsonContains("translations->locale->title->{$locale}", true) ->firstOrFail(); return Inertia::render('marketing/BlogShow', compact('post')); } public function packagesIndex() { $endcustomerPackages = Package::where('type', 'endcustomer')->orderBy('price')->get()->map(function ($p) { return $p->append(['features', 'limits']); }); $resellerPackages = Package::where('type', 'reseller')->orderBy('price')->get()->map(function ($p) { return $p->append(['features', 'limits']); }); return Inertia::render('marketing/Packages', compact('endcustomerPackages', '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]); } }