'Legal Check Invalid', 'tenant_slug' => $slug . '-invalid', 'display_name' => 'Legal Check', 'email' => 'invalid-' . $email, 'password' => 'Legal-check-123!', 'password_confirm' => 'Legal-check-123!', 'customer_type' => 'consumer', ]); $assert('Registrierung ohne Rechtstextannahme wird abgelehnt', $missingAcceptance['ok'] === false); $registration = saas_register_tenant_owner($pdo, [ 'tenant_name' => 'Legal Compliance Check', 'tenant_slug' => $slug, 'display_name' => 'Legal Check', 'email' => $email, 'password' => 'Legal-check-123!', 'password_confirm' => 'Legal-check-123!', 'customer_type' => 'consumer', 'accept_terms' => true, 'acknowledge_privacy' => true, 'accept_dpa' => true, ]); $assert('B2C-Registrierung gelingt mit Pflichtbestätigungen', $registration['ok'] === true); $tenantId = (int)($registration['identity']['tenant_id'] ?? 0); $userId = (int)($registration['identity']['user_id'] ?? 0); if ($tenantId > 0) { $stmt = $pdo->prepare('SELECT customer_type FROM tenants WHERE id = ?'); $stmt->execute([$tenantId]); $assert('Vertragstyp Verbraucher wird gespeichert', $stmt->fetchColumn() === 'consumer'); $stmt = $pdo->prepare('SELECT document_type, document_version, metadata_json FROM legal_acceptances WHERE tenant_id = ? ORDER BY document_type'); $stmt->execute([$tenantId]); $acceptances = $stmt->fetchAll(); $types = array_column($acceptances, 'document_type'); $assert('AGB, AVV und Datenschutzhinweis werden versioniert protokolliert', $types === ['dpa', 'privacy_notice', 'terms']); $acceptanceEvidence = json_decode((string)$acceptances[0]['metadata_json'], true); $assert('Rechtstext-Hash ist im Nachweis enthalten', !empty($acceptanceEvidence['document_sha256'])); $assert('Vertragsreferenz bleibt im Nachweis auch nach Kontolöschung erhalten', ($acceptanceEvidence['tenant_slug'] ?? null) === $slug && ($acceptanceEvidence['user_email'] ?? null) === $email); $input = [ 'request_type' => 'cancellation', 'requester_name' => 'Legal Check', 'requester_email' => $email, 'contract_reference' => $slug, 'cancellation_kind' => 'ordinary', 'requested_end' => 'earliest', 'request_reason' => '', 'metadata' => ['cancellation_kind' => 'ordinary'], ]; $stored = app_store_legal_request($pdo, $input); $processing = app_process_cancellation_request($pdo, $stored, $input); $assert('Kündigung wird eindeutig dem Vertrag zugeordnet', $stored['tenant_id'] === $tenantId); $assert('Kostenloser Vertrag wird zum bestätigten Zeitpunkt beendet', $processing['status'] === 'contract_ended'); $confirmation = app_fetch_legal_request_by_token($pdo, $stored['access_token']); $assert('Dauerhafte Kündigungsbestätigung ist per Geheimtoken abrufbar', $confirmation !== null && $confirmation['reference_code'] === $stored['reference_code']); $_GET = ['token' => $stored['access_token']]; ob_start(); include __DIR__ . '/../rechtserklaerung-bestaetigung.php'; $confirmationText = (string)ob_get_clean(); $_GET = []; $assert('Bestätigungsdownload lässt sich mit einem gültigen Token rendern', str_contains($confirmationText, $stored['reference_code'])); $export = app_export_tenant_data($pdo, $tenantId); $assert('Datenexport enthält Legal-Nachweise', count($export['legal_acceptances']) === 3 && count($export['legal_requests']) === 1); $pdo->prepare('DELETE FROM legal_requests WHERE reference_code = ?')->execute([$stored['reference_code']]); $pdo->prepare('DELETE FROM legal_acceptances WHERE tenant_id = ?')->execute([$tenantId]); $pdo->prepare('DELETE FROM tenants WHERE id = ?')->execute([$tenantId]); $pdo->prepare('DELETE FROM users WHERE id = ?')->execute([$userId]); } $withdrawalSlug = $slug . '-withdrawal'; $withdrawalEmail = 'withdrawal-' . $email; $withdrawalRegistration = saas_register_tenant_owner($pdo, [ 'tenant_name' => 'Legal Withdrawal Check', 'tenant_slug' => $withdrawalSlug, 'display_name' => 'Withdrawal Check', 'email' => $withdrawalEmail, 'password' => 'Legal-check-123!', 'password_confirm' => 'Legal-check-123!', 'customer_type' => 'consumer', 'accept_terms' => true, 'acknowledge_privacy' => true, 'accept_dpa' => true, ]); if ($withdrawalRegistration['ok']) { $withdrawalTenantId = (int)$withdrawalRegistration['identity']['tenant_id']; $withdrawalUserId = (int)$withdrawalRegistration['identity']['user_id']; $withdrawalInput = [ 'request_type' => 'withdrawal', 'requester_name' => 'Withdrawal Check', 'requester_email' => $withdrawalEmail, 'contract_reference' => $withdrawalSlug, 'request_reason' => '', ]; $withdrawalStored = app_store_legal_request($pdo, $withdrawalInput); $withdrawalProcessing = app_process_withdrawal_request($pdo, $withdrawalStored); $assert('B2C-Widerruf innerhalb von 14 Tagen beendet den kostenlosen Vertrag', $withdrawalProcessing['status'] === 'withdrawal_effective'); $stmt = $pdo->prepare('SELECT contract_ends_at FROM tenants WHERE id = ?'); $stmt->execute([$withdrawalTenantId]); $assert('Widerruf sperrt den Vertrag technisch', $stmt->fetchColumn() !== null); $pdo->prepare('DELETE FROM legal_requests WHERE reference_code = ?')->execute([$withdrawalStored['reference_code']]); $pdo->prepare('DELETE FROM legal_acceptances WHERE tenant_id = ?')->execute([$withdrawalTenantId]); $pdo->prepare('DELETE FROM tenants WHERE id = ?')->execute([$withdrawalTenantId]); $pdo->prepare('DELETE FROM users WHERE id = ?')->execute([$withdrawalUserId]); } else { $assert('B2C-Widerrufstest konnte registriert werden', false); } $css = file_get_contents(__DIR__ . '/../assets/css/main.css') ?: ''; $assert('Kein dynamischer Google-Fonts-Abruf im Haupt-CSS', !str_contains($css, 'fonts.googleapis.com')); $assert('Widerrufsfunktion ist hervorgehoben verlinkt', str_contains(app_public_legal_footer(), 'Vertrag widerrufen')); $assert('Kündigungsschaltfläche ist ständig verlinkt', str_contains(app_public_legal_footer(), 'Verträge hier kündigen')); $assert('Bestellschaltfläche weist eindeutig auf die Zahlungspflicht hin', str_contains((string)file_get_contents(__DIR__ . '/../abo-bestellen.php'), 'zahlungspflichtig bestellen')); $stripeCode = (string)file_get_contents(__DIR__ . '/../app/stripe.php'); $assert('Stripe-Portal ist auf das Ändern des Zahlungsmittels beschränkt', str_contains($stripeCode, "'type' => 'payment_method_update'")); if ($failures !== []) { echo "\nLegal compliance flow failed with " . count($failures) . " failure(s):\n"; foreach ($failures as $failure) { echo "- {$failure}\n"; } exit(1); } echo "\nLegal compliance flow passed with {$passes} assertions.\n";