managarten/.claude/plans/referral-system.md
Till-JS 9ece591bb5 docs: add referral system implementation plan
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 16:11:23 +01:00

40 KiB
Raw Blame History

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

// 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

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

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

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

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

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

// ============================================
// 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

// ============================================
// 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

// 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

<script lang="ts">
  let referralCode = $state('');
  let referralValidation = $state<{
    valid: boolean;
    referrerName?: string;
    bonusCredits?: number;
  } | null>(null);
  let validating = $state(false);

  // URL-Parameter auslesen
  onMount(() => {
    const urlCode = $page.url.searchParams.get('ref');
    if (urlCode) {
      referralCode = urlCode;
      validateCode();
    }
  });

  async function validateCode() {
    if (!referralCode || referralCode.length < 3) {
      referralValidation = null;
      return;
    }

    validating = true;
    const result = await api.referrals.validate(referralCode);
    referralValidation = result;
    validating = false;
  }
</script>

<!-- Im Formular -->
<div class="space-y-2">
  <Label for="referralCode">Einladungscode (optional)</Label>
  <div class="relative">
    <Input
      id="referralCode"
      bind:value={referralCode}
      onblur={validateCode}
      placeholder="z.B. X7K9P2"
      class={referralValidation?.valid ? 'border-green-500' : ''}
    />
    {#if validating}
      <Spinner class="absolute right-3 top-3" size="sm" />
    {:else if referralValidation?.valid}
      <CheckIcon class="absolute right-3 top-3 text-green-500" />
    {/if}
  </div>

  {#if referralValidation?.valid}
    <p class="text-sm text-green-600">
      Eingeladen von {referralValidation.referrerName} -
      Du erhältst {referralValidation.bonusCredits} Bonus-Credits!
    </p>
  {:else if referralValidation && !referralValidation.valid}
    <p class="text-sm text-red-600">
      Ungültiger Code
    </p>
  {/if}
</div>

<!-- Submit-Info -->
<p class="text-sm text-muted-foreground text-center">
  {#if referralValidation?.valid}
    {150 + referralValidation.bonusCredits} Credits zum Start
  {:else}
    150 Credits zum Start
  {/if}
</p>

10.2 Dashboard Referral-Widget

Datei: apps/manacore/apps/web/src/lib/components/referrals/ReferralWidget.svelte

<script lang="ts">
  import { Copy, Share2, QrCode, Users, TrendingUp } from 'lucide-svelte';

  interface Props {
    stats: ReferralStats;
  }

  let { stats }: Props = $props();

  const tierColors = {
    bronze: 'text-amber-600',
    silver: 'text-slate-400',
    gold: 'text-yellow-500',
    platinum: 'text-cyan-400'
  };

  const tierEmoji = {
    bronze: '🥉',
    silver: '🥈',
    gold: '🥇',
    platinum: '💎'
  };

  function copyCode() {
    navigator.clipboard.writeText(stats.autoCode);
    toast.success('Code kopiert!');
  }

  function shareLink() {
    const url = `https://manacore.app/r/${stats.autoCode}`;
    if (navigator.share) {
      navigator.share({ title: 'ManaCore einladen', url });
    } else {
      navigator.clipboard.writeText(url);
      toast.success('Link kopiert!');
    }
  }
</script>

<Card class="p-6">
  <div class="flex items-center justify-between mb-4">
    <h3 class="text-lg font-semibold flex items-center gap-2">
      <Users class="h-5 w-5" />
      Freunde einladen
    </h3>
    <Badge class={tierColors[stats.tier.current]}>
      {tierEmoji[stats.tier.current]} {stats.tier.current}
    </Badge>
  </div>

  <!-- Code Box -->
  <div class="bg-muted rounded-lg p-4 mb-4">
    <p class="text-sm text-muted-foreground mb-1">Dein Code:</p>
    <div class="flex items-center gap-2">
      <code class="text-2xl font-mono font-bold tracking-wider">
        {stats.autoCode}
      </code>
      <Button variant="ghost" size="icon" onclick={copyCode}>
        <Copy class="h-4 w-4" />
      </Button>
    </div>
  </div>

  <!-- Share Buttons -->
  <div class="flex gap-2 mb-6">
    <Button variant="outline" size="sm" onclick={shareLink}>
      <Share2 class="h-4 w-4 mr-2" />
      Teilen
    </Button>
    <Button variant="outline" size="sm" onclick={() => showQrModal = true}>
      <QrCode class="h-4 w-4 mr-2" />
      QR-Code
    </Button>
  </div>

  <!-- Stats Grid -->
  <div class="grid grid-cols-4 gap-4 mb-4">
    <div class="text-center">
      <p class="text-2xl font-bold">{stats.totals.registered}</p>
      <p class="text-xs text-muted-foreground">Registriert</p>
    </div>
    <div class="text-center">
      <p class="text-2xl font-bold">{stats.totals.activated}</p>
      <p class="text-xs text-muted-foreground">Aktiviert</p>
    </div>
    <div class="text-center">
      <p class="text-2xl font-bold">{stats.totals.qualified}</p>
      <p class="text-xs text-muted-foreground">Qualifiziert</p>
    </div>
    <div class="text-center">
      <p class="text-2xl font-bold text-green-600">{stats.totals.creditsEarned}</p>
      <p class="text-xs text-muted-foreground">Credits</p>
    </div>
  </div>

  <!-- Tier Progress -->
  {#if stats.tier.nextTier}
    <div class="space-y-2">
      <div class="flex justify-between text-sm">
        <span>Fortschritt zu {stats.tier.nextTier}</span>
        <span>{stats.tier.qualifiedCount}/{stats.tier.nextTierAt}</span>
      </div>
      <Progress value={stats.tier.progress * 100} />
      <p class="text-xs text-muted-foreground">
        Bei {stats.tier.nextTier}: {stats.tier.multiplier * 1.5}x Bonus
      </p>
    </div>
  {:else}
    <p class="text-sm text-center text-muted-foreground">
      💎 Maximales Tier erreicht! (3x Bonus)
    </p>
  {/if}

  <!-- Link to full page -->
  <Button variant="link" class="w-full mt-4" href="/referrals">
    Alle Referrals anzeigen →
  </Button>
</Card>

10.3 Referral-Detail-Seite

Datei: apps/manacore/apps/web/src/routes/(app)/referrals/+page.svelte

Enthält:

  • Vollständige Tier-Anzeige mit Progress
  • Code-Management (Custom-Codes erstellen/löschen)
  • Liste aller geworbenen User mit Filter
  • Cross-App-Anzeige pro User
  • Activity-Feed

11. Implementierungs-Roadmap

Phase 1: Foundation (1-2 Wochen)

[ ] Datenbank-Schema erstellen & migrieren
[ ] Basis-Services implementieren:
    [ ] ReferralCodeService (CRUD für Codes)
    [ ] ReferralTrackingService (Beziehungen & Stages)
    [ ] ReferralTierService (Tier-Berechnung)
[ ] API-Endpoints:
    [ ] GET /api/referrals/validate/:code
    [ ] GET /api/referrals/codes
    [ ] POST /api/referrals/codes
    [ ] DELETE /api/referrals/codes/:id
[ ] Integration in Registrierung:
    [ ] Auto-Code bei User-Erstellung
    [ ] Referral bei Registrierung anwenden
    [ ] Referee-Bonus (25 Credits)

Phase 2: Tracking & Bonuses (1 Woche)

[ ] Stage-Tracking implementieren:
    [ ] Activation-Detection (erste Credit-Nutzung)
    [ ] Qualification-Detection (erster Kauf)
    [ ] Retention-Cron (30-Tage-Check)
[ ] Bonus-System:
    [ ] Bonus-Event erstellen
    [ ] Credit-Transaction mit Referenz
    [ ] Tier-Multiplier anwenden
[ ] API-Endpoints:
    [ ] GET /api/referrals/stats
    [ ] GET /api/referrals/referred-users

Phase 3: Fraud Detection (1-2 Wochen)

[ ] Fingerprinting:
    [ ] IP-Hashing & Kategorisierung
    [ ] Device-Fingerprint-Erfassung
    [ ] VPN/Proxy-Detection (externe API)
[ ] Fraud-Scoring:
    [ ] Signal-Erfassung
    [ ] Score-Berechnung
    [ ] Threshold-basierte Actions
[ ] Hold-System:
    [ ] Auto-Hold bei High-Score
    [ ] Review-Queue
    [ ] Release/Reject-Logic
[ ] Rate-Limiting:
    [ ] Redis-basiertes Rate-Limiting
    [ ] Per-IP, Per-User, Per-Code Limits

Phase 4: Cross-App (3-4 Tage)

[ ] Cross-App-Tracking:
    [ ] App-Nutzung bei Credit-Usage tracken
    [ ] Einmalig-pro-App-Logik
    [ ] Bonus-Auszahlung
[ ] Integration in alle Apps:
    [ ] Credit-Service Hook
    [ ] Event-Emission

Phase 5: UI (1 Woche)

[ ] Registrierung erweitern:
    [ ] Referral-Code-Input
    [ ] URL-Parameter Support (?ref=CODE)
    [ ] Validierungs-Feedback
[ ] Dashboard-Widget:
    [ ] Code-Anzeige & Copy
    [ ] Stats-Übersicht
    [ ] Tier-Progress
[ ] Referral-Seite:
    [ ] Vollständige Stats
    [ ] Code-Management
    [ ] User-Liste mit Filter
    [ ] Activity-Feed
[ ] Share-Features:
    [ ] Social-Share-Buttons
    [ ] QR-Code-Generator
    [ ] OG-Tags für Share-Links

Phase 6: Admin & Analytics (3-4 Tage)

[ ] Admin-Dashboard:
    [ ] Overview-Stats
    [ ] Top-Referrer
    [ ] Fraud-Stats
[ ] Review-Queue-UI:
    [ ] Liste mit Filtering
    [ ] Approve/Reject-Actions
    [ ] Notes
[ ] Campaign-Management:
    [ ] Campaign-Code erstellen
    [ ] Tracking

12. Technische Notizen

12.1 Externe Abhängigkeiten

Service Zweck Kosten
IPinfo / IPdata IP-Kategorisierung (VPN/Proxy) ~$50/mo
FingerprintJS Device-Fingerprinting Free tier available
Redis Rate-Limiting & Caching Bereits vorhanden?

12.2 Performance-Überlegungen

  • Fraud-Score-Berechnung: Async, nicht blockierend
  • Stats-Aggregation: Materialized Views oder Caching
  • Rate-Limit-Checks: Redis mit TTL
  • Daily-Stats: Cron-Job für Aggregation

12.3 Datenschutz

  • IPs werden gehasht gespeichert (SHA256)
  • Referee-Namen werden anonymisiert ("Max M.")
  • Fingerprints werden nach 90 Tagen gelöscht
  • GDPR-Konformität: Referral-Daten können auf Anfrage gelöscht werden

13. Risiken & Mitigationen

Risiko Wahrscheinlichkeit Impact Mitigation
Fraud-Ring Mittel Hoch Multi-Layer Detection + Review
Bot-Registrierungen Hoch Mittel Rate-Limiting + Captcha
Credit-Inflation Niedrig Hoch Monatliche Limits + Monitoring
Performance-Issues Niedrig Mittel Caching + Async Processing
False Positives Mittel Mittel Review-Queue + Appeal-Prozess

14. Metriken & KPIs

Metrik Ziel Tracking
Referral-Rate 10% der Registrierungen Daily
Conversion (Reg → Qualified) 30% Weekly
Fraud-Rate < 5% Daily
False-Positive-Rate < 1% Weekly
Avg. Credits pro Referrer 100/Monat Monthly
Top-Tier-Anteil 5% Gold/Platinum Monthly

Dokument finalisiert - Bereit zur Implementierung