feat(packages): implement package-based business model
This commit is contained in:
115
docs/prp/15-packages-design.md
Normal file
115
docs/prp/15-packages-design.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# 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)
|
||||
```php
|
||||
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)
|
||||
```php
|
||||
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)
|
||||
```php
|
||||
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)
|
||||
```php
|
||||
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.
|
||||
Reference in New Issue
Block a user