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:
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 @@ + + + + + +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 @@ + + + + + +Erleben Sie Events durch professionelle Fotos. Unsere sichere PWA-Plattform für Gäste und Organisatoren. Einfach, mobil und datenschutzkonform.
+ Jetzt starten – Kostenlos +GDPR-konform, anonyme Sessions, keine PII-Speicherung.
+Offline-fähig, App-ähnlich, für iOS und Android.
+Automatische Thumbnails, Echtzeit-Updates, einfache Bedienung.
+Als Organisator: Registrieren, Event anlegen, Gäste einladen.
+Gäste: PWA öffnen, Fotos via Kamera oder Galerie teilen.
+Emotions auswählen, Fotos liken, Galerie browsen.
+Freigegebene Fotos herunterladen, Event abschließen.
+{{ session('success') }}
+ @endif + @if ($errors->any()) +"Perfekt für unsere Hochzeit! Einfach und sicher."
+- Anna & Max
+"Großes Firmenevent – alle Fotos zentral und mobil."
+- Team XYZ GmbH
+Ja, der Basic-Tarif ist kostenlos für 1 Event. Upgrades ab 99€.
+100% GDPR-konform. Keine personenbezogenen Daten gespeichert. Siehe Datenschutzerklärung.
+Über die PWA-App: Kamera oder Galerie, Emotions zuweisen, teilen.
+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']);