From 492b9b9fd1186c6145c78070ec34a643199e347d Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Fri, 26 Sep 2025 15:40:37 +0200 Subject: [PATCH] added marketing page and moved events&general landing page --- app/Http/Controllers/MarketingController.php | 207 ++++++++++++++++++ .../Controllers/PayPalWebhookController.php | 56 +++++ composer.json | 1 + composer.lock | 55 ++++- resources/js/guest/router.tsx | 2 +- .../js/layouts/auth/auth-simple-layout.tsx | 4 +- resources/views/guest.blade.php | 2 +- resources/views/legal/datenschutz.blade.php | 16 ++ resources/views/legal/impressum.blade.php | 21 ++ resources/views/marketing.blade.php | 205 +++++++++++++++++ resources/views/marketing/success.blade.php | 13 ++ routes/web.php | 40 +++- 12 files changed, 609 insertions(+), 13 deletions(-) create mode 100644 app/Http/Controllers/MarketingController.php create mode 100644 app/Http/Controllers/PayPalWebhookController.php create mode 100644 resources/views/legal/datenschutz.blade.php create mode 100644 resources/views/legal/impressum.blade.php create mode 100644 resources/views/marketing.blade.php create mode 100644 resources/views/marketing/success.blade.php diff --git a/app/Http/Controllers/MarketingController.php b/app/Http/Controllers/MarketingController.php new file mode 100644 index 0000000..f45323c --- /dev/null +++ b/app/Http/Controllers/MarketingController.php @@ -0,0 +1,207 @@ + 'basic', 'name' => 'Basic', 'events' => 1, 'price' => 0, 'description' => '1 Event, 100 Fotos, Grundfunktionen'], + ['id' => 'standard', 'name' => 'Standard', 'events' => 10, 'price' => 99, 'description' => '10 Events, Unbegrenzt Fotos, Erweiterte Features'], + ['id' => 'premium', 'name' => 'Premium', 'events' => 50, 'price' => 199, 'description' => '50 Events, Support & Custom, Alle Features'], + ]; + + return view('marketing', 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 checkout(Request $request, $package) + { + $packages = [ + 'basic' => ['name' => 'Basic', 'price' => 0, 'events' => 1], + 'standard' => ['name' => 'Standard', 'price' => 9900, 'events' => 10], // cents + 'premium' => ['name' => 'Premium', 'price' => 19900, 'events' => 50], + ]; + + if (!isset($packages[$package])) { + abort(404); + } + + $pkg = $packages[$package]; + + if ($pkg['price'] == 0) { + // Free package: create tenant and event + $tenant = Tenant::create([ + 'name' => $request->input('tenant_name', 'New Tenant'), + 'slug' => Str::slug('new-' . now()), + 'email' => $request->input('email'), + 'events_remaining' => $pkg['events'], + ]); + + // Create initial event + $event = $tenant->events()->create([ + 'name' => $request->input('event_name', 'My Event'), + 'slug' => Str::slug($request->input('event_name', 'my-event')), + 'status' => 'active', + ]); + + $purchase = EventPurchase::create([ + 'tenant_id' => $tenant->id, + 'events_purchased' => $pkg['events'], + 'amount' => 0, + 'currency' => 'EUR', + 'provider' => 'free', + 'status' => 'completed', + 'purchased_at' => now(), + ]); + + return redirect("/admin/tenants/{$tenant->id}/edit")->with('success', 'Konto erstellt! Willkommen bei Fotospiel.'); + } + + $stripe = new \Stripe\StripeClient(config('services.stripe.secret')); + $session = $stripe->checkout->sessions->create([ + 'payment_method_types' => ['card'], + 'line_items' => [[ + 'price_data' => [ + 'currency' => 'eur', + 'product_data' => [ + 'name' => $pkg['name'] . ' Package', + ], + 'unit_amount' => $pkg['price'], + ], + 'quantity' => 1, + ]], + 'mode' => 'payment', + 'success_url' => route('marketing.success', $package), + 'cancel_url' => route('marketing'), + 'metadata' => [ + 'package' => $package, + 'events' => $pkg['events'], + ], + ]); + + return redirect($session->url, 303); + } + + public function stripeCheckout($sessionId) + { + // Handle Stripe success + return view('marketing.success', ['provider' => 'Stripe']); + } + + public function paypalCheckout(Request $request, $package) + { + $packages = [ + 'basic' => ['name' => 'Basic', 'price' => 0, 'events' => 1], + 'standard' => ['name' => 'Standard', 'price' => 99, 'events' => 10], + 'premium' => ['name' => 'Premium', 'price' => 199, 'events' => 50], + ]; + + if (!isset($packages[$package])) { + abort(404); + } + + $pkg = $packages[$package]; + + if ($pkg['price'] == 0) { + // Free package: create tenant and event + $tenant = Tenant::create([ + 'name' => $request->input('tenant_name', 'New Tenant'), + 'slug' => Str::slug('new-' . now()), + 'email' => $request->input('email'), + 'events_remaining' => $pkg['events'], + ]); + + // Create initial event + $event = $tenant->events()->create([ + 'name' => $request->input('event_name', 'My Event'), + 'slug' => Str::slug($request->input('event_name', 'my-event')), + 'status' => 'active', + ]); + + $purchase = EventPurchase::create([ + 'tenant_id' => $tenant->id, + 'events_purchased' => $pkg['events'], + 'amount' => 0, + 'currency' => 'EUR', + 'provider' => 'free', + 'status' => 'completed', + 'purchased_at' => now(), + ]); + + return redirect("/admin/tenants/{$tenant->id}/edit")->with('success', 'Konto erstellt! Willkommen bei Fotospiel.'); + } + + $apiContext = new ApiContext( + new OAuthTokenCredential( + config('services.paypal.client_id'), + config('services.paypal.secret') + ) + ); + + $payment = new Payment(); + $payer = new Payer(); + $payer->setPaymentMethod('paypal'); + + $amountObj = new Amount(); + $amountObj->setCurrency('EUR'); + $amountObj->setTotal($pkg['price']); + + $transaction = new Transaction(); + $transaction->setAmount($amountObj); + + $redirectUrls = new RedirectUrls(); + $redirectUrls->setReturnUrl(route('marketing.success', $package)); + $redirectUrls->setCancelUrl(route('marketing')); + + $payment->setIntent('sale') + ->setPayer($payer) + ->setTransactions([$transaction]) + ->setRedirectUrls($redirectUrls); + + try { + $payment->create($apiContext); + return redirect($payment->getApprovalLink()); + } catch (Exception $e) { + return back()->with('error', 'Zahlung fehlgeschlagen'); + } + } +} diff --git a/app/Http/Controllers/PayPalWebhookController.php b/app/Http/Controllers/PayPalWebhookController.php new file mode 100644 index 0000000..b77198f --- /dev/null +++ b/app/Http/Controllers/PayPalWebhookController.php @@ -0,0 +1,56 @@ +all(); + $ipnMessage = $input['ipn_track_id'] ?? null; + $payerEmail = $input['payer_email'] ?? null; + $paymentStatus = $input['payment_status'] ?? null; + $mcGross = $input['mc_gross'] ?? 0; + $packageId = $input['custom'] ?? null; + + if ($paymentStatus === 'Completed' && $mcGross > 0) { + // Verify IPN with PayPal (simplified; use SDK for full verification) + // $verified = $this->verifyIPN($input); + + // Find or create tenant (for public checkout, perhaps create new or use session) + // For now, assume tenant_id from custom or session + $tenantId = $packageId ? Tenant::where('slug', $packageId)->first()->id ?? 1 : 1; + + // Create purchase and increment credits + $purchase = EventPurchase::create([ + 'tenant_id' => $tenantId, // Implement tenant resolution + 'events_purchased' => $mcGross / 49, // Example: 49€ per event credit + 'amount' => $mcGross, + 'currency' => $input['mc_currency'] ?? 'EUR', + 'provider' => 'paypal', + 'external_receipt_id' => $ipnMessage, + 'status' => 'completed', + 'purchased_at' => now(), + ]); + + $tenant = Tenant::find($tenantId); + $tenant->incrementCredits($purchase->events_purchased, 'paypal_purchase', 'PayPal IPN', $purchase->id); + + Log::info('PayPal IPN processed', $input); + } + + return response('OK', 200); + } + + private function verifyIPN($input) + { + // Use PayPal SDK to verify + // Return true/false + return true; // Placeholder + } +} diff --git a/composer.json b/composer.json index 365a733..2635200 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "laravel/sanctum": "^4.2", "laravel/tinker": "^2.10.1", "laravel/wayfinder": "^0.1.9", + "paypal/rest-api-sdk-php": "^1.6", "simplesoftwareio/simple-qrcode": "^4.2", "stripe/stripe-php": "^17.6" }, diff --git a/composer.lock b/composer.lock index 35605cc..9e08ec6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "855707cf018e10451bf627dbd6593f0a", + "content-hash": "b0eebcaa1d38fb0a00e546fad7bdc991", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -4407,6 +4407,59 @@ }, "time": "2024-05-08T12:36:18+00:00" }, + { + "name": "paypal/rest-api-sdk-php", + "version": "v1.6.4", + "source": { + "type": "git", + "url": "https://github.com/paypal/PayPal-PHP-SDK.git", + "reference": "06837d290c4906578cfd92786412dff330a1429c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paypal/PayPal-PHP-SDK/zipball/06837d290c4906578cfd92786412dff330a1429c", + "reference": "06837d290c4906578cfd92786412dff330a1429c", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "PayPal": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "authors": [ + { + "name": "PayPal", + "homepage": "https://github.com/paypal/rest-api-sdk-php/contributors" + } + ], + "description": "PayPal's PHP SDK for REST APIs", + "homepage": "http://paypal.github.io/PayPal-PHP-SDK/", + "keywords": [ + "payments", + "paypal", + "rest", + "sdk" + ], + "support": { + "issues": "https://github.com/paypal/PayPal-PHP-SDK/issues", + "source": "https://github.com/paypal/PayPal-PHP-SDK/tree/stable" + }, + "abandoned": "paypal/paypal-server-sdk", + "time": "2016-01-20T17:45:52+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.4", diff --git a/resources/js/guest/router.tsx b/resources/js/guest/router.tsx index f1edfe3..0b352aa 100644 --- a/resources/js/guest/router.tsx +++ b/resources/js/guest/router.tsx @@ -50,7 +50,7 @@ function HomeLayout() { } export const router = createBrowserRouter([ - { path: '/', element: }, + { path: '/event', element: }, { path: '/setup/:slug', element: , diff --git a/resources/js/layouts/auth/auth-simple-layout.tsx b/resources/js/layouts/auth/auth-simple-layout.tsx index 68c0192..4ecb949 100644 --- a/resources/js/layouts/auth/auth-simple-layout.tsx +++ b/resources/js/layouts/auth/auth-simple-layout.tsx @@ -1,5 +1,5 @@ import AppLogoIcon from '@/components/app-logo-icon'; -import { home } from '@/routes'; +import { marketing } from '@/routes'; import { Link } from '@inertiajs/react'; import { type PropsWithChildren } from 'react'; @@ -15,7 +15,7 @@ export default function AuthSimpleLayout({ children, title, description }: Props
- +
diff --git a/resources/views/guest.blade.php b/resources/views/guest.blade.php index 5cac0b1..b4472ad 100644 --- a/resources/views/guest.blade.php +++ b/resources/views/guest.blade.php @@ -6,7 +6,7 @@ {{ config('app.name', 'Fotospiel') }} @viteReactRefresh - @vite('resources/js/guest/main.tsx') + @vite(['resources/css/app.css', 'resources/js/guest/main.tsx'])
diff --git a/resources/views/legal/datenschutz.blade.php b/resources/views/legal/datenschutz.blade.php new file mode 100644 index 0000000..2636cb6 --- /dev/null +++ b/resources/views/legal/datenschutz.blade.php @@ -0,0 +1,16 @@ + + + + + + Datenschutzerklärung - Fotospiel + + +

Datenschutzerklärung

+

Wir nehmen den Schutz Ihrer persönlichen Daten sehr ernst und halten uns strikt an die Regeln der Datenschutzgesetze.

+

Verantwortlich: Fotospiel GmbH, Musterstraße 1, 12345 Musterstadt

+

Datenerfassung: Keine PII-Speicherung, anonyme Sessions für Gäste. E-Mails werden nur für Kontaktzwecke verarbeitet.

+

Ihre Rechte: Auskunft, Löschung, Widerspruch. Kontaktieren Sie uns unter Kontakt.

+

Cookies: Nur funktionale Cookies für die PWA.

+ + \ No newline at end of file diff --git a/resources/views/legal/impressum.blade.php b/resources/views/legal/impressum.blade.php new file mode 100644 index 0000000..0658f7a --- /dev/null +++ b/resources/views/legal/impressum.blade.php @@ -0,0 +1,21 @@ + + + + + + Impressum - Fotospiel + + + +

Impressum

+

Angaben gemäß § 5 TMG

+

Fotospiel GmbH
+ Musterstraße 1
+ 12345 Musterstadt
+ Vertreten durch: Max Mustermann
+ Kontakt: Kontakt

+

Umsatzsteuer-ID: DE123456789

+

Registergericht: Amtsgericht Musterstadt

+

Handelsregister: HRB 12345

+ + \ No newline at end of file diff --git a/resources/views/marketing.blade.php b/resources/views/marketing.blade.php new file mode 100644 index 0000000..b3f5184 --- /dev/null +++ b/resources/views/marketing.blade.php @@ -0,0 +1,205 @@ + + + + + + Fotospiel - Event-Fotos einfach und sicher + + + @vite(['resources/css/app.css']) + + + +
+
+

Fotospiel

+

Erleben Sie Events durch professionelle Fotos. Unsere sichere PWA-Plattform für Gäste und Organisatoren. Einfach, mobil und datenschutzkonform.

+ Jetzt starten – Kostenlos + Event-Fotos +
+
+ + +
+
+

Warum Fotospiel?

+
+
+
+ +
+

Sichere Uploads

+

GDPR-konform, anonyme Sessions, keine PII-Speicherung.

+
+
+
+ +
+

Mobile PWA

+

Offline-fähig, App-ähnlich, für iOS und Android.

+
+
+
+ +
+

Schnell & Einfach

+

Automatische Thumbnails, Echtzeit-Updates, einfache Bedienung.

+
+
+
+
+ + +
+
+

So funktioniert es – in 4 einfachen Schritten

+
+
+
1
+

Event erstellen

+

Als Organisator: Registrieren, Event anlegen, Gäste einladen.

+
+
+
2
+

Fotos hochladen

+

Gäste: PWA öffnen, Fotos via Kamera oder Galerie teilen.

+
+
+
3
+

Freigaben & Likes

+

Emotions auswählen, Fotos liken, Galerie browsen.

+
+
+
4
+

Download & Teilen

+

Freigegebene Fotos herunterladen, Event abschließen.

+
+
+
+
+ + +
+
+

Tarife

+
+
+

Basic

+

0 €

+
    +
  • 1 Event
  • +
  • 100 Fotos
  • +
  • Grundfunktionen
  • +
+ Kostenlos starten +
+
+

Standard

+

99 €

+
    +
  • 10 Events
  • +
  • Unbegrenzt Fotos
  • +
  • Erweiterte Features
  • +
+ Kaufen +
+
+

Premium

+

199 €

+
    +
  • 50 Events
  • +
  • Support & Custom
  • +
  • Alle Features
  • +
+ Kaufen +
+
+
+
+ + +
+
+

Kontakt

+
+ @csrf +
+ + +
+
+ + +
+
+ + +
+ +
+ @if (session('success')) +

{{ session('success') }}

+ @endif + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif +
+
+ + +
+
+

Was unsere Kunden sagen

+
+
+

"Perfekt für unsere Hochzeit! Einfach und sicher."

+

- Anna & Max

+
+
+

"Großes Firmenevent – alle Fotos zentral und mobil."

+

- Team XYZ GmbH

+
+
+
+
+ + +
+
+

Häufige Fragen

+
+
+

Ist es kostenlos?

+

Ja, der Basic-Tarif ist kostenlos für 1 Event. Upgrades ab 99€.

+
+
+

Datenschutz?

+

100% GDPR-konform. Keine personenbezogenen Daten gespeichert. Siehe Datenschutzerklärung.

+
+
+

Wie lade ich Fotos hoch?

+

Über die PWA-App: Kamera oder Galerie, Emotions zuweisen, teilen.

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/resources/views/marketing/success.blade.php b/resources/views/marketing/success.blade.php new file mode 100644 index 0000000..bc31f28 --- /dev/null +++ b/resources/views/marketing/success.blade.php @@ -0,0 +1,13 @@ + + + + + + Zahlung erfolgreich - Fotospiel + + +

Zahlung erfolgreich!

+

Vielen Dank für Ihren Kauf. Ihr Konto wurde aktualisiert.

+ Zum Admin-Dashboard + + \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 37b763a..9fdb423 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,8 +3,8 @@ use Illuminate\Support\Facades\Route; use Inertia\Inertia; -// Public landing: serve Guest PWA shell at root -Route::view('/', 'guest')->name('home'); + // Marketing-Seite unter Root +Route::view('/', 'marketing')->name('marketing'); Route::middleware(['auth', 'verified'])->group(function () { Route::get('dashboard', function () { @@ -15,19 +15,32 @@ Route::middleware(['auth', 'verified'])->group(function () { require __DIR__.'/settings.php'; require __DIR__.'/auth.php'; -// Guest PWA shell (served for event and /pwa paths; React handles routing) + // Guest PWA shell for /event and sub-routes +Route::view('/event/{any?}', 'guest')->where('any', '.*'); Route::view('/e/{any?}', 'guest')->where('any', '.*'); Route::view('/pwa/{any?}', 'guest')->where('any', '.*'); -Route::view('/legal/{any?}', 'guest')->where('any', '.*'); // Minimal public API for Guest PWA (stateless; no CSRF) Route::prefix('api/v1')->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class])->group(function () { - // Public legal pages - Route::get('/legal/{slug}', [\App\Http\Controllers\Api\LegalController::class, 'show']); + // Public legal pages (for marketing) + Route::get('/impressum', function () { + return view('legal.impressum'); + })->name('impressum'); + Route::get('/datenschutz', function () { + return view('legal.datenschutz'); + })->name('datenschutz'); + Route::get('/kontakt', function () { + return view('legal.kontakt'); + })->name('kontakt'); + Route::post('/kontakt', [\App\Http\Controllers\MarketingController::class, 'contact'])->name('kontakt.submit'); }); -// Stripe webhooks (no CSRF, no auth) -Route::post('/webhooks/stripe', [\App\Http\Controllers\StripeWebhookController::class, 'handle']); + // Stripe webhooks (no CSRF, no auth) + Route::post('/webhooks/stripe', [\App\Http\Controllers\StripeWebhookController::class, 'handle']); + // PayPal IPN webhook + Route::post('/webhooks/paypal', [\App\Http\Controllers\PayPalWebhookController::class, 'handle']); + // PayPal IPN webhook +Route::post('/webhooks/paypal', [\App\Http\Controllers\PayPalWebhookController::class, 'handle']); // CSV templates for Super Admin imports Route::get('/super-admin/templates/emotions.csv', function () { @@ -60,3 +73,14 @@ Route::get('/super-admin/templates/tasks.csv', function () { }; return response()->stream($callback, 200, $headers); }); + +// E-Commerce Routen für Marketing +Route::get('/buy-credits/{package}', [\App\Http\Controllers\MarketingController::class, 'checkout'])->name('buy.credits'); +Route::get('/checkout/{sessionId}', [\App\Http\Controllers\MarketingController::class, 'stripeCheckout']); +Route::get('/paypal-checkout/{package}', [\App\Http\Controllers\MarketingController::class, 'paypalCheckout']); +Route::get('/marketing/success/{package}', [\App\Http\Controllers\MarketingController::class, 'success'])->name('marketing.success'); + +// E-Commerce Routen für Marketing +Route::get('/buy-credits/{package}', [\App\Http\Controllers\MarketingController::class, 'checkout'])->name('buy.credits'); +Route::get('/checkout/{sessionId}', [\App\Http\Controllers\MarketingController::class, 'stripeCheckout']); +Route::get('/paypal-checkout/{package}', [\App\Http\Controllers\MarketingController::class, 'paypalCheckout']);