ungültige paket-IDs werden nun abgefangen

This commit is contained in:
Codex Agent
2025-12-20 16:59:14 +01:00
parent 6500b8df2c
commit 80985828d8
7 changed files with 57 additions and 13 deletions

View File

@@ -23,8 +23,16 @@ class CheckoutController extends Controller
{ {
use PresentsPackages; use PresentsPackages;
public function show(string $locale, string $checkoutSlug, Package $package): \Inertia\Response public function show(string $locale, string $checkoutSlug, string $package): \Inertia\Response|\Illuminate\Http\RedirectResponse
{ {
$resolvedPackage = Package::query()->find($package);
if (! $resolvedPackage) {
return redirect()
->route('packages', ['locale' => $locale])
->with('error', __('marketing.packages.package_not_found'));
}
$googleStatus = session()->pull('checkout_google_status'); $googleStatus = session()->pull('checkout_google_status');
$googleError = session()->pull('checkout_google_error'); $googleError = session()->pull('checkout_google_error');
$googleProfile = session()->pull('checkout_google_profile'); $googleProfile = session()->pull('checkout_google_profile');
@@ -35,7 +43,7 @@ class CheckoutController extends Controller
->all(); ->all();
return Inertia::render('marketing/CheckoutWizardPage', [ return Inertia::render('marketing/CheckoutWizardPage', [
'package' => $this->presentPackage($package), 'package' => $this->presentPackage($resolvedPackage),
'packageOptions' => $packageOptions, 'packageOptions' => $packageOptions,
'privacyHtml' => view('legal.datenschutz-partial')->render(), 'privacyHtml' => view('legal.datenschutz-partial')->render(),
'auth' => [ 'auth' => [

View File

@@ -2,10 +2,10 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use App\Support\LocaleConfig;
use Illuminate\Foundation\Inspiring; use Illuminate\Foundation\Inspiring;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Inertia\Middleware; use Inertia\Middleware;
use App\Support\LocaleConfig;
class HandleInertiaRequests extends Middleware class HandleInertiaRequests extends Middleware
{ {
@@ -63,6 +63,8 @@ class HandleInertiaRequests extends Middleware
'dashboard' => __('dashboard'), 'dashboard' => __('dashboard'),
], ],
'flash' => [ 'flash' => [
'success' => fn () => $request->session()->get('success'),
'error' => fn () => $request->session()->get('error'),
'verification' => fn () => $request->session()->get('verification'), 'verification' => fn () => $request->session()->get('verification'),
], ],
]; ];

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useMemo, useRef, useLayoutEffect } from 'react'; import React, { useState, useEffect, useMemo, useRef, useLayoutEffect } from 'react';
import { Link } from '@inertiajs/react'; import { Link, usePage } from '@inertiajs/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
@@ -16,6 +16,7 @@ import { useCtaExperiment } from '@/hooks/useCtaExperiment';
import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes'; import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes';
import { useLocale } from '@/hooks/useLocale'; import { useLocale } from '@/hooks/useLocale';
import { ArrowRight, Check, Star } from 'lucide-react'; import { ArrowRight, Check, Star } from 'lucide-react';
import toast from 'react-hot-toast';
interface Package { interface Package {
id: number; id: number;
@@ -255,11 +256,18 @@ const Packages: React.FC<PackagesProps> = ({ endcustomerPackages, resellerPackag
const locale = useLocale(); const locale = useLocale();
const { t } = useTranslation('marketing'); const { t } = useTranslation('marketing');
const { t: tCommon } = useTranslation('common'); const { t: tCommon } = useTranslation('common');
const { flash } = usePage<{ flash?: { error?: string } }>().props;
const { const {
variant: packagesHeroVariant, variant: packagesHeroVariant,
trackClick: trackPackagesHeroClick, trackClick: trackPackagesHeroClick,
} = useCtaExperiment('packages_hero_cta'); } = useCtaExperiment('packages_hero_cta');
useEffect(() => {
if (flash?.error) {
toast.error(flash.error);
}
}, [flash?.error]);
useEffect(() => { useEffect(() => {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const packageId = urlParams.get('package_id'); const packageId = urlParams.get('package_id');

View File

@@ -66,6 +66,7 @@ return [
'limits_label' => 'Limits & Kapazitäten', 'limits_label' => 'Limits & Kapazitäten',
'paddle_not_configured' => 'Dieses Package ist noch nicht für den Paddle-Checkout konfiguriert. Bitte kontaktiere den Support.', 'paddle_not_configured' => 'Dieses Package ist noch nicht für den Paddle-Checkout konfiguriert. Bitte kontaktiere den Support.',
'paddle_checkout_failed' => 'Der Paddle-Checkout konnte nicht gestartet werden. Bitte versuche es später erneut.', 'paddle_checkout_failed' => 'Der Paddle-Checkout konnte nicht gestartet werden. Bitte versuche es später erneut.',
'package_not_found' => 'Dieses Package ist nicht verfügbar. Bitte wähle ein anderes aus.',
], ],
'nav' => [ 'nav' => [
'home' => 'Startseite', 'home' => 'Startseite',

View File

@@ -66,6 +66,7 @@ return [
'limits_label' => 'Limits & Capacity', 'limits_label' => 'Limits & Capacity',
'paddle_not_configured' => 'This package is not ready for Paddle checkout. Please contact support.', 'paddle_not_configured' => 'This package is not ready for Paddle checkout. Please contact support.',
'paddle_checkout_failed' => 'We could not start the Paddle checkout. Please try again later.', 'paddle_checkout_failed' => 'We could not start the Paddle checkout. Please try again later.',
'package_not_found' => 'This package is no longer available. Please choose another one.',
], ],
'nav' => [ 'nav' => [
'home' => 'Home', 'home' => 'Home',

View File

@@ -160,10 +160,18 @@ Route::prefix('{locale}')
->where('checkoutSlug', 'bestellen|checkout') ->where('checkoutSlug', 'bestellen|checkout')
->name('checkout.show'); ->name('checkout.show');
} else { } else {
Route::get('/{checkoutSlug}/{package}', function (string $locale, string $checkoutSlug, Package $package) { Route::get('/{checkoutSlug}/{package}', function (string $locale, string $checkoutSlug, string $package) {
$resolvedPackage = Package::query()->find($package);
if (! $resolvedPackage) {
return redirect()
->route('packages', ['locale' => $locale])
->with('error', __('marketing.packages.package_not_found'));
}
return redirect()->route('packages', [ return redirect()->route('packages', [
'locale' => app()->getLocale(), 'locale' => $locale,
'highlight' => $package->slug, 'highlight' => $resolvedPackage->slug,
]); ]);
}) })
->where('checkoutSlug', 'bestellen|checkout') ->where('checkoutSlug', 'bestellen|checkout')
@@ -325,16 +333,30 @@ Route::middleware('auth')->group(function () {
->name('tenant.events.photos.archive'); ->name('tenant.events.photos.archive');
}); });
Route::get('/purchase-wizard/{package}', function (Request $request, Package $package) use ($determinePreferredLocale) { Route::get('/purchase-wizard/{package}', function (Request $request, string $package) use ($determinePreferredLocale) {
$locale = $determinePreferredLocale($request); $locale = $determinePreferredLocale($request);
$resolvedPackage = Package::query()->find($package);
return redirect()->to(CheckoutRoutes::wizardUrl($package, $locale), 301); if (! $resolvedPackage) {
return redirect()
->route('packages', ['locale' => $locale])
->with('error', __('marketing.packages.package_not_found'));
}
return redirect()->to(CheckoutRoutes::wizardUrl($resolvedPackage, $locale), 301);
}); });
Route::get('/checkout/{package}', function (Request $request, Package $package) use ($determinePreferredLocale) { Route::get('/checkout/{package}', function (Request $request, string $package) use ($determinePreferredLocale) {
$locale = $determinePreferredLocale($request); $locale = $determinePreferredLocale($request);
$resolvedPackage = Package::query()->find($package);
return redirect()->to(CheckoutRoutes::wizardUrl($package, $locale), 301); if (! $resolvedPackage) {
return redirect()
->route('packages', ['locale' => $locale])
->with('error', __('marketing.packages.package_not_found'));
}
return redirect()->to(CheckoutRoutes::wizardUrl($resolvedPackage, $locale), 301);
}); });
Route::post('/checkout/login', [CheckoutController::class, 'login'])->name('checkout.login'); Route::post('/checkout/login', [CheckoutController::class, 'login'])->name('checkout.login');
Route::post('/checkout/register', [CheckoutController::class, 'register'])->name('checkout.register'); Route::post('/checkout/register', [CheckoutController::class, 'register'])->name('checkout.register');

View File

@@ -326,11 +326,13 @@ class CheckoutAuthTest extends TestCase
); );
} }
public function test_checkout_show_with_invalid_package_returns_404() public function test_checkout_show_with_invalid_package_redirects_to_packages()
{ {
$response = $this->get(CheckoutRoutes::wizardUrl(999, 'de')); $response = $this->get(CheckoutRoutes::wizardUrl(999, 'de'));
$response->assertStatus(404); $response
->assertRedirect(route('packages', ['locale' => 'de']))
->assertSessionHas('error', __('marketing.packages.package_not_found'));
} }
public function test_checkout_register_missing_required_fields() public function test_checkout_register_missing_required_fields()