尽管排除了 CSRF,但 Filament Stripe Webhook 仍因 419 错误(页面已过期)而失败

问题描述 投票:0回答:1

迁移到新服务器后,我的灯丝应用程序遇到了一个持续存在的问题。使用结帐会话处理 Stripe Webhook 时,尤其会出现此问题。尽管进行了多次配置并尝试解决它,但问题仍然没有解决。我的应用程序使用 StripeController.php 和自定义路由进行结账会话。用户可以购买商品,直接结帐,如果结帐成功,则 webhook 会返回。但每次将 Webhook (checkout.session.completed) 发送到我的应用程序时,都会导致 HTTP 419 错误,并显示消息“页面已过期”。我检查了服务器日志(访问和错误日志),并可以确认 Webhook 请求上的 419 错误。灯丝日志文件没有显示错误。

设置:

  • 服务器:带有 KeyHelp 服务器面板的 Apache 在我自己的 VPS、PHP 8.3 上的 Ubuntu 上运行
  • SSL:让我们加密通过 KeyHelp 管理的 SSL 证书。
  • CDN:Cloudflare 在完整 DNS 设置和启用代理的情况下使用。
  • 应用程序:在子域下运行的 Filament v3

我感谢社区的任何建议。我的想法已经用完了,我希望有人能够提供一些见解或提出不同的方法来解决这个问题。

应用程序/Http/Controllers/StripeController.php:

<?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']);
    }
}

应用程序/Http/中间件:

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

路线/web.php:

<?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');

.env:

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=

到目前为止我尝试过的:

  • 在VerifyCsrfToken.php中将/webhook路由添加到$ except数组中,以将其排除在CSRF验证之外
  • ENV配置文件:尝试修改SESSION_SAME_SITE和配置的SESSION_DRIVER,验证Stripe密钥
  • 通过设置 X-Forwarded-Proto 和 X-Forwarded,将 Apache 配置为正确处理来自 Cloudflare 的转发标头
  • 在 Cloudflare 中创建缓存规则,专门排除 /webhook 路径以确保没有缓存问题
  • 配置 Laravel 的会话和 cookie 设置以支持跨域请求(SameSite=None 并启用安全 cookie)。
  • 还尝试将存储和引导/缓存的文件夹权限设置为 777,以确保没有权限问题阻止 Webhooks,但没有成功
  • 通过 KeyHelp 暂时禁用防火墙以排除与防火墙相关的问题
  • 检查了 PHP 设置,例如内存限制、执行时间和帖子大小,以确保它们足以处理 Stripe 请求
  • 创建并配置了自定义 TrustProxies 中间件文件以正确处理代理标头,确保应用程序在 Cloudflare 后面时将请求识别为 HTTPS
php laravel
1个回答
0
投票

我自己找到并修复了错误。在最新的 Laravel 中,现在必须将其插入“bootstrap/app.php”文件中,而不是在VerifyCsrfToken.php 文件中设置 Csrf 异常。

© www.soinside.com 2019 - 2024. All rights reserved.