Files
fotospiel-app/docs/archive/prp/15-packages-design.md
2025-11-20 12:31:21 +01:00

6.5 KiB

Packages-Design für Fotospiel

Überblick

Dieses Dokument definiert das neue package-basierte Business Model, das das bestehende Credits-System ersetzt. Packages sind vordefinierte Bündel mit Limits und Features, die als Einmalkäufe pro Event (für Endkunden) oder jährliche Subscriptions (für Reseller/Agenturen) verkauft werden. Das Modell priorisiert Einfachheit: Bei Event-Erstellung wählt der User ein Package, das Limits für diesen Event setzt. Für Reseller limitiert das Tenant-Package die Anzahl Events pro Jahr und globale Features.

Ziele:

  • Ersetze Credits vollständig (keine Balance mehr, sondern Event-spezifische Limits).
  • Unterstütze Freemium: Free-Paket für Einstieg.
  • Skalierbar: Endkunden pro Event, Reseller jährlich.
  • Integration: Stripe für Zahlungen (Einmalkäufe/Subscriptions), Ledger für Transaktionen.

Endkunden-Pakete (pro Event, Einmalkauf)

Diese Pakete werden bei Event-Erstellung ausgewählt und gekauft. Sie definieren Limits für den spezifischen Event (z.B. max_photos, gallery_duration). Preise basierend auf User-Vorschlag.

Paket Preis max_photos max_guests gallery_days max_tasks watermark branding Extra Features
Free/Test 0 € 30 10 3 1 Standard Nein -
Starter 19 € 300 50 14 5 Standard Nein -
Standard 39 € 1000 150 30 10 Custom Ja Logo
Premium 79 € 3000 500 180 20 Kein Ja Live-Slideshow, Analytics

Reseller/Agentur-Pakete (jährlich, Subscription)

Diese Pakete werden auf Tenant-Ebene gekauft und limitieren Events pro Jahr, mit erweiterten Features (z.B. White-Label). Preise als Richtwerte.

Paket Preis/Jahr max_events/year Per-Event Limits Branding Extra
Reseller S 149 € 5 Standard Eingeschränkt -
Reseller M 299 € 15 Standard Eigene Logos 3 Monate Galerie
Reseller L 599 € 40 Premium White-Label -
Enterprise ab 999 € Unlimited Premium Voll Custom Domain, Support

Datenbank-Schema

Globale Packages-Tabelle (für alle Pakete, geteilt)

Schema::create('packages', function (Blueprint $table) {
    $table->id();
    $table->string('name'); // z.B. 'Starter', 'Reseller M'
    $table->string('type'); // 'endcustomer' oder 'reseller'
    $table->decimal('price', 8, 2); // Preis in EUR
    $table->integer('max_photos')->nullable(); // Null für Reseller (vererbt an Events)
    $table->integer('max_guests')->nullable();
    $table->integer('gallery_days')->nullable();
    $table->integer('max_tasks')->nullable();
    $table->boolean('watermark_allowed')->default(true);
    $table->boolean('branding_allowed')->default(false);
    $table->integer('max_events_per_year')->nullable(); // Für Reseller
    $table->timestamp('expires_after')->nullable(); // Für Subscriptions
    $table->json('features')->nullable(); // z.B. ['live_slideshow', 'analytics']
    $table->timestamps();
});

Seeder: Füge die obigen Pakete ein.

Event-Packages (Zuordnung Event zu Endkunden-Paket)

Schema::create('event_packages', function (Blueprint $table) {
    $table->id();
    $table->foreignId('event_id')->constrained()->cascadeOnDelete();
    $table->foreignId('package_id')->constrained()->cascadeOnDelete();
    $table->decimal('purchased_price', 8, 2);
    $table->timestamp('purchased_at');
    $table->integer('used_photos')->default(0); // Counter für Limits
    $table->timestamps();
});
  • Bei Event-Create: Package auswählen/kaufen, Eintrag erstellen.
  • Checks: z.B. if ($event->package->used_photos >= $event->package->max_photos) abort(403);

Tenant-Packages (für Reseller)

Schema::create('tenant_packages', function (Blueprint $table) {
    $table->id();
    $table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
    $table->foreignId('package_id')->constrained()->cascadeOnDelete();
    $table->decimal('price', 8, 2);
    $table->timestamp('purchased_at');
    $table->timestamp('expires_at'); // z.B. +1 Jahr
    $table->integer('used_events')->default(0); // Counter für max_events_per_year
    $table->boolean('active')->default(true);
    $table->timestamps();
});
  • Bei Tenant-Registrierung: Free-Reseller oder Upgrade.
  • Check: if ($tenant->activePackage->used_events >= $tenant->activePackage->max_events_per_year) block new Event.

Ledger und Purchases (ersetzt Credits-Ledger)

Schema::create('package_purchases', function (Blueprint $table) {
    $table->id();
    $table->foreignId('tenant_id')->constrained()->nullable(); // Null für Event-spezifisch
    $table->foreignId('event_id')->constrained()->nullable(); // Für Endkunden
    $table->foreignId('package_id')->constrained();
    $table->string('stripe_id'); // Für Webhook/Idempotenz
    $table->decimal('price', 8, 2);
    $table->string('type'); // 'endcustomer_event', 'reseller_subscription'
    $table->json('metadata'); // z.B. {'event_id': 123}
    $table->timestamps();
});
  • Kein separater Ledger nötig; Purchases tracken alles.

Integration und Logik

  • Event-Create Flow: User wählt Package → Stripe-Checkout (Einmalkauf) → Webhook bestätigt → Event mit package_id erstellen.
  • Reseller-Upgrade: Im Admin-Dashboard Package auswählen → Subscription erstellen → expires_at setzen.
  • Limits-Checks: Middleware prüft Event-Package-Limits (Photos/Uploads), Tenant-Package für Event-Anzahl.
  • Fallback: Bestehende Tenants migrieren zu Free-Paket (z.B. if old_credits > 100 → Standard).
  • UI/Features: Wasserzeichen: if (!$package->watermark_allowed) hide; Branding: Custom Logo-Upload if allowed.
  • Billing: Stripe Products für jedes Package; Webhooks updaten Status (z.B. Subscription cancel → active=false).

Migration von Altem System

  • Entferne: event_credits_balance aus tenants, event_purchases, event_credits_ledger.
  • Migriere: Für Events mit Credits → Zuordnen zu Free-Paket; Tenant-Balance → Initial Reseller S.
  • Skript: Artisan Command php artisan migrate:packages für Daten-Transfer.

Dieses Design ist final und bereit für Implementierung. Updates via PR.