Come integrare i pagamenti Stripe in Laravel: guida passo passo
Accettare pagamenti in Laravel con Stripe, passo passo: dalle chiavi API alla sessione di Checkout fino ai webhook verificati. Codice aggiornato a Laravel 12 e 13.
Come integrare i pagamenti Stripe in Laravel: guida passo passo
Accettare pagamenti online è uno dei requisiti più comuni in un progetto Laravel, e Stripe è oggi lo standard di fatto: documentazione eccellente, SDK ufficiale e un sistema di webhook affidabile per confermare le transazioni. In questa guida vediamo passo passo come integrare un pagamento in un'app Laravel usando Stripe Checkout, la pagina di pagamento ospitata da Stripe.
Perché Checkout e non un form di pagamento fatto in casa? Perché i dati della carta non transitano mai dal tuo server: li gestisce direttamente Stripe sulla sua pagina sicura. Questo riduce drasticamente i requisiti di sicurezza (PCI) e ti fa risparmiare un sacco di lavoro.
Prerequisiti: un'app Laravel 12 o 13 funzionante, PHP 8.2+ (8.3+ su Laravel 13), Composer e un account Stripe (anche gratuito).
1. Recupera le chiavi API in modalità test
Accedi alla dashboard di Stripe, assicurati che l'interruttore in alto sia su Modalità test, poi vai in Sviluppatori → Chiavi API. Ti servono due valori:
Publishable key (
pk_test_...) — pubblica, usata lato client.Secret key (
sk_test_...) — segreta, usata solo lato server. Non condividerla mai.
In modalità test puoi simulare pagamenti senza muovere denaro reale.
2. Installa l'SDK ufficiale di Stripe
Dalla root del progetto:
composer require stripe/stripe-php
3. Configura le chiavi nell'applicazione
Aggiungi le chiavi al file .env (mai hardcodarle nel codice):
STRIPE_KEY=pk_test_xxxxxxxxxxxxxxxxxxxxxxxx
STRIPE_SECRET=sk_test_xxxxxxxxxxxxxxxxxxxxxxxx
STRIPE_WEBHOOK_SECRET=Poi esponile in config/services.php, così le richiami in modo pulito con config():
'stripe' => [
'key' => env('STRIPE_KEY'),
'secret' => env('STRIPE_SECRET'),
'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
],
4. Crea la sessione di Checkout
Il cuore dell'integrazione: un controller che crea una sessione di pagamento e reindirizza l'utente alla pagina di Stripe.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Stripe\Stripe;
use Stripe\Checkout\Session;
class CheckoutController extends Controller
{
public function create(Request $request)
{
Stripe::setApiKey(config('services.stripe.secret'));
$session = Session::create([
'mode' => 'payment',
'line_items' => [[
'price_data' => [
'currency' => 'eur',
'product_data' => ['name' => 'Piano Pro'],
'unit_amount' => 4900, // 49,00 € — sempre in centesimi
],
'quantity' => 1,
]],
'success_url' => route('checkout.success') . '?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => route('checkout.cancel'),
]);
return redirect($session->url, 303);
}
}Due dettagli importanti: gli importi vanno sempre espressi in centesimi (4900 = 49,00 €) e devono essere definiti lato server, mai presi da un campo inviato dal browser, altrimenti l'utente potrebbe manometterli.
5. Definisci le rotte e il pulsante di pagamento
In routes/web.php:
use App\Http\Controllers\CheckoutController;
use App\Http\Controllers\StripeWebhookController;
Route::post('/checkout', [CheckoutController::class, 'create'])->name('checkout.create');
Route::view('/checkout/success', 'checkout.success')->name('checkout.success');
Route::view('/checkout/cancel', 'checkout.cancel')->name('checkout.cancel');
Route::post('/stripe/webhook', [StripeWebhookController::class, 'handle'])->name('stripe.webhook');Nella tua vista Blade basta un piccolo form che punta alla rotta di checkout:
<form method="POST" action="{{ route('checkout.create') }}">
@csrf
<button type="submit">Acquista — 49 €</button>
</form>Le pagine checkout.success e checkout.cancel sono due semplici view che mostrano l'esito all'utente.
6. Conferma il pagamento con i webhook
Qui c'è l'errore più comune: non considerare un pagamento concluso solo perché l'utente è atterrato sulla pagina di successo. L'utente potrebbe chiudere il browser prima del redirect, o la rete potrebbe cadere. L'unica fonte di verità affidabile è il webhook che Stripe invia al tuo server quando il pagamento va a buon fine.
Crea il controller che riceve e verifica gli eventi:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Stripe\Webhook;
use Stripe\Exception\SignatureVerificationException;
class StripeWebhookController extends Controller
{
public function handle(Request $request)
{
$payload = $request->getContent();
$signature = $request->header('Stripe-Signature');
$secret = config('services.stripe.webhook_secret');
try {
$event = Webhook::constructEvent($payload, $signature, $secret);
} catch (\UnexpectedValueException $e) {
return response('Payload non valido', 400);
} catch (SignatureVerificationException $e) {
return response('Firma non valida', 400);
}
if ($event->type === 'checkout.session.completed') {
$session = $event->data->object;
// Qui completi l'ordine: lo segni come pagato,
// invii l'email di conferma, sblocchi l'accesso, ecc.
// Dati utili: $session->id, $session->customer_email, $session->amount_total
}
return response('OK', 200);
}
}La verifica della firma con constructEvent è fondamentale: garantisce che la richiesta arrivi davvero da Stripe e non da un malintenzionato che finge un pagamento.
Escludi il webhook dalla protezione CSRF
Stripe non può inviare un token CSRF, quindi la rotta del webhook va esclusa dal relativo controllo, in bootstrap/app.php. La sintassi cambia leggermente in base alla versione di Laravel.
Laravel 13 (il middleware è stato rinominato in PreventRequestForgery):
->withMiddleware(function (Middleware $middleware): void {
$middleware->preventRequestForgery(except: [
'stripe/*',
]);
})Laravel 12 e precedenti con il nuovo skeleton:
->withMiddleware(function (Middleware $middleware): void {
$middleware->validateCsrfTokens(except: [
'stripe/*',
]);
})Infine registra l'endpoint nella dashboard Stripe (Sviluppatori → Webhook), indicando l'URL https://tuodominio.it/stripe/webhook e selezionando l'evento checkout.session.completed. Stripe ti fornirà un signing secret (whsec_...) da incollare nella variabile STRIPE_WEBHOOK_SECRET.
7. Testa tutto in locale con la Stripe CLI
In sviluppo il tuo localhost non è raggiungibile da Stripe. La Stripe CLI risolve il problema inoltrando gli eventi alla tua macchina:
stripe login
stripe listen --forward-to localhost:8000/stripe/webhookIl comando listen ti stampa a video un signing secret temporaneo: usalo come STRIPE_WEBHOOK_SECRET durante i test. Per simulare un pagamento usa la carta di test 4242 4242 4242 4242, con una data di scadenza futura qualsiasi e un CVC a piacere.
8. Vai in produzione
Quando sei pronto al go-live, ricordati di: passare alle chiavi live (pk_live_... / sk_live_...), servire l'app rigorosamente in HTTPS, ricreare l'endpoint webhook in modalità live (avrà un nuovo signing secret) e fare un primo pagamento reale di importo minimo per verificare l'intero flusso end-to-end.
Sicurezza e best practice
Qualche regola da non dimenticare: la secret key resta solo lato server, mai nel codice JavaScript; verifica sempre la firma dei webhook; calcola gli importi sul server e mai fidandoti del client; gestisci la possibilità che Stripe invii lo stesso evento più volte (rendi l'operazione idempotente controllando l'ID della sessione già processata); e per operazioni pesanti dopo il pagamento — invio email, generazione fatture — usa le code di Laravel per rispondere subito a Stripe con un 200.
E per gli abbonamenti?
Se il tuo progetto prevede pagamenti ricorrenti, conviene usare Laravel Cashier, il pacchetto ufficiale che gestisce abbonamenti, periodi di prova, cambi di piano e fatturazione su Stripe con poche righe di codice. Per i pagamenti una tantum, invece, l'approccio con Checkout visto qui è perfetto.
In sintesi
Con Stripe Checkout integri i pagamenti in Laravel in modo sicuro e veloce: chiavi API, una sessione di Checkout, un webhook verificato per confermare le transazioni. La parte più importante non è far comparire il pulsante "Paga", ma gestire bene il webhook: è lì che il pagamento diventa davvero affidabile.
Hai un progetto che deve gestire pagamenti, abbonamenti o un checkout su misura? Raccontaci di cosa hai bisogno: ti aiutiamo a integrarlo nel modo giusto.