Why Choose Stripe for Laravel 11 Payment Integration?

             

Stripe stands out as one of the most powerful and developer-friendly payment gateways available today, making it the top choice for Laravel applications in 2026. What makes Stripe exceptional is its robust, well-documented API that supports seamless one-time payments, recurring subscriptions, global currencies, and advanced fraud prevention tools — all while maintaining top-tier security and PCI DSS compliance. Unlike traditional gateways, Stripe never lets sensitive card data touch your server thanks to client-side tokenization via Stripe.js or Elements, reducing your compliance burden significantly.

Why integrate Stripe specifically in Laravel? Laravel's elegant syntax, built-in validation, routing, and Eloquent ORM pair perfectly with Stripe's modern API structure. This combination allows developers to build fast, scalable payment systems with minimal boilerplate code. Whether you're creating an e-commerce platform, SaaS subscription service, or marketplace, Stripe + Laravel delivers reliable, secure transactions that boost user trust and conversion rates. In this comprehensive Laravel 11 Stripe tutorial, we'll walk you through exactly how to set up a secure payment flow using the official Stripe PHP SDK, handle card payments safely, store transaction details, and prepare for production best practices like Payment Intents for SCA/3DS compliance.

By the end, you'll have a fully functional, production-ready Stripe integration ready to process real payments.



Laravel 11 Stripe Payment Gateway Integration: Step-by-Step Tutorial

Table Of Content

1 Prerequisites

  • PHP ≥ 8.2
  • Composer
  • MySQL database
  • Stripe account (test keys from dashboard → Developers → API keys)

2 Introduction

This guide is an ideal starting point to understand how to integrate Stripe payment gateway in Laravel 11 and how you can integrate Stripe into an existing web application.

3 Create / Install a Laravel Project

3.1 Install Laravel Project

Ensure Composer is installed. Run:

composer create-project laravel/laravel laravel-stripe-demo

Then navigate to your project:

cd laravel-stripe-demo

3.2 Configure MySql Database

To store payment data, access the .env file to define database credentials:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_stripe_demo
DB_USERNAME=root
DB_PASSWORD=

4 Install Stripe PHP Library

Install the Stripe library:

composer require stripe/stripe-php

5 Configure Stripe Keys

Add Stripe API keys in the .env file, which you can find in your Stripe dashboard:

    # STRIPE
   STRIPE_KEY=pk_test_96xxxxxxxxxxxxxxxxxxxxxxxx
STRIPE_SECRET=sk_test_96xxxxxxxxxxxxxxxxxxxxxxxx
    
Then update config/services.php:
    
        'stripe' => [
    'key'    => env('STRIPE_KEY'),
    'secret' => env('STRIPE_SECRET'),
],
    

6 Create A Model and Migration

To create a migration for the payments table:

    php artisan make:migration create_payments_table

Add the following fields:
    
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    public function up(): void
    {
        Schema::create('payments', function (Blueprint $table) {
            $table->id();
            $table->string('payment_intent_id')->unique();
            $table->decimal('amount', 10, 2);
            $table->string('currency', 3)->default('usd');
            $table->string('status');
            $table->string('card_brand')->nullable();
            $table->string('card_last4')->nullable();
            $table->unsignedTinyInteger('card_exp_month')->nullable();
            $table->unsignedSmallInteger('card_exp_year')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('payments');
    }
};

Run the migration:

php artisan migrate

Create the Payment model in app/Models/Payment.php:
    
<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Payment extends Model
{
    use HasFactory;

    protected $fillable = [
        'payment_intent_id',
        'amount',
        'currency',
        'status',
        'card_brand',
        'card_last4',
        'card_exp_month',
        'card_exp_year',
    ];
}

7 Create Controller ( StripeController )

Create StripeController:

php artisan make:controller StripeController


<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Stripe\Stripe;
use Stripe\PaymentIntent;
use App\Models\Payment;

class StripeController extends Controller
{
    public function index()
    {
        return view('stripe.checkout');
    }

    public function createPaymentIntent(Request $request)
    {
        $request->validate([
            'amount' => 'required|numeric|min:1',
        ]);

        Stripe::setApiKey(config('services.stripe.secret'));

        try {
            $intent = PaymentIntent::create([
                'amount' => (int) round($request->amount * 100),
                'currency' => 'usd',
                'automatic_payment_methods' => ['enabled' => true],
                'description' => 'Laravel 11 Stripe test payment',
            ]);

            return response()->json(['clientSecret' => $intent->client_secret]);
        } catch (\Exception $e) {
            return response()->json(['error' => $e->getMessage()], 400);
        }
    }

    public function paymentSuccess(Request $request)
    {
        $request->validate([
            'payment_intent' => 'required|string',
        ]);

        Stripe::setApiKey(config('services.stripe.secret'));

        try {
            $intent = PaymentIntent::retrieve($request->payment_intent);

            if ($intent->status === 'succeeded') {
                $charge = $intent->charges->data[0] ?? null;
                $card = $charge?->payment_method_details?->card;

                Payment::create([
                    'payment_intent_id' => $intent->id,
                    'amount' => $intent->amount / 100,
                    'currency' => $intent->currency,
                    'status' => $intent->status,
                    'card_brand' => $card?->brand,
                    'card_last4' => $card?->last4,
                    'card_exp_month' => $card?->exp_month,
                    'card_exp_year' => $card?->exp_year,
                ]);

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

            return response()->json(['error' => 'Payment not succeeded'], 400);
        } catch (\Exception $e) {
            return response()->json(['error' => $e->getMessage()], 400);
        }
    }
}
?>

8 Define a Route

Add routes in routes/web.php:

use App\Http\Controllers\StripeController;

Route::get('/stripe', [StripeController::class, 'index'])->name('stripe');
Route::post('/stripe/create-payment-intent', [StripeController::class, 'createPaymentIntent']);
Route::post('/stripe/success', [StripeController::class, 'paymentSuccess']);

9 Create Blade File

Create the directory if needed: mkdir -p resources/views/stripe



   <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Laravel 11 Stripe Payment Integration</title>
    <script src="https://js.stripe.com/v3/"></script>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container py-5">
    <div class="row justify-content-center">
        <div class="col-lg-6">
            <div class="card shadow">
                <div class="card-body p-5">
                    <h2 class="card-title text-center mb-4">Pay with Stripe</h2>
                    <div id="messages" class="alert d-none"></div>

                    <form id="payment-form">
                        @csrf
                        <div class="mb-3">
                            <label for="amount" class="form-label">Amount (USD)</label>
                            <input type="number" class="form-control" id="amount" step="0.01" min="1" value="10.00" required>
                        </div>

                        <div class="mb-3">
                            <label class="form-label">Card Details</label>
                            <div id="card-element" class="form-control" style="padding: 12px;"></div>
                            <div id="card-errors" class="text-danger small mt-1"></div>
                        </div>

                        <button type="submit" class="btn btn-primary w-100" id="submit-btn">Pay Now</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    const stripe = Stripe('{{ config("services.stripe.key") }}');
    const elements = stripe.elements();
    const card = elements.create('card', { style: { base: { fontSize: '16px' } } });
    card.mount('#card-element');

    const form = document.getElementById('payment-form');
    const submitBtn = document.getElementById('submit-btn');
    const messagesDiv = document.getElementById('messages');
    const cardErrors = document.getElementById('card-errors');

    card.on('change', (e) => {
        cardErrors.textContent = e.error ? e.error.message : '';
    });

    form.addEventListener('submit', async (e) => {
        e.preventDefault();
        submitBtn.disabled = true;
        submitBtn.textContent = 'Processing...';
        messagesDiv.classList.add('d-none');
        cardErrors.textContent = '';

        const amount = parseFloat(document.getElementById('amount').value);

        try {
            const intentRes = await fetch('/stripe/create-payment-intent', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRF-TOKEN': document.querySelector('[name="_token"]').value
                },
                body: JSON.stringify({ amount })
            });
            const intentData = await intentRes.json();
            if (intentData.error) throw new Error(intentData.error);

            const result = await stripe.confirmCardPayment(intentData.clientSecret, {
                payment_method: {
                    card: card,
                    billing_details: { name: 'Test Customer' }
                }
            });

            if (result.error) throw new Error(result.error.message);

            // Verify & store on server
            const successRes = await fetch('/stripe/success', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRF-TOKEN': document.querySelector('[name="_token"]').value
                },
                body: JSON.stringify({ payment_intent: result.paymentIntent.id })
            });
            const successData = await successRes.json();

            if (successData.status === 'success') {
                showMessage('✅ Payment successful! Record saved.', 'success');
                form.reset();
                card.clear();
            } else {
                throw new Error('Server verification failed');
            }
        } catch (err) {
            showMessage('❌ ' + err.message, 'danger');
        } finally {
            submitBtn.disabled = false;
            submitBtn.textContent = 'Pay Now';
        }
    });

    function showMessage(msg, type) {
        messagesDiv.textContent = msg;
        messagesDiv.className = `alert alert-${type}`;
        messagesDiv.classList.remove('d-none');
    }
</script>
</body>
</html>

10 Folder Structure

11 Run Laravel Server to Test the App

Start your server with:

php artisan serve

Visit: http://127.0.0.1:8000/stripe

Testing

  • Use Stripe test card: 4242 4242 4242 4242, any future expiry, any CVC
  • For 3DS test: 4000 0000 0000 3220
  • Check DB table payments after successful payment

12 Conclusion

You've now integrated a fast, secure Stripe payment gateway in Laravel 11. This setup handles basic one-time payments reliably. For production, upgrade to Payment Intents/Checkout Sessions and add webhooks.

Reference URL

Revathi M - PHP and CodeIgniter Developer

Written by Revathi M

PHP Developer & Technical Writer · 10+ years building web applications with CodeIgniter and Laravel

Revathi specializes in PHP backend development, authentication systems, and REST API design. She writes practical, production-tested tutorials at Get Sample Code to help developers build secure applications faster.

Frequently Asked Questions

You need PHP 8.2 or higher, Composer, a fresh Laravel 11 installation, and a configured MySQL database in .env. A Stripe account is required to obtain publishable and secret keys.

Run the command: composer require stripe/stripe-php. This installs the official Stripe PHP SDK.

Add STRIPE_KEY=your_publishable_key and STRIPE_SECRET=your_secret_key to your .env file. Access them in code via env('STRIPE_SECRET').

The client-side uses Stripe.js to create a secure token from card details (no raw card data touches your server). The token is sent to the controller, which uses Stripe\\Charge::create() to process the payment.

Create a 'payments' migration with fields like charge_id, transaction_id, amount, card_last_four, etc. Use a Payment model with fillable attributes to save successful charge details.

Common issues: Invalid test keys, incorrect amount (must be in cents), missing token, or using live keys with test cards. Check Stripe dashboard logs and browser console for errors.

Yes, Stripe.js tokenization ensures raw card data never hits your server (PCI compliance). Use HTTPS in production and keep secret keys server-side only.

No, this focuses on one-time charges via Charge API. For subscriptions, consider Laravel Cashier or Stripe Billing with Payment Intents.

Yes, the Charge API is legacy. For better security and features (e.g., 3D Secure), migrate to Payment Intents or use Stripe Checkout sessions.