widerrufsbelehrung hinzugefügt und in den checkout mit eingebunden. refund ins backend eingebaut.

This commit is contained in:
Codex Agent
2025-12-07 11:57:05 +01:00
parent e092f72475
commit 1d3d49e05a
44 changed files with 1143 additions and 71 deletions

View File

@@ -38,18 +38,10 @@ class LegalController extends BaseController
public function show(Request $request, string $slug)
{
$locale = $request->query('lang', 'de');
// Support common English aliases as fallbacks
$s = strtolower($slug);
$aliasMap = [
'imprint' => 'impressum',
'privacy' => 'datenschutz',
'terms' => 'agb',
];
$resolved = $aliasMap[$s] ?? $s;
$slugs = $this->resolveSlugs($slug);
$page = LegalPage::query()
->where('slug', $resolved)
->whereIn('slug', $slugs)
->where('is_published', true)
->orderByDesc('version')
->first();
@@ -59,7 +51,7 @@ class LegalController extends BaseController
'Legal Page Not Found',
'The requested legal document does not exist.',
Response::HTTP_NOT_FOUND,
['slug' => $resolved]
['slug' => $slugs[0]]
);
}
@@ -77,6 +69,28 @@ class LegalController extends BaseController
])->header('Cache-Control', 'no-store');
}
protected function resolveSlugs(string $slug): array
{
$s = strtolower($slug);
$aliasMap = [
'imprint' => 'impressum',
'privacy' => 'datenschutz',
'terms' => 'agb',
'withdrawal' => 'widerrufsbelehrung',
'cancellation' => 'widerrufsbelehrung',
'cancellation-policy' => 'widerrufsbelehrung',
'widerruf' => 'widerrufsbelehrung',
];
$canonical = $aliasMap[$s] ?? $s;
return array_values(array_unique(
$canonical === 'widerrufsbelehrung'
? ['widerrufsbelehrung', 'widerruf']
: [$canonical]
));
}
protected function convertMarkdownToHtml(string $markdown): string
{
return trim((string) $this->markdown->convert($markdown));

View File

@@ -20,12 +20,12 @@ class LegalPageController extends Controller
{
public function show(Request $request, string $locale, ?string $slug = null): Response
{
$resolvedSlug = $this->resolveSlug($slug);
$slugCandidates = $this->resolveSlugs($slug);
$page = null;
try {
$page = LegalPage::query()
->where('slug', $resolvedSlug)
->whereIn('slug', $slugCandidates)
->where('is_published', true)
->orderByDesc('version')
->first();
@@ -37,7 +37,7 @@ class LegalPageController extends Controller
$locale = $request->route('locale', app()->getLocale());
if (! $page) {
$fallback = $this->loadFallbackDocument($resolvedSlug, $locale);
$fallback = $this->loadFallbackDocument($slugCandidates, $locale);
if (! $fallback) {
abort(404);
@@ -50,7 +50,7 @@ class LegalPageController extends Controller
'effectiveFrom' => null,
'effectiveFromLabel' => null,
'versionLabel' => null,
'slug' => $resolvedSlug,
'slug' => $slugCandidates[0],
]);
}
@@ -58,7 +58,8 @@ class LegalPageController extends Controller
?? $page->title[$page->locale_fallback]
?? $page->title['de']
?? $page->title['en']
?? Str::title($resolvedSlug);
?? Str::title($slugCandidates[0]);
$title = Str::ucfirst($title);
$bodyMarkdown = $page->body_markdown[$locale]
?? $page->body_markdown[$page->locale_fallback]
@@ -76,11 +77,11 @@ class LegalPageController extends Controller
? __('legal.effective_from', ['date' => $effectiveFrom->translatedFormat('d. F Y')])
: null,
'versionLabel' => __('legal.version', ['version' => $page->version]),
'slug' => $resolvedSlug,
'slug' => $slugCandidates[0],
]);
}
private function resolveSlug(?string $slug): string
private function resolveSlugs(?string $slug): array
{
$slug = strtolower($slug ?? '');
@@ -88,9 +89,20 @@ class LegalPageController extends Controller
'imprint' => 'impressum',
'privacy' => 'datenschutz',
'terms' => 'agb',
'withdrawal' => 'widerrufsbelehrung',
'cancellation' => 'widerrufsbelehrung',
'cancellation-policy' => 'widerrufsbelehrung',
'widerruf' => 'widerrufsbelehrung',
];
return $aliases[$slug] ?? $slug ?: 'impressum';
$canonical = $aliases[$slug] ?? $slug ?: 'impressum';
// Support both slugs for withdrawal in case DB uses a shorter slug
$fallbacks = $canonical === 'widerrufsbelehrung'
? ['widerrufsbelehrung', 'widerruf']
: [$canonical];
return array_values(array_unique($fallbacks));
}
private function convertMarkdownToHtml(string $markdown): string
@@ -111,7 +123,7 @@ class LegalPageController extends Controller
return trim((string) $converter->convert($markdown));
}
private function loadFallbackDocument(string $slug, string $locale): ?array
private function loadFallbackDocument(array $slugCandidates, string $locale): ?array
{
$candidates = array_unique([
strtolower($locale),
@@ -120,20 +132,22 @@ class LegalPageController extends Controller
'en',
]);
foreach ($candidates as $candidateLocale) {
$path = base_path("docs/legal/{$slug}-{$candidateLocale}.md");
foreach ($slugCandidates as $slug) {
foreach ($candidates as $candidateLocale) {
$path = base_path("docs/legal/{$slug}-{$candidateLocale}.md");
if (! is_file($path)) {
continue;
if (! is_file($path)) {
continue;
}
$markdown = (string) file_get_contents($path);
$title = $this->extractTitleFromMarkdown($markdown) ?? Str::title($slug);
return [
'markdown' => $markdown,
'title' => $title,
];
}
$markdown = (string) file_get_contents($path);
$title = $this->extractTitleFromMarkdown($markdown) ?? Str::title($slug);
return [
'markdown' => $markdown,
'title' => $title,
];
}
return null;

View File

@@ -132,6 +132,13 @@ class MarketingController extends Controller
Log::info('Buy packages called', ['auth' => Auth::check(), 'locale' => $locale, 'package_id' => $packageId]);
$package = Package::findOrFail($packageId);
$requiresWaiver = (bool) ($package->activates_immediately ?? true);
$request->validate([
'accepted_terms' => ['sometimes', 'boolean', 'accepted'],
'accepted_waiver' => ['sometimes', 'boolean'],
]);
$couponCode = $this->rememberCouponFromRequest($request, $package);
if (! Auth::check()) {
@@ -194,6 +201,16 @@ class MarketingController extends Controller
$this->checkoutSessions->selectProvider($session, CheckoutSession::PROVIDER_PADDLE);
$now = now();
$session->forceFill([
'accepted_terms_at' => $request->boolean('accepted_terms') ? $now : null,
'accepted_privacy_at' => $request->boolean('accepted_terms') ? $now : null,
'accepted_withdrawal_notice_at' => $request->boolean('accepted_terms') ? $now : null,
'digital_content_waiver_at' => $requiresWaiver && $request->boolean('accepted_waiver') ? $now : null,
'legal_version' => $this->resolveLegalVersion(),
])->save();
$appliedDiscountId = null;
if ($couponCode) {
@@ -219,6 +236,9 @@ class MarketingController extends Controller
'metadata' => [
'checkout_session_id' => $session->id,
'coupon_code' => $couponCode,
'legal_version' => $session->legal_version,
'accepted_terms' => (bool) $session->accepted_terms_at,
'accepted_waiver' => $requiresWaiver && (bool) $session->digital_content_waiver_at,
],
'discount_id' => $appliedDiscountId,
]);
@@ -628,4 +648,9 @@ class MarketingController extends Controller
'excerpt_html' => $this->convertMarkdownToHtml($excerpt),
];
}
protected function resolveLegalVersion(): string
{
return config('app.legal_version', now()->toDateString());
}
}

View File

@@ -29,6 +29,8 @@ class PaddleCheckoutController extends Controller
'return_url' => ['nullable', 'url'],
'inline' => ['sometimes', 'boolean'],
'coupon_code' => ['nullable', 'string', 'max:64'],
'accepted_terms' => ['required', 'boolean', 'accepted'],
'accepted_waiver' => ['sometimes', 'boolean'],
]);
$user = Auth::user();
@@ -44,12 +46,30 @@ class PaddleCheckoutController extends Controller
throw ValidationException::withMessages(['package_id' => 'Package is not linked to a Paddle price.']);
}
$requiresWaiver = (bool) ($package->activates_immediately ?? true);
if ($requiresWaiver && ! $request->boolean('accepted_waiver')) {
throw ValidationException::withMessages([
'accepted_waiver' => 'Ein sofortiger Beginn der digitalen Dienstleistung erfordert Ihre ausdrückliche Zustimmung.',
]);
}
$session = $this->sessions->createOrResume($user, $package, [
'tenant' => $tenant,
]);
$this->sessions->selectProvider($session, CheckoutSession::PROVIDER_PADDLE);
$now = now();
$session->forceFill([
'accepted_terms_at' => $now,
'accepted_privacy_at' => $now,
'accepted_withdrawal_notice_at' => $now,
'digital_content_waiver_at' => $requiresWaiver ? $now : null,
'legal_version' => $this->resolveLegalVersion(),
])->save();
$couponCode = Str::upper(trim((string) ($data['coupon_code'] ?? '')));
$discountId = null;
@@ -80,6 +100,9 @@ class PaddleCheckoutController extends Controller
'tenant_id' => (string) $tenant->id,
'package_id' => (string) $package->id,
'checkout_session_id' => (string) $session->id,
'legal_version' => $session->legal_version,
'accepted_terms' => '1',
'accepted_waiver' => $requiresWaiver && $request->boolean('accepted_waiver') ? '1' : '0',
],
'customer' => array_filter([
'email' => $user->email,
@@ -94,6 +117,9 @@ class PaddleCheckoutController extends Controller
'metadata' => [
'checkout_session_id' => $session->id,
'coupon_code' => $couponCode ?: null,
'legal_version' => $session->legal_version,
'accepted_terms' => true,
'accepted_waiver' => $requiresWaiver && $request->boolean('accepted_waiver'),
],
'discount_id' => $discountId,
]);
@@ -109,4 +135,9 @@ class PaddleCheckoutController extends Controller
return response()->json($checkout);
}
protected function resolveLegalVersion(): string
{
return config('app.legal_version', now()->toDateString());
}
}