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;
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');
$googleError = session()->pull('checkout_google_error');
$googleProfile = session()->pull('checkout_google_profile');
@@ -35,7 +43,7 @@ class CheckoutController extends Controller
->all();
return Inertia::render('marketing/CheckoutWizardPage', [
'package' => $this->presentPackage($package),
'package' => $this->presentPackage($resolvedPackage),
'packageOptions' => $packageOptions,
'privacyHtml' => view('legal.datenschutz-partial')->render(),
'auth' => [

View File

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

View File

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

View File

@@ -66,6 +66,7 @@ return [
'limits_label' => 'Limits & Kapazitäten',
'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.',
'package_not_found' => 'Dieses Package ist nicht verfügbar. Bitte wähle ein anderes aus.',
],
'nav' => [
'home' => 'Startseite',

View File

@@ -66,6 +66,7 @@ return [
'limits_label' => 'Limits & Capacity',
'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.',
'package_not_found' => 'This package is no longer available. Please choose another one.',
],
'nav' => [
'home' => 'Home',

View File

@@ -160,10 +160,18 @@ Route::prefix('{locale}')
->where('checkoutSlug', 'bestellen|checkout')
->name('checkout.show');
} 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', [
'locale' => app()->getLocale(),
'highlight' => $package->slug,
'locale' => $locale,
'highlight' => $resolvedPackage->slug,
]);
})
->where('checkoutSlug', 'bestellen|checkout')
@@ -325,16 +333,30 @@ Route::middleware('auth')->group(function () {
->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);
$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);
$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/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->assertStatus(404);
$response
->assertRedirect(route('packages', ['locale' => 'de']))
->assertSessionHas('error', __('marketing.packages.package_not_found'));
}
public function test_checkout_register_missing_required_fields()