syedmahamudul/laravel-stripe

Complete Stripe Payment Gateway Integration for ALL Laravel & PHP Versions

Maintainers

Package info

github.com/syedmahamudul/laravel-stripe

pkg:composer/syedmahamudul/laravel-stripe

Transparency log

Statistics

Installs: 4

Dependents: 0

Suggesters: 0

Stars: 3

Open Issues: 0

v1.0.2 2026-08-10 17:55 UTC

This package is auto-updated.

Last update: 2026-08-12 20:14:43 UTC


README

Latest Stable Version Total Downloads Latest Unstable Version License GitHub stars Monthly Downloads Daily Downloads composer.lock PHP Version

This package is built for Stripe online payment gateway. It supports Laravel 5.6+, 6.x, 7.x, 8.x, 9.x, 10.x, and 11.x, 12.x, 13.x and works with PHP 7.4 to 8.6+.

🚀 Features

  • Easy Installation - One command installation
  • Fluent API - Chainable methods for building payment requests
  • Automatic Validation - Built-in payment validation
  • IPN Support - Instant Payment Notification handling
  • Refund Functionality - Process refunds easily
  • Transaction Query - Check transaction status
  • Sandbox/Live Mode - Easy switching between test and production
  • All Laravel Versions - Works with Laravel 5.6 to 13.x
  • PHP 7.4 to 8.6+ - Compatible with all PHP versions
  • No Version Conflicts - Works with any Laravel project
  • Comprehensive Error Handling - Detailed error messages
  • Event-driven Architecture - Events for payment statuses
  • Comprehensive Logging - Debug and track payments
  • EMI Support - Easy EMI payment integration
  • Card BIN Restriction - Restrict payments to specific card BINs
  • Custom Callback URLs - Customize success/failure URLs
  • Multiple Products - Support for multiple products in one transaction
  • Checkout Integration - AJAX/JSON checkout mode support

📋 Table of Contents

Installation

Requirements

  • PHP 7.4 or higher
  • Laravel 5.6 or higher
  • Stripe Merchant Account (Sandbox or Live)

Install via Composer

composer require syedmahamudul/laravel-stripe

Publish Configuration

After installing the package, publish the configuration file.

Option 1: Automatic Installation (Recommended)

Run the installer command:

php artisan stripe:install

This command will:

  • Publish the configuration file
  • Create the required configuration
  • Display the next setup instructions

Option 2: Manual Configuration

If you don't want to use the automatic installer, you can publish the configuration file manually.

Run the following command:

php artisan vendor:publish --tag=stripe-config

After running the command, Laravel will publish the package configuration file to your application.

You should see output similar to:

INFO  Publishing [stripe-config] assets.

Copied File:
config/stripe.php

The published configuration file will be located at:

config/stripe.php

You can now customize the package settings and configure your Stripe credentials using your .env file.

Note: After publishing the configuration file, clear Laravel's configuration cache to ensure the new settings are loaded.

php artisan config:clear
php artisan cache:clear
php artisan optimize:clear

Verify the Configuration

After publishing, ensure the configuration file exists:

config/
└── stripe.php

Setup Environment

Update your .env file with Stripe credentials:

Sandbox/Test Mode:

Update your .env file with your sandbox/live credentials:

STRIPE_API_KEY=Stripe_API_Key // Live or Sandbox API Key
STRIPE_API_SECRET=Stripe_API_Secret  // Live or Sandbox API Secret
STRIPE_WEBHOOK_SECRET=stripe_webhook_url
STRIPE_CURRENCY=usd
STRIPE_SUCCESS_URL=/payment/success
STRIPE_CANCEL_URL=/payment/cancel
STRIPE_WEBHOOK_URL=/stripe/webhook

Important View File

You must create a view file for the folder payment and view file name form.blade.php, failed.blade.php, success.blade.php, Or use your custom view file name

Create View Controller

   /**
     * Show payment form
     */
    public function index()
    {
        return view('payment.index');
    }

Create Routes

Create routes for Stripe callbacks in routes/web.php:

<?php

use App\Http\Controllers\PaymentController;
use App\Http\Controllers\StripeWebhookController;
use App\Http\Controllers\SubscriptionController;
use Illuminate\Support\Facades\Route;

// Payment Routes
Route::get('/', [PaymentController::class, 'index'])->name('payment.index');
Route::post('/payment/process', [PaymentController::class, 'process'])->name('payment.process');
Route::get('/payment/success', [PaymentController::class, 'success'])->name('payment.success');
Route::get('/payment/success/{payment_id}', [PaymentController::class, 'showSuccess'])->name('payment.success.show');
Route::get('/payment/cancel', [PaymentController::class, 'cancel'])->name('payment.cancel');
Route::get('/payment/failed', [PaymentController::class, 'failed'])->name('payment.failed');
Route::post('/payment/refund/{paymentIntentId}', [PaymentController::class, 'refund'])->name('payment.refund');
Route::get('/payment/status/{paymentIntentId}', [PaymentController::class, 'status'])->name('payment.status');

// Subscription Routes
Route::get('/subscription/plans', [SubscriptionController::class, 'plans'])->name('subscription.plans');
Route::post('/subscription/subscribe', [SubscriptionController::class, 'subscribe'])->name('subscription.subscribe');
Route::delete('/subscription/cancel/{subscriptionId}', [SubscriptionController::class, 'cancel'])->name('subscription.cancel');
Route::post('/subscription/resume/{subscriptionId}', [SubscriptionController::class, 'resume'])->name('subscription.resume');
Route::put('/subscription/update/{subscriptionId}', [SubscriptionController::class, 'update'])->name('subscription.update');
Route::get('/subscription/my', [SubscriptionController::class, 'mySubscriptions'])->name('subscription.my');
Route::get('/subscription/success', [SubscriptionController::class, 'success'])->name('subscription.success');
Route::get('/subscription/failed', [SubscriptionController::class, 'failed'])->name('subscription.failed');

// Webhook Route (No CSRF protection)
Route::post('/stripe/webhook', [StripeWebhookController::class, 'handle'])
    ->name('stripe.webhook')
    ->withoutMiddleware(['web', 'csrf']);

Create the Payment Form

Create a payment form where customers can enter their order and billing information before initiating the payment.

Create a Blade view at:

resources/views/payment/index.blade.php
<!DOCTYPE html>
<html>
<head>
    <title>Make Payment</title>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container mt-5">
        <div class="row justify-content-center">
            <div class="col-md-6">
                <div class="card">
                    <div class="card-header">
                        <h4>Make Payment</h4>
                    </div>
                    <div class="card-body">
                        @if(session('error'))
                            <div class="alert alert-danger">{{ session('error') }}</div>
                        @endif

                        <form action="{{ route('payment.process') }}" method="POST" id="payment-form">
                            @csrf
                            <div class="mb-3">
                                <label for="amount" class="form-label">Amount (USD)</label>
                                <input type="number" class="form-control @error('amount') is-invalid @enderror" 
                                       id="amount" name="amount" step="0.01" min="0.5" required>
                                @error('amount')
                                    <div class="invalid-feedback">{{ $message }}</div>
                                @enderror
                            </div>

                            <div class="mb-3">
                                <label for="name" class="form-label">Full Name</label>
                                <input type="text" class="form-control @error('name') is-invalid @enderror" 
                                       id="name" name="name" required>
                                @error('name')
                                    <div class="invalid-feedback">{{ $message }}</div>
                                @enderror
                            </div>

                            <div class="mb-3">
                                <label for="email" class="form-label">Email</label>
                                <input type="email" class="form-control @error('email') is-invalid @enderror" 
                                       id="email" name="email" required>
                                @error('email')
                                    <div class="invalid-feedback">{{ $message }}</div>
                                @enderror
                            </div>

                            <div class="mb-3">
                                <label for="product_name" class="form-label">Product Name</label>
                                <input type="text" class="form-control" id="product_name" name="product_name">
                            </div>

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

Create Confirm Payment Form

Create a payment form where customers can enter their order and billing information before initiating the payment.

Create a Blade view at:

resources/views/payment/confirm.blade.php
<!DOCTYPE html>
<html>
<head>
    <title>Confirm Payment</title>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
    <script src="https://js.stripe.com/v3/"></script>
    <style>
        .payment-card {
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.1);
        }
        .payment-header {
            background: linear-gradient(135deg, #0d6efd, #0a58ca);
            color: white;
            padding: 20px;
            border-radius: 15px 15px 0 0;
        }
        .stripe-element {
            background: white;
            border: 1px solid #dee2e6;
            border-radius: 8px;
            padding: 12px;
        }
        #payment-element {
            min-height: 100px;
        }
        .btn-pay {
            background: linear-gradient(135deg, #28a745, #20c997);
            border: none;
            padding: 12px;
            font-weight: 600;
            transition: all 0.3s ease;
        }
        .btn-pay:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(40, 167, 69, 0.3);
        }
        .btn-pay:disabled {
            opacity: 0.7;
            transform: none;
        }
    </style>
</head>
<body>
    <div class="container mt-5">
        <div class="row justify-content-center">
            <div class="col-md-6">
                <div class="card payment-card">
                    <div class="payment-header text-center">
                        <h4><i class="fas fa-lock me-2"></i> Complete Payment</h4>
                    </div>
                    <div class="card-body p-4">
                        <div class="alert alert-info">
                            <div class="d-flex justify-content-between">
                                <span><strong>Amount:</strong></span>
                                <span>
                                    @if(function_exists('format_currency'))
                                        {{ format_currency($amount, $currency) }}
                                    @else
                                        {{ number_format($amount, 2) }} {{ strtoupper($currency) }}
                                    @endif
                                </span>
                            </div>
                            <div class="d-flex justify-content-between mt-1">
                                <span><strong>Currency:</strong></span>
                                <span>{{ strtoupper($currency) }}</span>
                            </div>
                        </div>
                        
                        <div class="mb-4">
                            <label class="form-label fw-bold">Card Details</label>
                            <div class="stripe-element">
                                <div id="payment-element"></div>
                            </div>
                        </div>

                        <button id="submit-button" class="btn btn-success w-100 btn-pay">
                            <i class="fas fa-lock me-2"></i>Pay Now
                        </button>
                        <div id="error-message" class="mt-3 text-danger"></div>
                        
                        <div class="mt-3 text-center">
                            <a href="{{ route('payment.cancel') }}" class="text-decoration-none text-muted">
                                <i class="fas fa-times me-1"></i> Cancel
                            </a>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const stripe = Stripe('{{ config('stripe.api_key') }}');
            const clientSecret = '{{ $clientSecret }}';

            const elements = stripe.elements({
                clientSecret: clientSecret,
                appearance: {
                    theme: 'stripe',
                    variables: {
                        colorPrimary: '#0d6efd',
                        colorBackground: '#ffffff',
                        colorText: '#212529',
                        fontFamily: 'system-ui, -apple-system, sans-serif',
                        borderRadius: '8px',
                    },
                },
            });

            const paymentElement = elements.create('payment');
            paymentElement.mount('#payment-element');

            const submitButton = document.getElementById('submit-button');
            const errorElement = document.getElementById('error-message');

            submitButton.addEventListener('click', async () => {
                submitButton.disabled = true;
                submitButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Processing...';
                errorElement.textContent = '';

                try {
                    const { error } = await stripe.confirmPayment({
                        elements,
                        confirmParams: {
                            return_url: '{{ route('payment.success') }}',
                        },
                    });

                    if (error) {
                        errorElement.textContent = error.message;
                        submitButton.disabled = false;
                        submitButton.innerHTML = '<i class="fas fa-lock me-2"></i>Pay Now';
                    }
                    // If no error, Stripe will redirect to return_url
                    
                } catch (err) {
                    errorElement.textContent = 'An unexpected error occurred. Please try again.';
                    submitButton.disabled = false;
                    submitButton.innerHTML = '<i class="fas fa-lock me-2"></i>Pay Now';
                }
            });
        });
    </script>
</body>
</html>

Process Payment

Create a controller to handle Stripe payment requests and callback responses.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Syedmahamudul\LaravelStripe\Services\PaymentService;
use Syedmahamudul\LaravelStripe\Exceptions\PaymentFailedException;
use Syedmahamudul\LaravelStripe\Models\Payment;
use Illuminate\Support\Facades\Log;

class PaymentController extends Controller
{


     protected $paymentService;

    public function __construct(PaymentService $paymentService)
    {
        $this->paymentService = $paymentService;
    }


     /**
     * Show payment form
     */
    public function index()
    {
        return view('payment.index');
    }

    public function process(Request $request)
    {
        $request->validate([
            'amount' => 'required|numeric|min:0.5',
            'name' => 'required|string|max:255',
            'email' => 'required|email',
        ]);

        try {
            $paymentData = [
                'amount' => $request->amount,
                'email' => $request->email,
                'name' => $request->name,
                'metadata' => [
                    'product_name' => $request->product_name ?? 'Payment',
                    'order_id' => $request->order_id ?? 'ORD-' . time(),
                ],
            ];

            $payment = $this->paymentService->processPayment($paymentData);

            return view('payment.confirm', [
                'clientSecret' => $payment['client_secret'],
                'paymentIntentId' => $payment['payment_intent_id'],
                'amount' => $request->amount,
                'currency' => config('stripe.currency', 'usd'),
            ]);

        } catch (PaymentFailedException $e) {
            return back()->with('error', 'Payment failed: ' . $e->getMessage());
        } catch (\Exception $e) {
            return back()->with('error', 'An error occurred: ' . $e->getMessage());
        }
    }

}

Success Payment

Create a controller to handle Stripe payment requests and callback responses.

<?php
public function success(Request $request)
    {
        $paymentIntentId = $request->query('payment_intent');
        $redirectStatus = $request->query('redirect_status');
        
        Log::info('Payment success redirect', [
            'payment_intent' => $paymentIntentId,
            'redirect_status' => $redirectStatus,
            'all_params' => $request->all()
        ]);

        // If we have payment_intent from query string
        if ($paymentIntentId) {
            try {
                // First try to get payment from database
                $payment = Payment::where('payment_intent_id', $paymentIntentId)->first();
                
                if ($payment) {
                    // Update payment status if needed
                    if ($payment->status !== 'completed' && $redirectStatus === 'succeeded') {
                        $payment->update([
                            'status' => 'completed',
                            'paid_at' => now(),
                        ]);
                    }
                    
                    // Redirect to success page with payment details
                    return redirect()->route('payment.success.show', [
                        'payment_id' => $payment->id
                    ]);
                } else {
                    // Payment not found in database, create a record
                    try {
                        // Get payment from Stripe API
                        $stripePayment = $this->paymentService->getPayment($paymentIntentId);
                        
                        if (!$stripePayment) {
                            // Create a new payment record
                            $payment = Payment::create([
                                'payment_intent_id' => $paymentIntentId,
                                'amount' => 0,
                                'currency' => config('stripe.currency', 'usd'),
                                'status' => $redirectStatus === 'succeeded' ? 'completed' : 'pending',
                                'paid_at' => $redirectStatus === 'succeeded' ? now() : null,
                                'metadata' => ['source' => 'stripe_redirect'],
                            ]);
                            
                            return redirect()->route('payment.success.show', [
                                'payment_id' => $payment->id
                            ]);
                        }
                    } catch (\Exception $e) {
                        Log::error('Error creating payment record: ' . $e->getMessage());
                    }
                }
            } catch (\Exception $e) {
                Log::error('Payment success processing error: ' . $e->getMessage());
            }
        }
        
        // If no payment_intent, show generic success
        return view('payment.success_redirect', [
            'paymentIntentId' => $paymentIntentId,
            'status' => $redirectStatus ?? 'succeeded',
            'amount' => 0,
            'currency' => config('stripe.currency', 'usd'),
        ]);
    }

Show Payment Success

Create a controller to handle Stripe payment requests and callback responses.

<?php
 public function showSuccess($paymentId)
    {
        try {
            $payment = Payment::findOrFail($paymentId);
            
            return view('payment.success', [
                'payment' => $payment,
                'paymentId' => $payment->id,
                'amount' => $payment->amount,
                'currency' => $payment->currency,
                'paymentIntentId' => $payment->payment_intent_id,
                'status' => $payment->status,
            ]);
            
        } catch (\Exception $e) {
            Log::error('Error showing success page: ' . $e->getMessage());
            return view('payment.success_redirect', [
                'paymentIntentId' => null,
                'status' => 'succeeded',
                'amount' => 0,
                'currency' => config('stripe.currency', 'usd'),
            ]);
        }
    }

Cancel Payment

Create a controller to handle Stripe payment requests and callback responses.

<?php
 /**
     * Cancel page
     */
    public function cancel(Request $request)
    {
        $paymentIntentId = $request->query('payment_intent');
        
        return view('payment.cancel', [
            'paymentIntentId' => $paymentIntentId,
            'message' => 'Payment was cancelled'
        ]);
    }

Failed Payment

Create a controller to handle Stripe payment requests and callback responses.

<?php
    /**
     * Failed page
     */
    public function failed(Request $request)
    {
        $paymentIntentId = $request->query('payment_intent');
        $errorMessage = session('error') ?? 'Payment failed. Please try again.';
        
        return view('payment.failed', [
            'paymentIntentId' => $paymentIntentId,
            'errorMessage' => $errorMessage
        ]);
    }

Refund Payment

Create a controller to handle Stripe payment requests and callback responses.

<?php
   
    /**
     * Refund payment
     */
    public function refund(Request $request, $paymentIntentId)
    {
        try {
            $payment = $this->paymentService->refundPayment(
                $paymentIntentId,
                $request->amount ?? null
            );

            return response()->json([
                'success' => true,
                'payment' => $payment,
                'message' => 'Payment refunded successfully'
            ]);

        } catch (PaymentFailedException $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage()
            ], 400);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'An error occurred: ' . $e->getMessage()
            ], 400);
        }
    }

Status Payment

Create a controller to handle Stripe payment requests and callback responses.

<?php
    /**
     * Get payment status
     */
    public function status($paymentIntentId)
    {
        try {
            $payment = $this->paymentService->getPayment($paymentIntentId);

            if (!$payment) {
                return response()->json([
                    'success' => false,
                    'message' => 'Payment not found'
                ], 404);
            }

            return response()->json([
                'success' => true,
                'payment' => $payment,
                'status' => $payment->status
            ]);

        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage()
            ], 400);
        }
    }

Payment Model

Create a Payment model and use Eloquent to temporarily store the order data before redirecting the customer to the Stripe payment gateway.

php artisan make:model Payment -m
<?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->unsignedBigInteger('user_id')->nullable();
            $table->string('payment_intent_id')->unique();
            $table->string('customer_id')->nullable();
            $table->decimal('amount', 10, 2);
            $table->string('currency', 3)->default('usd');
            $table->string('status')->default('pending');
            $table->json('metadata')->nullable();
            $table->json('stripe_data')->nullable();
            $table->string('refund_id')->nullable();
            $table->decimal('refund_amount', 10, 2)->nullable();
            $table->timestamp('refunded_at')->nullable();
            $table->timestamp('paid_at')->nullable();
            $table->string('checkout_session_id')->nullable();
            $table->timestamps();

            $table->index('user_id');
            $table->index('status');
            $table->index('created_at');
        });
    }

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

Store the Subscription

Create a Subscription model and use Eloquent to temporarily store the order data before redirecting the customer to the Stripe payment gateway.

php artisan make:model Subscription -m
<?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('subscriptions', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id')->nullable();
            $table->string('customer_id');
            $table->string('subscription_id')->unique();
            $table->string('price_id');
            $table->string('status');
            $table->timestamp('trial_ends_at')->nullable();
            $table->timestamp('ends_at')->nullable();
            $table->json('metadata')->nullable();
            $table->json('stripe_data')->nullable();
            $table->timestamps();

            $table->index('user_id');
            $table->index('status');
            $table->index('created_at');
        });
    }

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

Note: The order data is stored temporarily and should only be persisted to the orders table after the payment is completed successfully.

Clear Cache

After configuration:

php artisan config:clear
php artisan cache:clear
php artisan optimize:clear

Security

  • ✅ Callback URLs are automatically excluded from CSRF verification.
  • ✅ IPN requests are validated.
  • ✅ Amount and Transaction ID are verified.
  • ✅ No sensitive data is stored in logs.
  • ✅ Sandbox mode supported.
  • ✅ SSL/TLS encryption.
  • ✅ Validation against Stripe API.

Testing

Run the following command:

php artisan stripe:make-payment 100 --product="Test Product"

This command initiates a sandbox payment and displays the payment URL.

Troubleshooting

1. Could not find a matching version

Cause

Package version not tagged.

Solution

Use dev-main or create a Git tag.

Author

Syed Mahamudul Hassan

License

This package is open-sourced software licensed under the MIT License.

See the LICENSE file for more information.

Support

If this package helps you, please consider giving it a ⭐ on GitHub.