Rechtstexte und B2C-Vertragsabläufe absichern

This commit is contained in:
2026-08-22 14:31:56 +02:00
parent d320a4fd7a
commit a31a235422
58 changed files with 2502 additions and 316 deletions
+42 -8
View File
@@ -6,6 +6,7 @@ require_once __DIR__ . '/bootstrap.php';
require_once __DIR__ . '/database.php';
require_once __DIR__ . '/faq.php';
require_once __DIR__ . '/billing.php';
require_once __DIR__ . '/legal.php';
function saas_email_norm(string $email): string
{
@@ -55,7 +56,7 @@ function saas_normalize_host(?string $host): string
function saas_primary_app_host(): string
{
return saas_normalize_host(app_env('APP_PRIMARY_HOST', ''));
return saas_normalize_host(app_env('APP_PRIMARY_HOST', app_primary_host() ?? ''));
}
function saas_is_primary_app_host(?string $host = null): bool
@@ -92,6 +93,7 @@ function saas_resolve_tenant_domain(PDO $pdo, ?string $host = null): ?array
WHERE td.domain_norm = ?
AND td.status = ?
AND t.status = ?
AND (t.contract_ends_at IS NULL OR t.contract_ends_at > NOW())
LIMIT 1'
);
$stmt->execute([$host, 'active', 'active']);
@@ -166,6 +168,8 @@ function saas_current_user(?PDO $pdo = null): ?array
t.id AS tenant_id,
t.slug AS tenant_slug,
t.name AS tenant_name,
t.customer_type,
t.contract_ends_at,
t.status AS tenant_status,
tm.role,
tm.status AS membership_status
@@ -177,6 +181,7 @@ function saas_current_user(?PDO $pdo = null): ?array
AND u.status = ?
AND tm.status = ?
AND t.status = ?
AND (t.contract_ends_at IS NULL OR t.contract_ends_at > NOW())
LIMIT 1'
);
$stmt->execute([$userId, $tenantId, 'active', 'active', 'active']);
@@ -264,6 +269,7 @@ function saas_fetch_tenant_settings(PDO $pdo, int $tenantId): ?array
t.id AS tenant_id,
t.slug,
t.name,
t.customer_type,
t.status,
t.timezone,
t.locale,
@@ -312,6 +318,7 @@ function saas_fetch_tenant_settings(PDO $pdo, int $tenantId): ?array
'tenant_id' => (int)$settings['tenant_id'],
'slug' => (string)$settings['slug'],
'name' => (string)$settings['name'],
'customer_type' => (string)$settings['customer_type'],
'status' => (string)$settings['status'],
'timezone' => (string)$settings['timezone'],
'locale' => (string)$settings['locale'],
@@ -344,6 +351,7 @@ function saas_fetch_tenant_settings(PDO $pdo, int $tenantId): ?array
function saas_update_tenant_settings(PDO $pdo, int $tenantId, array $input): array
{
$tenantName = trim((string)($input['tenant_name'] ?? ''));
$customerType = (string)($input['customer_type'] ?? '');
$timezone = trim((string)($input['timezone'] ?? 'Europe/Berlin'));
$locale = trim((string)($input['locale'] ?? 'de-DE'));
$currencyCode = strtoupper(trim((string)($input['currency_code'] ?? 'EUR')));
@@ -370,6 +378,9 @@ function saas_update_tenant_settings(PDO $pdo, int $tenantId, array $input): arr
if (strlen($tenantName) < 3 || strlen($tenantName) > 255) {
$errors[] = 'Der Kundenname muss zwischen 3 und 255 Zeichen lang sein.';
}
if (!in_array($customerType, ['business', 'consumer'], true)) {
$errors[] = 'Bitte gib an, ob der Vertrag geschäftlich oder als Verbraucher geführt wird.';
}
if (!in_array($timezone, DateTimeZone::listIdentifiers(), true)) {
$errors[] = 'Die Zeitzone ist ungültig.';
}
@@ -442,10 +453,10 @@ function saas_update_tenant_settings(PDO $pdo, int $tenantId, array $input): arr
$stmt = $pdo->prepare(
'UPDATE tenants
SET name = ?, timezone = ?, locale = ?, currency_code = ?
SET name = ?, customer_type = ?, timezone = ?, locale = ?, currency_code = ?
WHERE id = ?'
);
$stmt->execute([$tenantName, $timezone, $locale, $currencyCode, $tenantId]);
$stmt->execute([$tenantName, $customerType, $timezone, $locale, $currencyCode, $tenantId]);
$stmt = $pdo->prepare(
'INSERT INTO tenant_settings
@@ -554,7 +565,8 @@ function saas_find_auth_identity(PDO $pdo, string $email, string $tenantSlug = '
WHERE u.email_norm = ?
AND u.status = ?
AND tm.status = ?
AND t.status = ?' . $tenantFilter . '
AND t.status = ?
AND (t.contract_ends_at IS NULL OR t.contract_ends_at > NOW())' . $tenantFilter . '
ORDER BY
CASE tm.role
WHEN \'owner\' THEN 1
@@ -593,6 +605,7 @@ function saas_list_user_memberships(PDO $pdo, int $userId): array
AND u.status = ?
AND tm.status = ?
AND t.status = ?
AND (t.contract_ends_at IS NULL OR t.contract_ends_at > NOW())
ORDER BY
CASE tm.role
WHEN \'owner\' THEN 1
@@ -630,6 +643,7 @@ function saas_identity_for_user_tenant(PDO $pdo, int $userId, int $tenantId): ?a
AND u.status = ?
AND tm.status = ?
AND t.status = ?
AND (t.contract_ends_at IS NULL OR t.contract_ends_at > NOW())
LIMIT 1'
);
$stmt->execute([$userId, $tenantId, 'active', 'active', 'active']);
@@ -1021,7 +1035,8 @@ function saas_authenticate(PDO $pdo, string $email, string $password, string $te
JOIN tenants t ON t.id = tm.tenant_id
WHERE tm.user_id = ?
AND tm.status = ?
AND t.status = ?' . $tenantFilter . '
AND t.status = ?
AND (t.contract_ends_at IS NULL OR t.contract_ends_at > NOW())' . $tenantFilter . '
ORDER BY
CASE tm.role
WHEN \'owner\' THEN 1
@@ -1206,6 +1221,7 @@ function saas_register_tenant_owner(PDO $pdo, array $input): array
$emailNorm = saas_email_norm($email);
$password = (string)($input['password'] ?? '');
$passwordConfirm = (string)($input['password_confirm'] ?? '');
$customerType = (string)($input['customer_type'] ?? '');
$errors = [];
if (strlen($tenantName) < 3 || strlen($tenantName) > 255) {
@@ -1226,6 +1242,18 @@ function saas_register_tenant_owner(PDO $pdo, array $input): array
if ($password !== $passwordConfirm) {
$errors[] = 'Die Passwort-Wiederholung stimmt nicht.';
}
if (!in_array($customerType, ['business', 'consumer'], true)) {
$errors[] = 'Bitte gib an, ob du als Unternehmen oder als Verbraucher handelst.';
}
if (empty($input['accept_terms'])) {
$errors[] = 'Bitte akzeptiere die AGB.';
}
if (empty($input['acknowledge_privacy'])) {
$errors[] = 'Bitte bestätige, dass du die Datenschutzerklärung zur Kenntnis genommen hast.';
}
if (empty($input['accept_dpa'])) {
$errors[] = 'Bitte vereinbare den AVV für den Fall, dass du Daten anderer Personen verwaltest.';
}
if ($errors !== []) {
return ['ok' => false, 'errors' => $errors];
@@ -1265,10 +1293,10 @@ function saas_register_tenant_owner(PDO $pdo, array $input): array
$pdo->beginTransaction();
$stmt = $pdo->prepare(
'INSERT INTO tenants (slug, name, status, timezone, locale, currency_code)
VALUES (?, ?, ?, ?, ?, ?)'
'INSERT INTO tenants (slug, name, customer_type, status, timezone, locale, currency_code)
VALUES (?, ?, ?, ?, ?, ?, ?)'
);
$stmt->execute([$tenantSlug, $tenantName, 'active', 'Europe/Berlin', 'de-DE', 'EUR']);
$stmt->execute([$tenantSlug, $tenantName, $customerType, 'active', 'Europe/Berlin', 'de-DE', 'EUR']);
$tenantId = (int)$pdo->lastInsertId();
$stmt = $pdo->prepare(
@@ -1311,6 +1339,11 @@ function saas_register_tenant_owner(PDO $pdo, array $input): array
);
$stmt->execute([$tenantId, $userId, $displayName, $email, $emailNorm, 1]);
$acceptanceMetadata = ['customer_type' => $customerType];
app_record_legal_acceptance($pdo, $tenantId, $userId, 'terms', 'registration', $acceptanceMetadata);
app_record_legal_acceptance($pdo, $tenantId, $userId, 'privacy_notice', 'registration', $acceptanceMetadata);
app_record_legal_acceptance($pdo, $tenantId, $userId, 'dpa', 'registration', $acceptanceMetadata);
$verificationToken = saas_create_auth_token($pdo, $userId, 'email_verification', $tenantId, 1440);
$pdo->commit();
@@ -1335,6 +1368,7 @@ function saas_register_tenant_owner(PDO $pdo, array $input): array
'tenant_id' => $tenantId,
'tenant_slug' => $tenantSlug,
'tenant_name' => $tenantName,
'customer_type' => $customerType,
'role' => 'owner',
],
'email_verification_token' => $verificationToken['token'],