Files
kaffeekasse-saas/scripts/check-legal-compliance-flow.php

155 lines
7.4 KiB
PHP

<?php
declare(strict_types=1);
require __DIR__ . '/dev-db.php';
require_once __DIR__ . '/../app/saas-auth.php';
require_once __DIR__ . '/../app/legal-requests.php';
require_once __DIR__ . '/../app/data-export.php';
$pdo = dev_pdo();
$suffix = bin2hex(random_bytes(4));
$slug = 'legal-check-' . $suffix;
$email = $slug . '@test.local';
$failures = [];
$passes = 0;
$assert = static function (string $label, bool $condition) use (&$failures, &$passes): void {
if ($condition) {
$passes++;
echo "PASS {$label}\n";
} else {
$failures[] = $label;
echo "FAIL {$label}\n";
}
};
$missingAcceptance = saas_register_tenant_owner($pdo, [
'tenant_name' => '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";