Introduction
Have you ever wondered why some websites make online payments feel as smooth as butter while others leave you frustrated and clicking away? The secret often lies in their payment gateway integration. If you’re a Laravel developer looking to Stripe integrate into your application, you’re in the right place!
Think of Stripe as the Swiss Army knife of payment processing – it’s versatile, reliable, and packed with features that make handling online payments a breeze. When combined with Laravel’s elegant framework, you get a powerhouse combination that can handle everything from simple one-time payments to complex subscription billing.
Understanding Stripe and Laravel Integration
What Makes Stripe Perfect for Laravel?
Stripe isn’t just another payment processor – it’s a complete payment infrastructure that speaks the same language as modern web developers. When you combine it with Laravel, you’re essentially pairing a Ferrari engine with a Formula 1 chassis.
Laravel’s built-in features like middleware, validation, and Eloquent ORM work seamlessly with Stripe’s API structure. The framework’s service container makes it easy to inject Stripe services wherever you need them, while Laravel’s event system perfectly complements Stripe’s webhook functionality.
Key Benefits of This Integration
The stripe laravel combination offers several advantages:
- Rapid development with pre-built components
- Robust security through Laravel’s built-in protections
- Scalable architecture that grows with your business
- Comprehensive documentation and community support
Prerequisites and Setup Requirements
What You’ll Need Before Starting
Before diving into the integration, make sure you have these essentials ready:
Technical Requirements:
- PHP 8.0 or higher
- Laravel 9.x or 10.x
- Composer installed globally
- A Stripe account (free to create)
- Basic knowledge of Laravel routing and controllers
Stripe Account Setup:
- Create your free Stripe account at stripe.com
- Verify your email address
- Complete your business profile
- Obtain your API keys from the dashboard
Understanding API Keys
Stripe provides two types of API keys:
- Publishable keys (safe for client-side code)
- Secret keys (must be kept server-side only)
Think of these like a house key and a master key – the publishable key opens the front door, while the secret key gives you access to everything inside.
Installing Stripe in Your Laravel Project
Using Composer for Installation
The easiest way to add Stripe to your Laravel project is through Composer:
composer require stripe/stripe-php
Laravel Cashier Alternative
For more advanced features, consider Laravel Cashier:
composer require laravel/cashier
Advanced Laravel Features
For more complex integrations, you might want to explore Laravel’s official documentation on payment processing patterns and best practices. The Laravel Documentation provides excellent guidance on structuring payment-related services and implementing proper error handling strategies.
Laravel Cashier provides a more Laravel-centric approach to Stripe integration, offering:
- Built-in subscription management
- Invoice handling
- Webhook management
- Customer portal integration
Publishing Configuration Files
If using Cashier, publish the migration files:
php artisan vendor:publish --tag="cashier-migrations"
php artisan migrate
Configuring Environment Variables
Setting Up Your .env File
Add your Stripe credentials to your .env
file:
STRIPE_KEY=pk_test_your_publishable_key_here
STRIPE_SECRET=sk_test_your_secret_key_here
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here
Config File Setup
Create a dedicated config file for Stripe settings:
// config/stripe.php
return [
'key' => env('STRIPE_KEY'),
'secret' => env('STRIPE_SECRET'),
'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
];
Pro Tip: Never commit your actual API keys to version control. Always use environment variables!
Creating Your First Payment Form
Frontend Payment Form
Create a simple payment form using Stripe Elements:
<form id="payment-form">
<div id="payment-element">
<!-- Stripe Elements will create form elements here -->
</div>
<button id="submit" type="submit">Pay Now</button>
</form>
JavaScript Integration
Initialize Stripe on the frontend:
const stripe = Stripe('{{ config('stripe.key') }}');
const elements = stripe.elements();
const paymentElement = elements.create('payment');
paymentElement.mount('#payment-element');
Handling Form Submission
Process the form submission with client-side validation:
document.getElementById('payment-form').addEventListener('submit', async (event) => {
event.preventDefault();
const {error} = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: '{{ route('payment.success') }}'
}
});
if (error) {
console.error('Payment failed:', error);
}
});
Processing Payments with Controllers
Creating the Payment Controller
Generate a controller to handle payment processing:
php artisan make:controller PaymentController
Payment Processing Logic
Implement the core payment processing method:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Stripe\Stripe;
use Stripe\PaymentIntent;
class PaymentController extends Controller
{
public function createPaymentIntent(Request $request)
{
Stripe::setApiKey(config('stripe.secret'));
$paymentIntent = PaymentIntent::create([
'amount' => $request->amount * 100, // Convert to cents
'currency' => 'usd',
'metadata' => [
'user_id' => auth()->id(),
'order_id' => $request->order_id
]
]);
return response()->json([
'client_secret' => $paymentIntent->client_secret
]);
}
}
Route Configuration
Add routes for your payment endpoints:
Route::post('/create-payment-intent', [PaymentController::class, 'createPaymentIntent'])
->middleware('auth');
Route::get('/payment/success', [PaymentController::class, 'success'])
->name('payment.success');
Handling Payment Success and Failures
Success Page Implementation
Create a dedicated success page for completed payments:
public function success(Request $request)
{
$paymentIntentId = $request->get('payment_intent');
if ($paymentIntentId) {
// Retrieve payment details from Stripe
Stripe::setApiKey(config('stripe.secret'));
$paymentIntent = PaymentIntent::retrieve($paymentIntentId);
// Update your database
// Send confirmation email
// Redirect to thank you page
return view('payment.success', compact('paymentIntent'));
}
return redirect()->route('home')->with('error', 'Payment verification failed');
}
Error Handling Best Practices
Implement comprehensive error handling:
try {
$paymentIntent = PaymentIntent::create($paymentData);
} catch (\Stripe\Exception\CardException $e) {
// Card was declined
return back()->with('error', 'Your card was declined.');
} catch (\Stripe\Exception\InvalidRequestException $e) {
// Invalid parameters
return back()->with('error', 'Invalid payment request.');
} catch (\Exception $e) {
// General error
return back()->with('error', 'Something went wrong. Please try again.');
}
Implementing Stripe Webhooks
Why Webhooks Matter
Webhooks are like having a direct phone line between Stripe and your application. Instead of constantly asking “Has anything happened?”, Stripe calls you immediately when events occur.
Setting Up Webhook Endpoints
Create a webhook controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Stripe\Webhook;
use Stripe\Exception\SignatureVerificationException;
class WebhookController extends Controller
{
public function handleWebhook(Request $request)
{
$payload = $request->getContent();
$sigHeader = $request->header('Stripe-Signature');
$endpointSecret = config('stripe.webhook_secret');
try {
$event = Webhook::constructEvent($payload, $sigHeader, $endpointSecret);
} catch (SignatureVerificationException $e) {
return response('Invalid signature', 400);
}
switch ($event->type) {
case 'payment_intent.succeeded':
$this->handlePaymentSucceeded($event->data->object);
break;
case 'payment_intent.payment_failed':
$this->handlePaymentFailed($event->data->object);
break;
default:
\Log::info('Unhandled webhook event: ' . $event->type);
}
return response('Webhook handled', 200);
}
private function handlePaymentSucceeded($paymentIntent)
{
// Update order status
// Send confirmation email
// Update customer records
}
}
Webhook Route Configuration
Add webhook routes with CSRF exemption:
// In routes/web.php
Route::post('/stripe/webhook', [WebhookController::class, 'handleWebhook'])
->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);
Managing Customer Data
Creating Stripe Customers
Link your Laravel users with Stripe customers:
public function createStripeCustomer(User $user)
{
Stripe::setApiKey(config('stripe.secret'));
$customer = \Stripe\Customer::create([
'email' => $user->email,
'name' => $user->name,
'metadata' => [
'user_id' => $user->id
]
]);
$user->update(['stripe_customer_id' => $customer->id]);
return $customer;
}
Storing Payment Methods
Save customer payment methods for future use:
public function savePaymentMethod(Request $request)
{
$user = auth()->user();
if (!$user->stripe_customer_id) {
$this->createStripeCustomer($user);
}
$paymentMethod = \Stripe\PaymentMethod::retrieve($request->payment_method_id);
$paymentMethod->attach(['customer' => $user->stripe_customer_id]);
return response()->json(['status' => 'success']);
}
Setting Up Subscription Billing
Creating Subscription Plans
Define subscription products and prices in your Stripe dashboard or programmatically:
public function createSubscriptionPlan($productName, $amount, $interval = 'month')
{
Stripe::setApiKey(config('stripe.secret'));
$product = \Stripe\Product::create([
'name' => $productName,
]);
$price = \Stripe\Price::create([
'product' => $product->id,
'unit_amount' => $amount * 100,
'currency' => 'usd',
'recurring' => ['interval' => $interval],
]);
return $price;
}
Managing Subscriptions
Handle subscription creation and management:
public function createSubscription(Request $request)
{
$user = auth()->user();
$subscription = \Stripe\Subscription::create([
'customer' => $user->stripe_customer_id,
'items' => [[
'price' => $request->price_id,
]],
'payment_behavior' => 'default_incomplete',
'expand' => ['latest_invoice.payment_intent'],
]);
return response()->json([
'subscription_id' => $subscription->id,
'client_secret' => $subscription->latest_invoice->payment_intent->client_secret
]);
}
Security Best Practices
Protecting Sensitive Data
Never store sensitive payment information in your database:
- Card numbers
- CVV codes
- Expiration dates
Instead, use Stripe’s tokenization system and store only:
- Customer IDs
- Payment method IDs
- Transaction references
Implementing Request Validation
Always validate incoming requests:
public function createPaymentIntent(Request $request)
{
$validated = $request->validate([
'amount' => 'required|numeric|min:0.50',
'currency' => 'required|string|in:usd,eur,gbp',
'payment_method_id' => 'sometimes|string'
]);
// Process payment...
}
Rate Limiting
Protect your payment endpoints with rate limiting:
Route::middleware(['throttle:payment'])
->post('/create-payment-intent', [PaymentController::class, 'createPaymentIntent']);
Testing Your Integration
Using Stripe Test Mode
Stripe provides test card numbers for different scenarios:
Successful payments:
4242424242424242
(Visa)5555555555554444
(Mastercard)
Failed payments:
4000000000000002
(Card declined)4000000000009995
(Insufficient funds)
Automated Testing
Create tests for your payment functionality:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
class PaymentTest extends TestCase
{
public function test_payment_intent_creation()
{
$user = User::factory()->create();
$response = $this->actingAs($user)->post('/create-payment-intent', [
'amount' => 10.00,
'currency' => 'usd'
]);
$response->assertStatus(200)
->assertJsonStructure(['client_secret']);
}
}
Common Troubleshooting Issues
CORS Configuration Issues
If you’re having CORS problems, add proper headers:
// In app/Http/Middleware/Cors.php
public function handle($request, Closure $next)
{
return $next($request)
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
}
SSL Certificate Requirements
Stripe requires HTTPS in production. Ensure your Laravel app uses SSL:
// In AppServiceProvider
public function boot()
{
if (app()->environment('production')) {
\URL::forceScheme('https');
}
}
Debugging Webhook Issues
Log webhook events for debugging:
public function handleWebhook(Request $request)
{
\Log::info('Webhook received', [
'type' => $event->type ?? 'unknown',
'id' => $event->id ?? 'unknown',
'data' => $event->data ?? []
]);
// Process webhook...
}
Performance Optimization Tips
Database Indexing
Add indexes to frequently queried payment-related fields:
// In your migration
$table->index('stripe_customer_id');
$table->index('stripe_subscription_id');
$table->index(['user_id', 'status']);
Caching Strategies
Cache customer data to reduce API calls:
public function getStripeCustomer(User $user)
{
return Cache::remember("stripe_customer_{$user->id}", 3600, function () use ($user) {
return \Stripe\Customer::retrieve($user->stripe_customer_id);
});
}
Queued Processing
Use Laravel queues for webhook processing:
public function handleWebhook(Request $request)
{
ProcessStripeWebhook::dispatch($request->all())->onQueue('webhooks');
return response('Webhook queued', 200);
}
Going Live with Your Payment System
Pre-Launch Checklist
Before switching to live mode:
✅ Test all payment flows thoroughly
✅ Verify webhook endpoints are working
✅ Confirm SSL certificates are valid
✅ Review error handling and logging
✅ Set up monitoring and alerts
✅ Complete Stripe account verification
Switching to Live Mode
Update your environment variables:
STRIPE_KEY=pk_live_your_live_publishable_key
STRIPE_SECRET=sk_live_your_live_secret_key
Monitoring and Maintenance
Set up monitoring for:
- Payment success rates
- Error frequencies
- Response times
- Webhook delivery failures
Conclusion
Integrating Stripe with Laravel doesn’t have to be rocket science. With the right approach and understanding of both platforms, you can create a robust, secure payment system that serves your users well.
Remember, the key to successful stripe laravel integration lies in thorough testing, proper error handling, and following security best practices. Start with small, simple implementations and gradually add complexity as your understanding grows.
The combination of Stripe’s powerful payment infrastructure and Laravel’s elegant framework gives you everything you need to build world-class payment experiences. Whether you’re handling one-time payments or complex subscription billing, this integration will scale with your business needs.
Frequently Asked Questions
How long does it typically take to integrate Stripe with Laravel?
A basic integration can be completed in a few hours, while a full-featured implementation with subscriptions and advanced features might take several days. The timeline depends on your requirements and Laravel experience level.
Is it safe to store Stripe API keys in the .env file?
Yes, storing API keys in the .env file is the recommended approach for Laravel applications. Just ensure your .env file is never committed to version control and has proper file permissions (typically 600) on your server.
Can I use Stripe with Laravel for international payments?
Absolutely! Stripe supports payments in 135+ currencies and is available in 40+ countries. You can configure multiple currencies and handle international transactions seamlessly within your Laravel application.
What’s the difference between using raw Stripe PHP SDK versus Laravel Cashier?
The raw Stripe PHP SDK gives you complete control and flexibility, while Laravel Cashier provides a more Laravel-centric approach with built-in subscription management, invoicing, and customer portals. Choose Cashier if you need subscription billing features.
How do I handle failed payments and retry logic in my Laravel Stripe integration?
You can handle failed payments through webhook events, implement automatic retry mechanisms using Laravel queues, and provide customers with options to update payment methods. Set up proper error logging and customer notifications for the best user experience.