Laravel Spark with Stripe and Paddle: SaaS Billing for Beginners

April 16, 2025 · 8 min read

2026 UPDATE - LARAVEL 13
We’re excited to announce that we have updated all of our blog post examples to reflect the new Laravel 13 version! Our previous examples were based on Laravel 10, but with the release of Laravel 13, we wanted to ensure that our readers have access to the most up-to-date information and examples.
Using Stripe and Paddle with Laravel Spark
A major component of the Laravel ecosystem is Laravel Spark, which easily integrates subscription management into your app while relying on Laravel Cashier to handle the underlying payment processes. In this guide, we will explore how to effectively set up Laravel Spark with Stripe and Paddle and how to allow subscribed users access to exclusive blog content.
What is Laravel Cashier?
Laravel Cashier is a powerful subscription billing management package for Laravel applications that simplifies the implementation of subscription services and payment processing. Here are some key features of Laravel Cashier:
Simplified Subscription Management: Easily create and manage subscriptions for your users without dealing with complex payment gateway integrations.
Payment Gateway Support: Primarily designed to work with Stripe and Paddle, Cashier handles the necessary API interactions for you.
Automatic Renewals: Automatically manage subscription renewals, ensuring users are billed according to their plans without manual intervention.
Invoice Handling: Generate and manage invoices, allowing users to view their billing history and download invoices as needed.
Flexibility with Discounts: Apply discount codes and manage promotional offers to enhance customer retention.
User-Friendly Interface: Provides a clean and intuitive API, making it easier for developers to implement billing features without extensive knowledge of payment processing.
Developers can build rich features for their applications while leaving the complexities of payment processing to Laravel Cashier. Now that we have explained Cashier, we can discuss Laravel Spark.
What is Laravel Spark?
Laravel Spark is a commercial package that builds on the capabilities of Laravel Cashier, providing a detailed foundation for creating subscription-based applications. Here are some of the key aspects that set Spark apart:
Multi-Tenant User Management: Easy setup and management of user accounts and teams.
Tailored User Interfaces: Pre-designed user interfaces for subscription management.
Plan and Team Management: Create and manage multiple subscription plans and team membership options.
Billing Information: Includes payment methods and billing history.
Feature Flags: Toggle features on and off for users based on their subscription level.
Notifications and Alerts: Built-in options to notify users about upcoming renewals, payment confirmations, and other important billing-related alerts.
Installing and setting up Laravel Spark
We first need to purchase a license from the official Laravel Spark website.
Step 1. Install Laravel Spark using Composer
Laravel Spark can be installed by adding the Spark repository to our application’s composer.json file. We also need to add the laravel/spark-paddle or laravel/spark-stripe package to the list of required packages:
{
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.2",
"laravel/framework": "^11.0",
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.9"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.13",
"fakerphp/faker": "^1.23",
"laravel/breeze": "^2.0",
"laravel/pint": "^1.13",
"laravel/sail": "^1.26",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.0",
"pestphp/pest": "^2.0",
"pestphp/pest-plugin-laravel": "^2.0",
"spatie/laravel-ignition": "^2.4",
"laravel/framework": "^11.0",
"laravel/spark-paddle": "^5.0"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"repositories": [
{
"type": "composer",
"url": "https://spark.laravel.com"
}
],
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}Now we can run the composer update command to add the package to our application.
Step 2. Publish the configuration files
After installing the package, we need to publish the Spark installation files using:
php artisan spark:install
This command will generate the necessary files and configurations required to get started with Spark.
Step 3. Run Migrations
We run the database migrations to set up the necessary tables:
php artisan migrate
Our Laravel Spark setup is complete, and we are ready to integrate payment gateways such as Stripe and Paddle.
Step 4. Define the Billable Model
Most applications bill users monthly or annually, but you could also choose a different model, like a team. To finish the setup, we need to add the Spark\Billable trait to our billable model and cast the trial_ends_at attribute to datetime.
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spark\Billable;
class User extends Authenticatable
{
use Billable, HasFactory, Notifiable;
protected $casts = [
'trial_ends_at' => 'datetime',
];
}Using Laravel Spark with Stripe
Integrating Stripe with Laravel Spark enables us to manage subscription payments easily.
Step 1. Create a Stripe account
We need an active Stripe account to use Stripe with Laravel Spark.
Step 2. Obtain API keys
We should retrieve our Stripe API keys from the Stripe dashboard.
Step 3. Update the .env file ( Stripe )
Next, we add our Stripe credentials to the .env file:
CASHIER_CURRENCY=USD
CASHIER_CURRENCY_LOCALE=en
STRIPE_KEY=YOUR_STRIPE_PUBLIC_KEY
STRIPE_SECRET=YOUR_STRIPE_SECRET_KEY
STRIPE_WEBHOOK_SECRET=YOUR_STRIPE_SECRET_KEYStep 4. Create subscription plans
We specify our subscription plans in our application using Laravel Spark’s built-in functionalities. We can do this in the config/spark.php file:
use App\Models\User;
'billables' => [
'user' => [
'model' => User::class,
'trial_days' => 7,
'plans' => [
[
'name' => 'Standard',
'short_description' => 'Our plan description',
'monthly_id' => 'price_id',
'yearly_id' => 'price_id',
'features' => [
'Feature 1',
'Feature 2',
'Feature 3',
],
],
],
],
]Step 5. Configure Stripe Webhooks
We need to enable Stripe Webhooks on the Stripe dashboard webhook management panel. This will send webhook alerts to our application's /spark/webhook URI. We should enable webhook alerts for the following events:
customer.deleted
customer.subscription.created
customer.subscription.deleted
customer.subscription.updated
customer.updated
invoice.payment_action_required
invoice.payment_succeeded
payment_method.automatically_updated
Using Stripe Webhooks locally
We need to allow Stripe to send webhooks to our local development environment. We can do this with the Stripe CLI command:
stripe listen --forward-to http://localhost:8000/spark/webhook
Now that Spark is configured with Stripe, customers can start subscribing to our plans.
Using Laravel Spark with Paddle
Integrating Paddle with Laravel Spark enables us to manage subscription payments easily.
Step 1. Create a Paddle account
We should visit the Paddle website and create an account.
Step 2. Retrieve Paddle credentials
We need to find our Vendor ID and Public Key on the Paddle account settings.
Step 3. Update the .env file ( Paddle )
Add your Paddle credentials to the .env file:
CASHIER_CURRENCY=USD
CASHIER_CURRENCY_LOCALE=en
PADDLE_SANDBOX=true
PADDLE_CLIENT_SIDE_TOKEN=YOUR_PADDLE_TOKEN
PADDLE_API_KEY=YOUR_PADDLE_API_KEY
PADDLE_WEBHOOK_SECRET=YOUR_PADDLE_WEBHOOK_SECRETStep 4. Create subscription plans
Define your subscription options similarly to how you did with Stripe in the PlanController.php:
use App\Models\User;
'billables' => [
'user' => [
'model' => User::class,
'trial_days' => 5,
'plans' => [
[
'name' => 'Standard',
'short_description' => 'Our plan description',
'monthly_id' => 'monthly_price_id',
'yearly_id' => 'yearly_price_id',
'features' => [
'Feature 1',
'Feature 2',
'Feature 3',
],
],
],
],
]Step 5. Configure Paddle Webhooks
We need to enable Paddle Webhooks on the Paddle dashboard webhook management panel. This will send webhook alerts to our application's /paddle/webhook URI. We should enable webhook alerts for the following events:
Customer Updated
Transaction Completed
Transaction Updated
Subscription Created
Subscription Updated
Subscription Paused
Subscription Canceled
Using Paddle Webhooks locally
We can do this by exposing our application via a site sharing service such as Ngrok or Expose.
Your application is now ready to handle subscriptions through Paddle alongside Stripe.
Example of using Laravel Spark for Premium Content Access
Let's continue our Laravel for Beginners series, where we have an existing example with the blog post project. We'll create a new PremiumPostsController that restricts access to premium blog posts, making them available only to subscribed users. For simplicity, we will only focus on the most critical features to make this work.
Prerequisites
We already have our posts created and a Post model. We will need to make a new migration command to modify the Post model and add a new column is_premium. This can be done with the command:
php artisan make:migration add_is_premium_to_posts_table --table=posts
Open the newly created migration file and add the following code:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->boolean('is_premium')->default(false);
});
}
};Now you need to run the migration command to update the database:
php artisan migrate
To be able to modify existing posts and create some premium posts, we need to modify the $fillable property on the Post model file.
protected $fillable = [
'title',
'content',
'is_premium',
];Another important change is on the PostsController at the store and update methods, we also need to add the new column:
$validatedData = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
'is_premium' => 'required',
]);Make sure to add an input field on your view file to be able to create or edit premium posts.
If you are able to create premium posts, we can now learn how to show them to subscribed users only.
1. Create the PremiumPostsController
Generate a new controller for handling premium posts using the following command:
php artisan make:controller PremiumPostsController
2. Define the method for displaying Premium posts
In your newly created PremiumPostsController, you'll want to create a method that checks if the user is subscribed before allowing access to premium content:
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
class PremiumPostsController extends Controller
{
public function index()
{
// Only let subscribed users proceed
if (!Auth::check() || !Auth::user()->subscribed('default')) {
return redirect()->route('spark.portal')
->with('error', 'You must be a subscriber to access premium content.');
}
$premiumPosts = Post::where('is_premium', true)->get();
return view('premium-posts.index', compact('premiumPosts'));
}
public function show($id)
{
// Only let subscribed users proceed
if (!Auth::check() || !Auth::user()->subscribed('default')) {
return redirect()->route('spark.portal')
->with('error', 'You must be a subscriber to access premium content.');
}
$post = Post::where('is_premium', true)->findOrFail($id);
return view('premium-posts.show', compact('post'));
}
}3. Define the route for Premium posts
Next, update your routes/web.php file to include a route for the premium posts index page and view page. This route should point to the index and show method in your PremiumPostsController:
use App\Http\Controllers\PremiumPostsController;
use App\Http\Controllers\LocaleController;
use App\Http\Controllers\PostController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
Route::get('/premium', [PremiumPostsController::class, 'index'])->name('premium.index');
Route::get('/premium/{id}', [PremiumPostsController::class, 'show'])->name('premium.show');
});
Route::resource('posts', PostController::class);
Route::resource('users', UserController::class);
Route::post('/locale', LocaleController::class)->name('locale.change');
require __DIR__.'/auth.php';4. Create a view for displaying Premium posts
Now, create a view file to display the premium posts. In your resources directory, create a new view file at resources/views/premium-posts/index.blade.php:
@extends('layouts.app')
@section('content')
<h1>Premium Posts</h1>
@if($premiumPosts->isEmpty())
<p>No premium posts available at the moment.</p>
@else
<ul>
@foreach($premiumPosts as $post)
<li>
<a href="{{ route('premium.show', $post->id) }}">
{{ $post->title }}
</a>
</li>
@endforeach
</ul>
@endif
@endsectionWe also need to create the view page premium-posts/show.blade.php to show individual premium posts:
@extends('layouts.app')
@section('content')
<h1>{{ $post->title }}</h1>
<div>
{{ $post->content }}
</div>
<a href="{{ route('premium.posts') }}">Back to Premium Posts</a>
@endsectionConclusion
Using Laravel Spark with Stripe and Paddle simplifies building subscription-based Laravel applications by providing billing integration, subscription management, and premium content access out of the box. This approach allows you to deliver a web app where only subscribed users can access premium articles, with clear redirects and messaging for unsubscribed visitors.
This article is part of our series Laravel for Beginners: A Step-by-Step Guide to Learn the Concepts.
Bring Your Ideas to Life 🚀
If you need help with a Laravel project let’s get in touch.
Lucky Media is proud to be recognized as a leading Laravel Development Agency
Technologies

Stay up-to-date
Be updated with all news, products and tips we share!
On this page
- 2026 UPDATE - LARAVEL 13
- Using Stripe and Paddle with Laravel Spark
- What is Laravel Cashier?
- What is Laravel Spark?
- Installing and setting up Laravel Spark
- Step 1. Install Laravel Spark using Composer
- Step 2. Publish the configuration files
- Step 3. Run Migrations
- Step 4. Define the Billable Model
- Using Laravel Spark with Stripe
- Step 1. Create a Stripe account
- Step 2. Obtain API keys
- Step 3. Update the .env file ( Stripe )
- Step 4. Create subscription plans
- Step 5. Configure Stripe Webhooks
- Using Stripe Webhooks locally
- Using Laravel Spark with Paddle
- Step 1. Create a Paddle account
- Step 2. Retrieve Paddle credentials
- Step 3. Update the .env file ( Paddle )
- Step 4. Create subscription plans
- Step 5. Configure Paddle Webhooks
- Using Paddle Webhooks locally
- Example of using Laravel Spark for Premium Content Access
- Prerequisites
- 1. Create the PremiumPostsController
- 2. Define the method for displaying Premium posts
- 3. Define the route for Premium posts
- 4. Create a view for displaying Premium posts
- Conclusion
- Bring Your Ideas to Life 🚀

