/** * ============================================================================ * FELSENNET-MODDING - LIZENZ-API (PLESK-KOMPATIBEL) * ============================================================================ * * Domain: license.felsennet.de * * Endpunkte: * - POST /validate.php → Lizenz validieren * - POST /activate.php → Lizenz aktivieren * - POST /purchase.php → Kauf erstellen * - GET /products.php → Produkte laden * * ============================================================================ */ header('Content-Type: application/json; charset=utf-8'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type'); // Preflight if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; } // ============================================================================ // DATENBANK-KONFIGURATION // ============================================================================ define('DB_HOST', 'localhost'); define('DB_NAME', 'felsennet_licenses'); define('DB_USER', 'felsennet_user'); // In Plesk anpassen define('DB_PASS', 'dein_passwort'); // In Plesk anpassen // ============================================================================ // DATENBANK-VERBINDUNG // ============================================================================ function getDB() { try { $pdo = new PDO( 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4', DB_USER, DB_PASS, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false ] ); return $pdo; } catch (PDOException $e) { sendError('Datenbankverbindung fehlgeschlagen', 500); } } // ============================================================================ // HILFSFUNKTIONEN // ============================================================================ function sendJSON($data, $code = 200) { http_response_code($code); echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); exit; } function sendError($message, $code = 400) { sendJSON(['success' => false, 'error' => $message], $code); } function generateLicenseKey($productId) { $prefix = strtoupper(substr($productId, 0, 4)); $segments = []; for ($i = 0; $i < 3; $i++) { $segment = ''; for ($j = 0; $j < 4; $j++) { $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $segment .= $chars[random_int(0, strlen($chars) - 1)]; } $segments[] = $segment; } return "FN-{$prefix}-" . implode('-', $segments); } function hashServerKey($serverKey) { return hash('sha256', $serverKey); } function getClientIP() { return $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'unknown'; } function logActivation($db, $licenseKey, $serverKey, $action, $status) { $stmt = $db->prepare(" INSERT INTO activation_logs (license_key, server_key, ip_address, action, status) VALUES (?, ?, ?, ?, ?) "); $stmt->execute([$licenseKey, $serverKey, getClientIP(), $action, $status]); } // ============================================================================ // INPUT VERARBEITUNG // ============================================================================ $method = $_SERVER['REQUEST_METHOD']; $input = json_decode(file_get_contents('php://input'), true) ?? $_POST; // ============================================================================ // ROUTING // ============================================================================ $action = $_GET['action'] ?? ''; switch ($action) { // ======================================================================== // LIZENZ VALIDIEREN (von FiveM Script aufgerufen) // ======================================================================== case 'validate': if ($method !== 'POST') sendError('Nur POST erlaubt', 405); $license = $input['license'] ?? ''; $serverKey = $input['serverKey'] ?? ''; $productId = $input['productId'] ?? ''; if (!$license || !$serverKey) { sendError('Fehlende Parameter'); } $db = getDB(); // Lizenz suchen $stmt = $db->prepare("SELECT * FROM licenses WHERE license_key = ? AND product_id = ?"); $stmt->execute([$license, $productId]); $lic = $stmt->fetch(); if (!$lic) { logActivation($db, $license, $serverKey, 'validate', 'invalid_license'); sendError('Lizenz nicht gefunden', 404); } // Prüfe ob abgelaufen if ($lic['expires_at'] && strtotime($lic['expires_at']) < time()) { logActivation($db, $license, $serverKey, 'validate', 'expired'); sendError('Lizenz abgelaufen', 403); } // Prüfe Status if (in_array($lic['status'], ['banned', 'revoked'])) { logActivation($db, $license, $serverKey, 'validate', 'banned'); sendError('Lizenz widerrufen', 403); } // Erste Aktivierung if ($lic['status'] === 'pending' && empty($lic['server_key_hash'])) { $stmt = $db->prepare(" UPDATE licenses SET server_key = ?, server_key_hash = ?, ip_address = ?, status = 'active', activated_at = NOW() WHERE license_key = ? "); $stmt->execute([$serverKey, hashServerKey($serverKey), getClientIP(), $license]); logActivation($db, $license, $serverKey, 'activate', 'success'); sendJSON([ 'valid' => true, 'message' => 'Lizenz aktiviert', 'productName' => $lic['product_name'] ]); } // Bereits aktiviert - Server-Key prüfen if ($lic['server_key_hash'] === hashServerKey($serverKey)) { logActivation($db, $license, $serverKey, 'validate', 'success'); sendJSON([ 'valid' => true, 'productName' => $lic['product_name'] ]); } else { logActivation($db, $license, $serverKey, 'validate', 'wrong_server'); sendJSON([ 'valid' => false, 'error' => 'Lizenz an anderen Server gebunden', 'supportUrl' => 'https://discord.gg/felsennet' ], 403); } break; // ======================================================================== // PRODUKTE LADEN // ======================================================================== case 'products': $db = getDB(); $stmt = $db->query("SELECT * FROM products WHERE active = 1 ORDER BY created_at DESC"); $products = $stmt->fetchAll(); // Download-URL entfernen (für öffentliche API) foreach ($products as &$p) { unset($p['download_url']); } sendJSON(['success' => true, 'products' => $products]); break; // ======================================================================== // PRODUKT DETAILS // ======================================================================== case 'product': $productId = $_GET['id'] ?? ''; if (!$productId) sendError('Produkt-ID fehlt'); $db = getDB(); $stmt = $db->prepare("SELECT * FROM products WHERE product_id = ? AND active = 1"); $stmt->execute([$productId]); $product = $stmt->fetch(); if (!$product) sendError('Produkt nicht gefunden', 404); unset($product['download_url']); sendJSON(['success' => true, 'product' => $product]); break; // ======================================================================== // KAUF ERSTELLEN (nach PayPal Zahlung) // ======================================================================== case 'purchase': if ($method !== 'POST') sendError('Nur POST erlaubt', 405); $productId = $input['productId'] ?? ''; $customerEmail = $input['customerEmail'] ?? ''; $customerName = $input['customerName'] ?? ''; $customerDiscord = $input['customerDiscord'] ?? ''; $paymentId = $input['paymentId'] ?? ''; $price = floatval($input['price'] ?? 0); if (!$productId || !$customerEmail) { sendError('Fehlende Pflichtfelder'); } $db = getDB(); // Produkt laden $stmt = $db->prepare("SELECT * FROM products WHERE product_id = ? AND active = 1"); $stmt->execute([$productId]); $product = $stmt->fetch(); if (!$product) sendError('Produkt nicht gefunden', 404); // Lizenz generieren $licenseKey = generateLicenseKey($productId); // Lizenz speichern $stmt = $db->prepare(" INSERT INTO licenses ( license_key, product_id, product_name, customer_email, customer_name, customer_discord, purchase_price, payment_id, status ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending') "); $stmt->execute([ $licenseKey, $productId, $product['name'], $customerEmail, $customerName, $customerDiscord, $price, $paymentId ]); sendJSON([ 'success' => true, 'licenseKey' => $licenseKey, 'productName' => $product['name'], 'downloadUrl' => $product['download_url'] ]); break; // ======================================================================== // ADMIN: LOGIN // ======================================================================== case 'admin-login': if ($method !== 'POST') sendError('Nur POST erlaubt', 405); $username = $input['username'] ?? ''; $password = $input['password'] ?? ''; $db = getDB(); $stmt = $db->prepare("SELECT * FROM admin_users WHERE username = ?"); $stmt->execute([$username]); $admin = $stmt->fetch(); if (!$admin || $admin['password_hash'] !== hash('sha256', $password)) { sendError('Ungültige Anmeldedaten', 401); } $token = bin2hex(random_bytes(32)); // Token speichern (optional: separate Tabelle) $stmt = $db->prepare("UPDATE admin_users SET last_login = NOW() WHERE id = ?"); $stmt->execute([$admin['id']]); sendJSON([ 'success' => true, 'token' => $token, 'role' => $admin['role'] ]); break; // ======================================================================== // ADMIN: ALLE LIZENZEN // ======================================================================== case 'admin-licenses': $db = getDB(); $stmt = $db->query(" SELECT l.*, p.name as product_name FROM licenses l LEFT JOIN products p ON l.product_id = p.product_id ORDER BY l.created_at DESC "); $licenses = $stmt->fetchAll(); sendJSON(['success' => true, 'licenses' => $licenses]); break; // ======================================================================== // ADMIN: SERVER-KEY RESET // ======================================================================== case 'admin-reset-key': if ($method !== 'POST') sendError('Nur POST erlaubt', 405); $licenseKey = $input['licenseKey'] ?? ''; $reason = $input['reason'] ?? ''; if (!$licenseKey) sendError('Lizenz-Key fehlt'); $db = getDB(); $stmt = $db->prepare("SELECT * FROM licenses WHERE license_key = ?"); $stmt->execute([$licenseKey]); $lic = $stmt->fetch(); if (!$lic) sendError('Lizenz nicht gefunden', 404); $stmt = $db->prepare(" UPDATE licenses SET server_key = NULL, server_key_hash = NULL, status = 'pending', reset_count = reset_count + 1, notes = CONCAT(COALESCE(notes, ''), '\nKey reset: ?, ?') WHERE license_key = ? "); $stmt->execute([$reason, date('Y-m-d H:i:s'), $licenseKey]); logActivation($db, $licenseKey, '', 'reset', $reason); sendJSON(['success' => true, 'message' => 'Server-Key zurückgesetzt']); break; // ======================================================================== // ADMIN: LIZENZ WIDERRUFEN // ======================================================================== case 'admin-revoke': if ($method !== 'POST') sendError('Nur POST erlaubt', 405); $licenseKey = $input['licenseKey'] ?? ''; $reason = $input['reason'] ?? ''; if (!$licenseKey) sendError('Lizenz-Key fehlt'); $db = getDB(); $stmt = $db->prepare(" UPDATE licenses SET status = 'revoked', notes = CONCAT(COALESCE(notes, ''), '\nRevoked: ?, ?') WHERE license_key = ? "); $stmt->execute([$reason, date('Y-m-d H:i:s'), $licenseKey]); sendJSON(['success' => true, 'message' => 'Lizenz widerrufen']); break; // ======================================================================== // ADMIN: STATISTIKEN // ======================================================================== case 'admin-stats': $db = getDB(); $stats = []; // Gesamt Lizenzen $stmt = $db->query("SELECT COUNT(*) as count FROM licenses"); $stats['totalLicenses'] = $stmt->fetch()['count']; // Aktive Lizenzen $stmt = $db->query("SELECT COUNT(*) as count FROM licenses WHERE status = 'active'"); $stats['activeLicenses'] = $stmt->fetch()['count']; // Gesamt Umsatz $stmt = $db->query("SELECT SUM(purchase_price) as total FROM licenses WHERE payment_id IS NOT NULL"); $stats['totalRevenue'] = floatval($stmt->fetch()['total'] ?? 0); // Lizenzen diesen Monat $stmt = $db->query("SELECT COUNT(*) as count FROM licenses WHERE created_at >= DATE_FORMAT(NOW(), '%Y-%m-01')"); $stats['thisMonthLicenses'] = $stmt->fetch()['count']; // Umsatz diesen Monat $stmt = $db->query("SELECT SUM(purchase_price) as total FROM licenses WHERE created_at >= DATE_FORMAT(NOW(), '%Y-%m-01') AND payment_id IS NOT NULL"); $stats['thisMonthRevenue'] = floatval($stmt->fetch()['total'] ?? 0); sendJSON(['success' => true, 'stats' => $stats]); break; // ======================================================================== // ADMIN: PRODUKT ERSTELLEN // ======================================================================== case 'admin-create-product': if ($method !== 'POST') sendError('Nur POST erlaubt', 405); $productId = $input['productId'] ?? ''; $name = $input['name'] ?? ''; $description = $input['description'] ?? ''; $price = floatval($input['price'] ?? 0); $downloadUrl = $input['downloadUrl'] ?? ''; $imageUrl = $input['imageUrl'] ?? ''; if (!$productId || !$name || $price <= 0) { sendError('Fehlende oder ungültige Pflichtfelder'); } $db = getDB(); $stmt = $db->prepare(" INSERT INTO products (product_id, name, description, price, download_url, image_url) VALUES (?, ?, ?, ?, ?, ?) "); $stmt->execute([$productId, $name, $description, $price, $downloadUrl, $imageUrl]); sendJSON(['success' => true, 'message' => 'Produkt erstellt']); break; // ======================================================================== // SUPPORT TICKET ERSTELLEN // ======================================================================== case 'support-ticket': if ($method !== 'POST') sendError('Nur POST erlaubt', 405); $licenseKey = $input['licenseKey'] ?? ''; $customerEmail = $input['customerEmail'] ?? ''; $customerDiscord = $input['customerDiscord'] ?? ''; $subject = $input['subject'] ?? ''; $message = $input['message'] ?? ''; $priority = $input['priority'] ?? 'normal'; if (!$subject || !$message) { sendError('Betreff und Nachricht erforderlich'); } $ticketId = 'TKT-' . strtoupper(substr(md5(time() . rand()), 0, 8)); $db = getDB(); $stmt = $db->prepare(" INSERT INTO support_tickets (ticket_id, license_key, customer_email, customer_discord, subject, message, priority) VALUES (?, ?, ?, ?, ?, ?, ?) "); $stmt->execute([$ticketId, $licenseKey, $customerEmail, $customerDiscord, $subject, $message, $priority]); sendJSON(['success' => true, 'ticketId' => $ticketId]); break; // ======================================================================== // DEFAULT // ======================================================================== default: sendJSON([ 'name' => 'Felsennet-Modding License API', 'version' => '1.0.0', 'endpoints' => [ 'validate' => 'POST /?action=validate', 'products' => 'GET /?action=products', 'product' => 'GET /?action=product&id=XXX', 'purchase' => 'POST /?action=purchase' ] ]); }