diff --git a/.claude/plans/referral-system.md b/.claude/plans/referral-system.md new file mode 100644 index 000000000..4b6fb9545 --- /dev/null +++ b/.claude/plans/referral-system.md @@ -0,0 +1,1247 @@ +# ManaCore Referral System - Design Document + +> **Status**: Finalisiert - Bereit zur Implementierung +> **Erstellt**: 2025-12-07 +> **Autor**: Claude Code + +--- + +## 1. Übersicht & Ziele + +### 1.1 Was wollen wir erreichen? + +Ein **app-übergreifendes Referral-System** für das ManaCore-Ökosystem, das: +- Organisches Wachstum durch User-Empfehlungen fördert +- User mit Credits bei jedem Stage-Übergang belohnt +- Transparentes Tracking aller Referral-Stages bietet +- Tier-basierte Belohnungen für Power-Referrer ermöglicht +- Cross-App-Engagement der geworbenen User trackt und belohnt +- Umfassende Fraud-Detection implementiert + +### 1.2 Finale Entscheidungen + +| Aspekt | Entscheidung | +|--------|--------------| +| Belohnungsart | Nur Credits, keine Geld-Auszahlung | +| Code-Generierung | Automatisch für jeden User + unbegrenzte Custom-Codes (mit Rate-Limit) | +| Code-Format | Nur Random, kurz (z.B. `X7K9P2`) | +| Code-Scope | Global einzigartig (über alle Apps) | +| Stage-Tracking | Alle Stages werden getrackt mit Bonus bei jedem Übergang | +| Retention | Wird getrackt (30 Tage aktiv) | +| E-Mail-Verifizierung | Bleibt deaktiviert | +| Tier-System | Ja, Lifetime-basiert, nicht rückwirkend | +| Referee-Bonus | Nicht tier-abhängig (alle gleich) | +| Cross-App | Codes flexibel nutzbar + Bonus für Cross-App-Nutzung | +| Fraud-Detection | Umfassend implementiert | + +--- + +## 2. Referral-Stages & Bonus-Struktur + +### 2.1 Stage-Definitionen mit Boni + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ REFERRAL LIFECYCLE │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ [1] REGISTERED Account erstellt │ +│ │ → Referrer: 5 Credits │ +│ │ → Referee: +25 Bonus (zusätzlich zu 150 Signup) │ +│ ▼ │ +│ [2] ACTIVATED Erste Credit-Nutzung (in irgendeiner App) │ +│ │ → Referrer: 10 Credits (× Tier-Multiplier) │ +│ ▼ │ +│ [3] QUALIFIED Erster Credit-Kauf getätigt │ +│ │ → Referrer: 25 Credits (× Tier-Multiplier) │ +│ ▼ │ +│ [4] RETAINED 30 Tage nach Registrierung noch aktiv │ +│ → Referrer: 15 Credits (× Tier-Multiplier) │ +│ │ +│ [+] CROSS_APP Referee nutzt weitere App (einmalig pro App) │ +│ → Referrer: 5 Credits pro App (× Tier-Multiplier) │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 2.2 Bonus-Tabelle + +#### Referrer-Boni (wer wirbt) + +| Event | Basis-Bonus | Bronze (1x) | Silver (1.5x) | Gold (2x) | Platinum (3x) | +|-------|-------------|-------------|---------------|-----------|---------------| +| Registered | 5 | 5 | 7 | 10 | 15 | +| Activated | 10 | 10 | 15 | 20 | 30 | +| Qualified | 25 | 25 | 37 | 50 | 75 | +| Retained (30d) | 15 | 15 | 22 | 30 | 45 | +| Cross-App (pro App) | 5 | 5 | 7 | 10 | 15 | + +**Max pro Referral:** 55 Basis + 13 Apps × 5 = **120 Credits** (Bronze) bis **360 Credits** (Platinum) + +#### Referee-Boni (wer geworben wird) + +| Event | Bonus | +|-------|-------| +| Registrierung mit Code | +25 Credits (= 175 total statt 150) | + +--- + +## 3. Tier-System + +### 3.1 Tier-Struktur (Lifetime-basiert) + +| Tier | Qualifizierte Referrals | Multiplier | Badge | +|------|------------------------|------------|-------| +| Bronze | 0-4 | 1.0x | 🥉 | +| Silver | 5-14 | 1.5x | 🥈 | +| Gold | 15-29 | 2.0x | 🥇 | +| Platinum | 30+ | 3.0x | 💎 | + +### 3.2 Wichtige Regeln + +- **Lifetime-basiert**: Einmal erreichte Qualified-Referrals zählen für immer +- **Nicht rückwirkend**: Bestehende Referrals bekommen keinen nachträglichen Bonus +- **Nur für Referrer**: Referee-Bonus ist für alle gleich (25 Credits) +- **Berechnung**: Nur QUALIFIED Referrals zählen für Tier-Aufstieg + +--- + +## 4. Code-System + +### 4.1 Code-Typen + +| Typ | Beschreibung | Format | Beispiel | +|-----|--------------|--------|----------| +| `auto` | Automatisch bei User-Erstellung | 6 Zeichen alphanumerisch | `X7K9P2` | +| `custom` | Vom User erstellt | 3-20 Zeichen, alphanumerisch + Bindestrich | `TILLCODES` | +| `campaign` | Admin-generiert | Beliebig | `LAUNCH2024` | + +### 4.2 Code-Regeln + +- **Global einzigartig**: Ein Code existiert nur einmal im gesamten System +- **Keine Limits**: User können unbegrenzt Custom-Codes erstellen +- **Rate-Limit**: Max 10 Code-Erstellungen pro Stunde pro User +- **Flexibel**: Jeder Code funktioniert für alle Apps (erstellt ManaCore-Account) +- **Tracking**: Ursprungs-App wird getrackt (woher kam der Link?) + +### 4.3 Code-Generierung + +```typescript +// Auto-Code Format: 6 alphanumerische Zeichen (ohne verwechselbare: 0/O, 1/I/L) +const CHARSET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'; + +function generateCode(): string { + return Array.from({ length: 6 }, () => + CHARSET[Math.floor(Math.random() * CHARSET.length)] + ).join(''); +} +// Beispiele: X7K9P2, ABCD34, MNPQ78 +``` + +--- + +## 5. Cross-App-Tracking + +### 5.1 Konzept + +Wenn ein geworbener User eine neue App zum ersten Mal nutzt, erhält der Referrer einen Bonus: + +``` +User "Till" wirbt "Max" über Chat +├── Max registriert sich → Till: +5 Credits +├── Max nutzt Chat (Activation) → Till: +10 Credits +├── Max kauft Credits → Till: +25 Credits +├── Max nutzt Picture (neu!) → Till: +5 Credits (Cross-App) +├── Max nutzt Calendar (neu!) → Till: +5 Credits (Cross-App) +└── 30 Tage später → Till: +15 Credits (Retention) + +Total für Till: 65 Credits (Bronze) bis 195 Credits (Platinum) +``` + +### 5.2 App-Liste für Cross-App-Tracking + +```typescript +const TRACKABLE_APPS = [ + 'chat', // ManaChat + 'picture', // ManaPicture + 'presi', // Presi + 'mail', // ManaMail + 'manadeck', // ManaDeck + 'todo', // ManaTodo + 'calendar', // ManaCalendar + 'contacts', // ManaContacts + 'finance', // ManaFinance + 'clock', // ManaClock + 'zitare', // Zitare + 'storage', // ManaStorage + 'moodlit', // Moodlit +]; +// 13 Apps = max 65 Cross-App Credits (Bronze) bis 195 (Platinum) +``` + +--- + +## 6. Fraud-Detection System + +### 6.1 Übersicht + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ FRAUD DETECTION LAYERS │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Layer 1: Prevention (vor Registrierung) │ +│ ├── Rate-Limiting auf Code-Validierung │ +│ ├── Self-Referral-Block │ +│ └── Captcha bei verdächtigen Patterns │ +│ │ +│ Layer 2: Detection (bei Registrierung) │ +│ ├── IP-Fingerprinting │ +│ ├── Device-Fingerprinting │ +│ ├── Email-Domain-Analyse │ +│ └── Timing-Analyse │ +│ │ +│ Layer 3: Verification (nach Registrierung) │ +│ ├── Minimum-Zeit bis Qualification (24h) │ +│ ├── Behavior-Analyse │ +│ └── Cross-Reference mit bekannten Fraud-Patterns │ +│ │ +│ Layer 4: Enforcement (bei Verdacht) │ +│ ├── Auto-Hold für Bonus-Auszahlung │ +│ ├── Manual Review Queue │ +│ └── Account-Suspension bei Bestätigung │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 6.2 Fraud-Signale & Scoring + +| Signal | Punkte | Beschreibung | +|--------|--------|--------------| +| Same IP | +30 | Gleiche IP wie Referrer | +| Same Device | +50 | Gleicher Device-Fingerprint | +| Disposable Email | +20 | Wegwerf-Email-Domain | +| Similar Email | +25 | Email ähnlich wie Referrer (z.B. till1@, till2@) | +| Rapid Registration | +15 | Registrierung < 5 Min nach Code-Abruf | +| Bulk Registrations | +40 | 5+ Registrierungen vom gleichen Referrer in 1h | +| VPN/Proxy | +20 | Bekannte VPN/Proxy-IP | +| New Account Referrer | +15 | Referrer-Account < 7 Tage alt | +| Instant Qualification | +35 | Kauf < 1h nach Registrierung | +| Minimal Activity | +25 | Keine echte App-Nutzung vor Kauf | + +**Fraud-Score Thresholds:** +- 0-29: ✅ Low Risk → Automatische Bonus-Auszahlung +- 30-59: ⚠️ Medium Risk → Verzögerte Auszahlung (48h Hold) +- 60-89: 🚨 High Risk → Auto-Hold + Manual Review +- 90+: 🛑 Critical → Bonus blockiert + Account-Flag + +### 6.3 Rate-Limits + +| Aktion | Limit | Zeitraum | +|--------|-------|----------| +| Code-Validierung | 20 | pro Minute pro IP | +| Code-Erstellung | 10 | pro Stunde pro User | +| Registrierungen pro Code | 50 | pro Tag | +| Registrierungen pro Referrer | 20 | pro Tag | +| Bonus-Claims | 100 | pro Tag pro Referrer | + +### 6.4 Timing-Regeln + +| Regel | Wert | Beschreibung | +|-------|------|--------------| +| Min. Zeit bis Activation | 5 Min | Nach Registrierung | +| Min. Zeit bis Qualification | 24h | Nach Registrierung | +| Retention-Check | 30 Tage | Nach Registrierung | +| Fraud-Review-Window | 7 Tage | Nach Qualification | + +### 6.5 Auto-Hold System + +```typescript +interface BonusHold { + id: string; + referralId: string; + userId: string; // Wer bekommt den Bonus + amount: number; + reason: 'fraud_score' | 'rate_limit' | 'manual_flag'; + fraudScore: number; + fraudSignals: string[]; // ['same_ip', 'rapid_registration'] + status: 'held' | 'released' | 'rejected'; + holdUntil: Date; // Auto-Release nach X Tagen wenn ok + reviewedBy?: string; // Admin-ID + reviewedAt?: Date; + createdAt: Date; +} +``` + +### 6.6 Device & IP Fingerprinting + +```typescript +interface FraudFingerprint { + id: string; + + // IP-Daten + ipAddress: string; + ipHash: string; // Für Datenschutz + ipType: 'residential' | 'datacenter' | 'vpn' | 'proxy' | 'tor'; + ipCountry: string; + ipCity?: string; + + // Device-Daten + deviceFingerprint: string; + userAgent: string; + screenResolution?: string; + timezone?: string; + language?: string; + + // Tracking + firstSeenAt: Date; + lastSeenAt: Date; + registrationCount: number; + flaggedCount: number; +} +``` + +--- + +## 7. Datenbank-Schema + +### 7.1 Neue Tabellen + +```sql +-- Schema erstellen +CREATE SCHEMA IF NOT EXISTS referrals; + +-- ============================================ +-- CORE TABLES +-- ============================================ + +-- Referral-Codes (global einzigartig) +CREATE TABLE referrals.codes ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id TEXT NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + code TEXT NOT NULL UNIQUE, -- Global einzigartig + type TEXT NOT NULL DEFAULT 'auto', -- 'auto', 'custom', 'campaign' + source_app_id TEXT, -- App wo Code erstellt wurde + is_active BOOLEAN DEFAULT true, + uses_count INTEGER DEFAULT 0, + max_uses INTEGER, -- NULL = unbegrenzt + expires_at TIMESTAMPTZ, -- NULL = nie + metadata JSONB DEFAULT '{}', + created_at TIMESTAMPTZ DEFAULT now(), + + CONSTRAINT code_format CHECK (code ~ '^[A-Z0-9-]{3,20}$') +); + +CREATE INDEX codes_lookup_idx ON referrals.codes(code) WHERE is_active = true; +CREATE INDEX codes_user_idx ON referrals.codes(user_id); + +-- Referral-Beziehungen +CREATE TABLE referrals.relationships ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + referrer_id TEXT NOT NULL REFERENCES auth.users(id), + referee_id TEXT NOT NULL REFERENCES auth.users(id) UNIQUE, + code_id UUID NOT NULL REFERENCES referrals.codes(id), + source_app_id TEXT, -- App über die geworben wurde + + -- Stage Tracking + status TEXT NOT NULL DEFAULT 'registered', + registered_at TIMESTAMPTZ NOT NULL DEFAULT now(), + activated_at TIMESTAMPTZ, + qualified_at TIMESTAMPTZ, + retained_at TIMESTAMPTZ, + + -- Fraud + fraud_score INTEGER DEFAULT 0, + fraud_signals TEXT[] DEFAULT '{}', + is_flagged BOOLEAN DEFAULT false, + + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now(), + + CONSTRAINT no_self_referral CHECK (referrer_id != referee_id) +); + +CREATE INDEX relationships_referrer_idx ON referrals.relationships(referrer_id); +CREATE INDEX relationships_referee_idx ON referrals.relationships(referee_id); +CREATE INDEX relationships_status_idx ON referrals.relationships(status); +CREATE INDEX relationships_flagged_idx ON referrals.relationships(is_flagged) WHERE is_flagged = true; + +-- Cross-App-Tracking +CREATE TABLE referrals.cross_app_activations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + relationship_id UUID NOT NULL REFERENCES referrals.relationships(id), + app_id TEXT NOT NULL, + activated_at TIMESTAMPTZ DEFAULT now(), + bonus_paid BOOLEAN DEFAULT false, + + UNIQUE(relationship_id, app_id) +); + +CREATE INDEX cross_app_relationship_idx ON referrals.cross_app_activations(relationship_id); + +-- User Tier Status +CREATE TABLE referrals.user_tiers ( + user_id TEXT PRIMARY KEY REFERENCES auth.users(id), + tier TEXT NOT NULL DEFAULT 'bronze', + qualified_count INTEGER DEFAULT 0, + total_earned INTEGER DEFAULT 0, -- Lifetime Credits durch Referrals + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now() +); + +-- ============================================ +-- BONUS & TRANSACTION TABLES +-- ============================================ + +-- Bonus-Events (Audit-Trail) +CREATE TABLE referrals.bonus_events ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + relationship_id UUID NOT NULL REFERENCES referrals.relationships(id), + user_id TEXT NOT NULL REFERENCES auth.users(id), + event_type TEXT NOT NULL, -- 'registered', 'activated', 'qualified', 'retained', 'cross_app' + app_id TEXT, -- Für cross_app events + credits_base INTEGER NOT NULL, -- Basis-Credits + tier_multiplier REAL NOT NULL DEFAULT 1.0, + credits_final INTEGER NOT NULL, -- Nach Multiplier + tier_at_time TEXT NOT NULL, + transaction_id UUID, -- Referenz zu credits.transactions + + -- Hold-Status + status TEXT DEFAULT 'pending', -- 'pending', 'paid', 'held', 'rejected' + hold_reason TEXT, + hold_until TIMESTAMPTZ, + released_at TIMESTAMPTZ, + + created_at TIMESTAMPTZ DEFAULT now() +); + +CREATE INDEX bonus_events_relationship_idx ON referrals.bonus_events(relationship_id); +CREATE INDEX bonus_events_user_idx ON referrals.bonus_events(user_id); +CREATE INDEX bonus_events_status_idx ON referrals.bonus_events(status); + +-- ============================================ +-- FRAUD DETECTION TABLES +-- ============================================ + +-- IP/Device Fingerprints +CREATE TABLE referrals.fingerprints ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- IP + ip_hash TEXT NOT NULL, -- SHA256 der IP + ip_type TEXT DEFAULT 'unknown', -- 'residential', 'datacenter', 'vpn', 'proxy', 'tor' + ip_country TEXT, + ip_asn TEXT, + + -- Device + device_hash TEXT, -- Fingerprint-Hash + user_agent_hash TEXT, + + -- Stats + first_seen_at TIMESTAMPTZ DEFAULT now(), + last_seen_at TIMESTAMPTZ DEFAULT now(), + registration_count INTEGER DEFAULT 0, + flagged_count INTEGER DEFAULT 0, + + UNIQUE(ip_hash, device_hash) +); + +CREATE INDEX fingerprints_ip_idx ON referrals.fingerprints(ip_hash); +CREATE INDEX fingerprints_device_idx ON referrals.fingerprints(device_hash); + +-- Fingerprint zu User Mapping +CREATE TABLE referrals.user_fingerprints ( + user_id TEXT NOT NULL REFERENCES auth.users(id), + fingerprint_id UUID NOT NULL REFERENCES referrals.fingerprints(id), + seen_at TIMESTAMPTZ DEFAULT now(), + context TEXT, -- 'registration', 'login', 'code_validation' + + PRIMARY KEY (user_id, fingerprint_id) +); + +-- Fraud-Flags (für Muster-Erkennung) +CREATE TABLE referrals.fraud_patterns ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + pattern_type TEXT NOT NULL, -- 'email_domain', 'ip_range', 'device_pattern' + pattern_value TEXT NOT NULL, + severity TEXT DEFAULT 'medium', -- 'low', 'medium', 'high', 'critical' + score_impact INTEGER NOT NULL, + description TEXT, + is_active BOOLEAN DEFAULT true, + created_by TEXT, + created_at TIMESTAMPTZ DEFAULT now() +); + +-- Rate-Limit Tracking +CREATE TABLE referrals.rate_limits ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + identifier TEXT NOT NULL, -- IP, User-ID, oder Code + identifier_type TEXT NOT NULL, -- 'ip', 'user', 'code' + action TEXT NOT NULL, -- 'code_validation', 'code_creation', 'registration' + count INTEGER DEFAULT 1, + window_start TIMESTAMPTZ DEFAULT now(), + window_end TIMESTAMPTZ NOT NULL, + + UNIQUE(identifier, identifier_type, action, window_start) +); + +CREATE INDEX rate_limits_lookup_idx ON referrals.rate_limits(identifier, identifier_type, action, window_end); + +-- Admin Review Queue +CREATE TABLE referrals.review_queue ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + relationship_id UUID NOT NULL REFERENCES referrals.relationships(id), + fraud_score INTEGER NOT NULL, + fraud_signals TEXT[] NOT NULL, + priority TEXT DEFAULT 'medium', -- 'low', 'medium', 'high', 'critical' + status TEXT DEFAULT 'pending', -- 'pending', 'approved', 'rejected', 'escalated' + assigned_to TEXT, + notes TEXT, + reviewed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT now() +); + +CREATE INDEX review_queue_status_idx ON referrals.review_queue(status, priority); + +-- ============================================ +-- ANALYTICS TABLES +-- ============================================ + +-- Tägliche Statistiken (für Dashboard) +CREATE TABLE referrals.daily_stats ( + date DATE NOT NULL, + app_id TEXT, -- NULL = global + + registrations INTEGER DEFAULT 0, + activations INTEGER DEFAULT 0, + qualifications INTEGER DEFAULT 0, + retentions INTEGER DEFAULT 0, + + credits_paid INTEGER DEFAULT 0, + credits_held INTEGER DEFAULT 0, + + fraud_blocked INTEGER DEFAULT 0, + + PRIMARY KEY (date, COALESCE(app_id, 'global')) +); + +-- ============================================ +-- VIEWS +-- ============================================ + +-- Referrer-Stats View +CREATE VIEW referrals.referrer_stats AS +SELECT + r.referrer_id, + ut.tier, + ut.qualified_count, + ut.total_earned, + COUNT(*) FILTER (WHERE r.status = 'registered') as registered_count, + COUNT(*) FILTER (WHERE r.activated_at IS NOT NULL) as activated_count, + COUNT(*) FILTER (WHERE r.qualified_at IS NOT NULL) as qualified_count_current, + COUNT(*) FILTER (WHERE r.retained_at IS NOT NULL) as retained_count, + COUNT(*) FILTER (WHERE r.is_flagged) as flagged_count +FROM referrals.relationships r +LEFT JOIN referrals.user_tiers ut ON r.referrer_id = ut.user_id +GROUP BY r.referrer_id, ut.tier, ut.qualified_count, ut.total_earned; +``` + +### 7.2 Erweiterung bestehender Tabellen + +```sql +-- Transaction-Type erweitern (falls noch nicht vorhanden) +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_enum + WHERE enumlabel = 'referral' + AND enumtypid = 'credits.transaction_type'::regtype + ) THEN + ALTER TYPE credits.transaction_type ADD VALUE 'referral'; + END IF; +END +$$; + +-- Referral-Referenz in Transactions +ALTER TABLE credits.transactions + ADD COLUMN IF NOT EXISTS referral_bonus_event_id UUID + REFERENCES referrals.bonus_events(id); +``` + +--- + +## 8. API-Endpoints + +### 8.1 Code-Management + +```typescript +// ============================================ +// PUBLIC ENDPOINTS +// ============================================ + +// Code validieren (für Registrierungs-Form) +GET /api/referrals/validate/:code +Response: { + valid: boolean, + referrerName?: string, // "Till S." (anonymisiert) + bonusCredits: number, // 25 + error?: string // 'invalid' | 'expired' | 'max_uses' +} + +// ============================================ +// AUTHENTICATED ENDPOINTS +// ============================================ + +// Eigene Codes abrufen +GET /api/referrals/codes +Response: { + codes: Array<{ + id: string, + code: string, + type: 'auto' | 'custom' | 'campaign', + isActive: boolean, + usesCount: number, + createdAt: string + }>, + autoCode: string // Primärer Auto-Code +} + +// Neuen Custom-Code erstellen +POST /api/referrals/codes +Body: { code: string } // 3-20 Zeichen +Response: { + code: { id, code, ... }, + error?: string // 'taken' | 'invalid_format' | 'rate_limited' +} + +// Code deaktivieren +DELETE /api/referrals/codes/:id +Response: { success: boolean } + +// ============================================ +// STATS ENDPOINTS +// ============================================ + +// Meine Referral-Stats +GET /api/referrals/stats +Response: { + tier: { + current: 'silver', + multiplier: 1.5, + qualifiedCount: 8, + nextTier: 'gold', + nextTierAt: 15, + progress: 0.53 // 8/15 + }, + totals: { + registered: 23, + activated: 18, + qualified: 8, + retained: 5, + creditsEarned: 450, + creditsPending: 35 // In Hold + }, + byApp: { + chat: { registered: 12, activated: 10, qualified: 4, credits: 200 }, + picture: { registered: 11, activated: 8, qualified: 4, credits: 250 } + }, + recentActivity: [ + { type: 'qualified', refereeName: 'Max M.', credits: 25, at: '...' }, + { type: 'cross_app', refereeName: 'Anna K.', app: 'picture', credits: 5, at: '...' } + ] +} + +// Meine geworbenen User +GET /api/referrals/referred-users +Query: ?status=all|registered|activated|qualified|retained&limit=20&offset=0 +Response: { + users: Array<{ + id: string, + name: string, // Anonymisiert: "Max M." + status: string, + registeredAt: string, + activatedAt?: string, + qualifiedAt?: string, + retainedAt?: string, + appsUsed: string[], // ['chat', 'picture'] + creditsEarned: number, + isFlagged: boolean + }>, + pagination: { total, limit, offset } +} + +// ============================================ +// INTERNAL ENDPOINTS (Service-to-Service) +// ============================================ + +// Referral bei Registrierung anwenden +POST /api/internal/referrals/apply +Headers: { 'X-Service-Key': '...' } +Body: { + refereeId: string, + code: string, + sourceAppId: string, + ipAddress: string, + deviceFingerprint?: string, + userAgent: string +} +Response: { + success: boolean, + referralId?: string, + bonusAwarded?: number, + fraudScore?: number, + error?: string +} + +// Stage-Update (von anderen Services) +POST /api/internal/referrals/stage-update +Headers: { 'X-Service-Key': '...' } +Body: { + userId: string, + stage: 'activated' | 'qualified', + appId: string, + metadata?: object +} +Response: { success: boolean, bonusPaid?: number } + +// Cross-App-Aktivierung +POST /api/internal/referrals/cross-app +Headers: { 'X-Service-Key': '...' } +Body: { + userId: string, + appId: string +} +Response: { success: boolean, bonusPaid?: number, isNewApp: boolean } +``` + +### 8.2 Admin-Endpoints + +```typescript +// ============================================ +// ADMIN ENDPOINTS +// ============================================ + +// Campaign-Code erstellen +POST /api/admin/referrals/campaign-codes +Body: { + code: string, + maxUses?: number, + expiresAt?: string, + metadata?: { campaign: string, source: string } +} + +// Review-Queue abrufen +GET /api/admin/referrals/review-queue +Query: ?status=pending&priority=high&limit=50 +Response: { + items: Array<{ + id: string, + relationship: { referrerId, refereeId, ... }, + fraudScore: number, + fraudSignals: string[], + priority: string, + bonusAmount: number, + createdAt: string + }> +} + +// Review abschließen +POST /api/admin/referrals/review/:id +Body: { + action: 'approve' | 'reject', + notes?: string +} + +// Fraud-Pattern hinzufügen +POST /api/admin/referrals/fraud-patterns +Body: { + patternType: 'email_domain' | 'ip_range' | 'device_pattern', + patternValue: string, + severity: 'low' | 'medium' | 'high' | 'critical', + scoreImpact: number, + description?: string +} + +// Dashboard-Stats +GET /api/admin/referrals/dashboard +Query: ?period=7d|30d|90d +Response: { + overview: { + totalReferrals: number, + conversionRate: number, // registered → qualified + creditsPaid: number, + creditsHeld: number, + fraudBlocked: number + }, + byDay: Array<{ date, registrations, qualifications, credits }>, + topReferrers: Array<{ userId, name, qualified, credits }>, + fraudStats: { + blockedToday: number, + pendingReview: number, + commonSignals: Array<{ signal, count }> + } +} +``` + +--- + +## 9. Service-Architektur + +### 9.1 Neue Module in mana-core-auth + +``` +services/mana-core-auth/src/ +├── referrals/ +│ ├── referrals.module.ts +│ ├── referrals.controller.ts +│ ├── services/ +│ │ ├── referral-code.service.ts # Code-Generierung & Verwaltung +│ │ ├── referral-tracking.service.ts # Stage-Tracking & Bonus +│ │ ├── referral-tier.service.ts # Tier-Berechnung +│ │ ├── referral-stats.service.ts # Statistiken +│ │ └── referral-cross-app.service.ts # Cross-App-Tracking +│ ├── fraud/ +│ │ ├── fraud-detection.service.ts # Fraud-Scoring +│ │ ├── fingerprint.service.ts # IP/Device Fingerprinting +│ │ ├── rate-limit.service.ts # Rate-Limiting +│ │ └── fraud-patterns.service.ts # Pattern-Matching +│ ├── dto/ +│ │ ├── create-code.dto.ts +│ │ ├── apply-referral.dto.ts +│ │ ├── stage-update.dto.ts +│ │ └── ... +│ └── entities/ +│ └── ... (Drizzle schemas) +``` + +### 9.2 Integration Points + +```typescript +// 1. Bei User-Registrierung (better-auth.service.ts) +async registerB2C(dto: RegisterB2CDto) { + const user = await this.createUser(dto); + await this.creditsService.initializeBalance(user.id); + + // NEU: Auto-Code erstellen + await this.referralCodeService.createAutoCode(user.id); + + // NEU: Referral anwenden wenn Code übergeben + if (dto.referralCode) { + await this.referralTrackingService.applyReferral({ + refereeId: user.id, + code: dto.referralCode, + sourceAppId: dto.appId, + fingerprint: dto.fingerprint + }); + } + + return { user, token }; +} + +// 2. Bei Credit-Nutzung (credits.service.ts) +async useCredits(userId: string, dto: UseCreditsDto) { + const result = await this.deductCredits(userId, dto); + + // NEU: Cross-App-Tracking + await this.referralCrossAppService.trackAppUsage(userId, dto.appId); + + // NEU: Activation-Check (erste Nutzung überhaupt) + await this.referralTrackingService.checkActivation(userId); + + return result; +} + +// 3. Bei Credit-Kauf (nach Stripe-Webhook) +async completePurchase(userId: string, purchaseId: string) { + await this.creditBalance(userId, amount); + + // NEU: Qualification-Check (erster Kauf) + await this.referralTrackingService.checkQualification(userId); +} + +// 4. Cron-Job für Retention (täglich) +@Cron('0 0 * * *') +async checkRetention() { + await this.referralTrackingService.processRetentionBatch(); +} +``` + +--- + +## 10. UI-Komponenten + +### 10.1 Registrierungs-Seite (erweitert) + +**Datei:** `apps/manacore/apps/web/src/routes/(auth)/register/+page.svelte` + +```svelte + + + +
+ Eingeladen von {referralValidation.referrerName} - + Du erhältst {referralValidation.bonusCredits} Bonus-Credits! +
+ {:else if referralValidation && !referralValidation.valid} ++ Ungültiger Code +
+ {/if} ++ {#if referralValidation?.valid} + {150 + referralValidation.bonusCredits} Credits zum Start + {:else} + 150 Credits zum Start + {/if} +
+``` + +### 10.2 Dashboard Referral-Widget + +**Datei:** `apps/manacore/apps/web/src/lib/components/referrals/ReferralWidget.svelte` + +```svelte + + +Dein Code:
+
+ {stats.autoCode}
+
+
+ {stats.totals.registered}
+Registriert
+{stats.totals.activated}
+Aktiviert
+{stats.totals.qualified}
+Qualifiziert
+{stats.totals.creditsEarned}
+Credits
++ Bei {stats.tier.nextTier}: {stats.tier.multiplier * 1.5}x Bonus +
++ 💎 Maximales Tier erreicht! (3x Bonus) +
+ {/if} + + + +