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');
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/*',
];
}
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.