Keyhelp Server Fehler 419 Page Expired in Filament App mit Stripe Webhook  [SOLVED]

For topics beyond KeyHelp. / Für Themen jenseits von KeyHelp.
Post Reply
weisss
Posts: 2
Joined: Sat 21. Sep 2024, 20:54

Keyhelp Server Fehler 419 Page Expired in Filament App mit Stripe Webhook

Post by weisss »

Server-Betriebssystem + Version
Ubuntu 22.04.5 LTS (64-bit)
Kernel 5.15.0-25-generic
Webserver Apache 2.4.52

Eingesetzte Server-Virtualisierung-Technologie
KVM

KeyHelp-Version + Build-Nummer
24.2 (Build 3326)


Problembeschreibung / Fehlermeldungen
Ich habe leider ein Problem mit meiner Filament-App nach der Migration auf einen neuen Server. Das Problem erscheint bei der Nutzung des Stripe-Webhooks unter Verwendung einer Checkout-Session auf. Trotz verschiedener Konfigurationen und Versuche, es zu lösen, bleibt das Problem bestehen. Meine Anwendung verwendet eine Checkout-Sitzung mit einem StripeController.php und einer benutzerdefinierten Route. Benutzer können einen Artikel kaufen, diesen direkt bezahlen und dann sendet der Webhook zurück, ob der Checkout erfolgreich war. Aber jedes Mal, wenn der Webhook (checkout.session.completed) an meine Anwendung gesendet wird, resultiert dies in einem HTTP 419 Fehler mit der Meldung „Page expired“. Ich habe die Serverprotokolle (Zugriffs- und Fehlerprotokolle) überprüft und kann 419-Fehler bei Webhook-Anfragen bestätigen. Die Filament-Protokolldatei zeigt keinen Fehler an.
Da ich den Ordner der funktionierenden Filament App auf meinen neuen Server kopiert habe und dies ebenfalls nicht geht, muss der Fehler an Keyhelp liegen. Ich finde jedoch nicht die Ursache!

Setup:
  • Server: Apache mit KeyHelp-Serverpanel auf Ubuntu auf meinem eigenen VPS, PHP 8.3
  • SSL: Let’s Encrypt SSL-Zertifikate, verwaltet über KeyHelp.
  • CDN: Cloudflare mit vollständiger DNS-Einrichtung und aktiviertem Proxy.
  • Anwendung: Filament v3 läuft unter Subdomain

Erwartetes Ergebnis
Erfolgreiche POST response der Webhook

Tatsächliches Ergebnis
419 Page Expired

Schritte zur Reproduktion
Hier der Code meiner Dateien:

.env:

Code: Select all

APP_ENV=production
APP_KEY=xxx
APP_DEBUG=true
APP_TIMEZONE=Europe/Berlin
APP_URL=https://app.domain.de

APP_LOCALE=de
APP_FALLBACK_LOCALE=de
APP_FAKER_LOCALE=de_DE

APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database

STRIPE_SECRET=xxx
STRIPE_WEBHOOK_SECRET=xxx

BCRYPT_ROUNDS=12

LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=

SESSION_DRIVER=cookie
SESSION_LIFETIME=120
SESSION_DOMAIN=.domain.de
SESSION_SAME_SITE=None
SESSION_SECURE_COOKIE=true
# SESSION_PATH=/

BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync

CACHE_STORE=database
CACHE_PREFIX=

routes/web.php:

Code: Select all

<?php

use App\Http\Controllers\InvoiceController;
use App\Http\Controllers\ReportController;
use App\Http\Controllers\StripeController;
use Illuminate\Support\Facades\Route;

Route::get('/invoices/{id}/download', [InvoiceController::class, 'downloadPdf'])->name('invoices.download');
Route::get('/reports/{id}/download', [ReportController::class, 'downloadReportPdf'])->name('reports.download');

Route::get('/create-checkout-session', [StripeController::class, 'createCheckoutSession'])->name('create.checkout.session');
Route::get('/payment-success', [StripeController::class, 'success'])->name('payment.success');
Route::get('/payment-cancel', [StripeController::class, 'cancel'])->name('payment.cancel');

// Define the POST route for the webhook
Route::post('/webhook', [StripeController::class, 'handleWebhook'])->name('webhook.handle');
app/Http/Middleware.php:

Code: Select all

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        '/webhook', 
        'webhook', 
        'webhook/*',
    ];
}
app/Http/Controllers/StripeController.php:

Code: Select all

<?php

namespace App\Http\Controllers;

use App\Models\Ecg;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Stripe\Checkout\Session;
use Stripe\Stripe;
use Stripe\Webhook;

class StripeController extends Controller
{
    public function createCheckoutSession(Request $request)
    {
        // Setze den API-Schlüssel über die Config
        Stripe::setApiKey(config('services.stripe.secret'));

        $amount = $request->query('amount');
        $ecgId = $request->query('ecg_id'); // Get the ECG ID from the query parameters

        $session = Session::create([
            'payment_method_types' => ['card'],
            'line_items' => [[
                'price_data' => [
                    'currency' => 'eur',
                    'product_data' => [
                        'name' => 'EKG-Befundung',
                    ],
                    'unit_amount' => $amount * 100, // amount in cents
                ],
                'quantity' => 1,
            ]],
            'mode' => 'payment',
            'success_url' => route('payment.success', ['ecg_id' => $ecgId]),
            'cancel_url' => route('payment.cancel'),
            'metadata' => [
                'ecg_id' => $ecgId, // Store the ECG ID in metadata
            ],
        ]);

        return redirect()->away($session->url);
    }

    public function success(Request $request)
    {
        $ecgId = $request->query('ecg_id');
        return view('payment.success', ['ecg_id' => $ecgId]);
    }

    public function cancel()
    {
        return view('payment.cancel');
    }

    public function handleWebhook(Request $request)
    {
        Stripe::setApiKey(config('services.stripe.secret'));
        $endpointSecret = config('services.stripe.webhook_secret');
        
        $payload = $request->getContent();
        $sigHeader = $request->header('Stripe-Signature');

        try {
            $event = Webhook::constructEvent($payload, $sigHeader, $endpointSecret);
        } catch (\UnexpectedValueException $e) {
            return response()->json(['error' => 'Invalid payload'], 400);
        } catch (\Stripe\Exception\SignatureVerificationException $e) {
            return response()->json(['error' => 'Invalid signature'], 400);
        }

        switch ($event->type) {
            case 'checkout.session.completed':
                $session = $event->data->object;
                $ecgId = $session->metadata->ecg_id;

                $ecg = Ecg::find($ecgId);
                if ($ecg) {
                    $ecg->isPaid = true; // Update the isPaid property
                    $ecg->save();
                }
                break;
            default:
                Log::warning('Unhandled event type: ' . $event->type);
        }

        return response()->json(['status' => 'success']);
    }
}


Zusätzliche Informationen
Was ich bisher versucht habe:
  • Die /webhook-Route zum $except-Array in VerifyCsrfToken.php hinzugefügt, um sie von der CSRF-Überprüfung auszuschließen.
  • In der .env versucht, SESSION_SAME_SITE zu ändern und SESSION_DRIVER zu konfigurieren, Stripe-Secret-Keys überprüft.
  • Die Filament App vom funktionierenden Server auf meinen neuen Server kopiert - geht auch nicht.
  • Apache so konfiguriert, dass es die weitergeleiteten Header von Cloudflare korrekt verarbeitet, indem X-Forwarded-Proto und X-Forwarded gesetzt wurden.
  • Cache-Regeln in Cloudflare erstellt, die den Pfad /webhook speziell ausschließen, um sicherzustellen, dass keine Caching-Probleme auftreten.
  • Sitzungs- und Cookie-Einstellungen von Laravel so konfiguriert, dass sie Cross-Origin-Anfragen unterstützen (SameSite=None und sichere Cookies aktiviert).
  • Ordnerberechtigungen für storage und bootstrap/cache auf 777 gesetzt, um sicherzustellen, dass keine Berechtigungsprobleme die Webhooks blockieren, jedoch ohne Erfolg.
  • Firewall vorübergehend über KeyHelp deaktiviert, um firewallbezogene Probleme auszuschließen.
  • PHP-Einstellungen wie Speicherkapazität, Ausführungszeiten und Post-Größen überprüft, um sicherzustellen, dass sie für die Verarbeitung von Stripe-Anfragen ausreichend sind.
  • Benutzerdefinierte TrustProxies-Middleware-Datei erstellt und konfiguriert, um Proxy-Header korrekt zu verarbeiten und sicherzustellen, dass die App Anfragen als HTTPS erkennt, wenn sie hinter Cloudflare steht.
Mir gehen leider mittlerweile die Ideen aus. Ich hoffe, jemand kann mir diesbezüglich helfen.
weisss
Posts: 2
Joined: Sat 21. Sep 2024, 20:54

Re: Keyhelp Server Fehler 419 Page Expired in Filament App mit Stripe Webhook  [SOLVED]

Post by weisss »

Ich konnte den Fehler selbstständig finden und beheben. Statt die Csrf Ausnahme in der VerifyCsrfToken.php Datei vorzunehmen, muss diese im neuesten Laravel jetzt in der 'bootstrap/app.php' Datei eingefügt werden.
Post Reply