feat(packages): implement package-based business model

This commit is contained in:
Codex Agent
2025-09-26 22:13:56 +02:00
parent 6fc36ebaf4
commit 0a643c3e4d
54 changed files with 3301 additions and 282 deletions

View File

@@ -1,7 +1,7 @@
# 08 — Billing (MVP: Event Credits)
# 08 — Billing (Packages)
- Model: one-off purchases that grant event credits; no subscriptions in MVP.
- Tables: `event_purchases`, `event_credits_ledger` (see 04-data-model-migrations.md).
- Providers: Stripe (server-side checkout + webhooks); store receipts deferred.
- Idempotency: purchase intents keyed; ledger writes idempotent; retries safe.
- Limits: enforce `event_credits_balance >= 1` to create an event; ledger decrements on event creation.
- Model: one-off purchases of event packages (Endkunden) or annual subscriptions (Reseller); see 15-packages-design.md for details.
- Tables: `packages`, `event_packages`, `tenant_packages`, `package_purchases` (see 04-data-model-migrations.md and 15-packages-design.md).
- Providers: Stripe (server-side checkout + webhooks for Einmalkäufe/Subscriptions); store receipts.
- Idempotency: purchase intents keyed; purchase writes idempotent; retries safe.
- Limits: Enforce package selection at event creation; check event-specific limits (e.g. max_photos) during usage; tenant limits for reseller event count.

View File

@@ -2,7 +2,7 @@
## Executive Summary
This document details the Freemium business model for the Fotospiel tenant app, combining free access with in-app purchases for event credits. The model prioritizes user acquisition through a free app download while monetizing through value-driven upgrades. Key metrics: 5-10% conversion rate, ARPU €10-15, scalable to 100k+ users.
This document details the Package-based business model for the Fotospiel tenant app, combining free access with purchases of predefined packages. The model prioritizes user acquisition through a free test package while monetizing through value-driven upgrades. Key metrics: 5-10% conversion rate, ARPU €10-15, scalable to 100k+ users. See 15-packages-design.md for package details.
## Model Analysis
@@ -30,18 +30,13 @@ This document details the Freemium business model for the Fotospiel tenant app,
- Complex IAP setup and testing
- Requires strong onboarding to drive conversions
### Hybrid Freemium Recommendation
**Core Strategy:** Free app with limited first event (50 photos, basic features), unlimited upgrades via IAP credits/subscriptions.
### Package-based Recommendation
**Core Strategy:** Free app with limited test package for first event, upgrades via package purchases (Einmalkäufe for Endkunden, Subscriptions for Reseller).
**Pricing Structure:**
- **Free Tier:** 1 basic event (50 photos, standard tasks, no custom branding)
- **Consumable Credits:**
- Starter Pack: €4.99 for 5 events (100 photos each)
- Pro Pack: €14.99 for 20 events (unlimited photos)
- **Subscriptions:**
- Pro Unlimited: €4.99/month (all features, unlimited events)
- Agency: €19.99/month (multi-tenant, analytics, white-label)
- **Non-Consumables:** Lifetime Unlimited: €49.99 (one-time purchase)
**Pricing Structure:** See 15-packages-design.md for Endkunden (pro Event: Free/Test 0€, Starter 19€, etc.) and Reseller (jährlich: S 149€, etc.).
- **Free Tier:** Test package (30 photos, 10 guests, 3 days gallery, 1 task, standard watermark)
- **Endkunden Packages:** Einmalkäufe pro Event with increasing limits/features.
- **Reseller Packages:** Annual subscriptions with event limits and branding options.
**Expected Metrics:**
- Downloads: 50k/year
@@ -91,38 +86,13 @@ This document details the Freemium business model for the Fotospiel tenant app,
- **Analytics:** Firebase for funnel tracking, RevenueCat for purchase events
### Backend API Extensions
- **Credit Management:** `/api/v1/tenant/credits` endpoints
- **Purchase Validation:** Webhook receiver from RevenueCat
- **Package Management:** `/api/v1/packages` and `/api/v1/tenant/packages` endpoints
- **Purchase Validation:** Webhook receiver from Stripe
- **Event Limiting:** Middleware checking credit balance before creation
- **Subscription Sync:** Real-time updates via WebSockets (optional)
### Database Schema Additions
```sql
-- tenant_credits table
CREATE TABLE tenant_credits (
tenant_id VARCHAR(255) PRIMARY KEY,
balance INTEGER DEFAULT 1, -- 1 free event
total_purchased INTEGER DEFAULT 0,
subscription_active BOOLEAN DEFAULT FALSE,
subscription_tier VARCHAR(50),
last_sync TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- purchase_history table
CREATE TABLE purchase_history (
id VARCHAR(255) PRIMARY KEY,
tenant_id VARCHAR(255) NOT NULL,
package_id VARCHAR(255) NOT NULL,
credits_added INTEGER,
price DECIMAL(10,2),
currency VARCHAR(3),
platform VARCHAR(50), -- 'ios' or 'android'
transaction_id VARCHAR(255),
purchased_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);
```
See 15-packages-design.md for updated schema: `packages`, `event_packages`, `tenant_packages`, `package_purchases`.
## Risk Mitigation

View 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.

View File

@@ -12,7 +12,8 @@ This directory supersedes the legacy `fotospiel_prp.md`. Content is split into s
- 05-admin-superadmin.md — Super Admin web console (Filament)
- 06-tenant-admin-pwa.md — Store-ready Tenant Admin PWA
- 07-guest-pwa.md — Guest (event attendee) PWA
- 08-billing.md — Event credits MVP, ledger, purchases
- 08-billing.md — Packages (Einmalkäufe/Subscriptions), ledger, purchases
- 15-packages-design.md — Package definitions, schema, integration
- 09-security-compliance.md — RBAC, audit, GDPR
- 10-storage-media-pipeline.md — Object storage, processing, CDN
- 11-ops-ci-cd.md — CI, releases, environments

View File

@@ -28,8 +28,8 @@ Diese Dokumentation beschreibt alle API-Endpunkte, die die Tenant Admin App mit
### Stats laden
- **GET /api/v1/tenant/dashboard**
- **Headers**: `Authorization: Bearer {token}`
- **Response**: `{ credits, active_events, new_photos, task_progress }`
- **Zweck**: Übersicht-Daten für Dashboard-Cards
- **Response**: `{ active_package, active_events, new_photos, task_progress }`
- **Zweck**: Übersicht-Daten für Dashboard-Cards (active_package: current tenant package info)
## Events
@@ -47,9 +47,9 @@ Diese Dokumentation beschreibt alle API-Endpunkte, die die Tenant Admin App mit
### Event erstellen
- **POST /api/v1/tenant/events**
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
- **Body**: `{ title, date, location, description }`
- **Body**: `{ title, date, location, description, package_id }`
- **Response**: 201 Created mit erstelltem Event
- **Validierung**: Prüft Credit-Balance (1 Credit pro Event)
- **Validierung**: Prüft Tenant-Package (Reseller-Limit) und erstellt Event-Package (Einmalkauf oder Free)
### Event-Details
- **GET /api/v1/tenant/events/{slug}**
@@ -198,8 +198,8 @@ Alle API-Requests enthalten:
```json
{
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Nicht genügend Credits verfügbar"
"code": "PACKAGE_LIMIT_EXCEEDED",
"message": "Package-Limit überschritten (z.B. max_photos)"
}
}
```

View File

@@ -11,7 +11,7 @@ Die Tenant Admin App muss folgende Kernfunktionen bereitstellen:
- **Member-Management**: Hinzufügen/Entfernen von Event-Mitgliedern; Rollen (Admin, Member).
- **Task & Emotion Management**: Zuweisen von Tasks und Emotions zu Events; Overrides für Tenant-spezifische Bibliotheken.
- **Settings-Management**: Tenant-spezifische Einstellungen (Theme, Limits, Legal Pages).
- **Billing & Purchases**: Kaufen von Event-Credits; Ledger-Übersicht; Integration mit Stripe.
- **Billing & Purchases**: Kaufen von Packages (pro Event oder Tenant); Ledger-Übersicht; Integration mit Stripe.
- **Notifications**: Push-Benachrichtigungen für neue Photos, Event-Updates, niedrigen Credit-Balance.
- **Offline-Support**: Caching von Events und Photos; Queuing von Uploads/Mutations mit Sync bei Online-Wiederkehr.
- **Audit & Compliance**: Logging kritischer Aktionen; ETag-basierte Conflict-Resolution; GDPR-konforme Datenlöschung.
@@ -27,7 +27,7 @@ Die App ist API-first und interagiert ausschließlich über den Backend-API-Endp
### Core Features
- **Event Lifecycle**:
- Erstellen: Erfordert mind. 1 Event-Credit; Slug-Generierung (unique pro Tenant).
- Erstellen: Erfordert Package-Auswahl (Free oder Kauf); Slug-Generierung (unique pro Tenant).
- Bearbeiten: Update von Datum, Ort, Tasks, Emotions, Join-Link.
- Veröffentlichen: Generiert QR-Code und Share-Link; aktiviert Guest-PWA-Zugriff.
- Archivieren: Soft-Delete mit Retention-Periode (GDPR); Credit-Rückerstattung optional.
@@ -39,8 +39,8 @@ Die App ist API-first und interagiert ausschließlich über den Backend-API-Endp
- Bibliothek: Globale + Tenant-spezifische Tasks/Emotions.
- Zuweisung: Drag-and-Drop zu Events; Fortschritts-Tracking.
- **Billing Integration**:
- Credit-Balance: Anzeige und Kauf von Packs (z.B. 5 Events für 29€).
- Ledger: Historie von Käufen, Consumptions, Refunds.
- Package-Auswahl: Anzeige verfügbarer Packages und Kauf (Einmalkauf/Subscription).
- Ledger: Historie von Package-Käufen und Nutzung.
- Stripe-Checkout: Server-side Intent-Erstellung; Webhook-Handling für Confirmation.
### Offline & Sync
@@ -62,8 +62,8 @@ Die App konsumiert den API-Contract aus docs/prp/03-api.md. Schlüssel-Endpunkte
### Events
- `GET /tenant/events`: Liste (paginiert, filterbar nach Status/Datum).
- `POST /tenant/events`: Erstellen (validiert Credit-Balance).
- `GET /tenant/events/{id}`: Details inkl. Tasks, Stats.
- `POST /tenant/events`: Erstellen (validiert Tenant-Package und erstellt Event-Package).
- `GET /tenant/events/{id}`: Details inkl. Tasks, Stats, package_limits.
- `PATCH /tenant/events/{id}`: Update (ETag für Concurrency).
- `DELETE /tenant/events/{id}`: Archivieren.
@@ -83,13 +83,13 @@ Die App konsumiert den API-Contract aus docs/prp/03-api.md. Schlüssel-Endpunkte
- `PATCH /tenant/settings`: Update.
### Billing
- `GET /tenant/ledger`: Credit-Historie.
- `POST /tenant/purchases/intent`: Stripe-Checkout-Session erstellen.
- `GET /tenant/credits/balance`: Aktueller Stand.
- `GET /tenant/packages`: Tenant-Packages und Limits.
- `POST /tenant/purchases/intent`: Stripe-Checkout-Session für Package erstellen.
- `GET /api/v1/packages`: Verfügbare Packages.
### Pagination & Errors
- Standard: `page`, `per_page` (max 50 für Mobile).
- Errors: Parsen von `{ error: { code, message } }`; User-freundliche Messages (z.B. "Nicht genug Credits").
- Errors: Parsen von `{ error: { code, message } }`; User-freundliche Messages (z.B. "Package-Limit überschritten").
## Non-Functional Requirements
- **Performance**: Ladezeiten < 2s; Lazy-Loading für Galleries.