Laravel Spark with Stripe and Paddle: SaaS Billing for Beginners

Arlind Musliu cofounder at Lucky Media
Arlind Musliu

April 16, 2025 · 8 min read

Laravel for Beginners: Using Laravel Spark with Stripe and Paddle

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_KEY

Step 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_SECRET

Step 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
@endsection

We 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>
@endsection

Conclusion

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

Laravel
Arlind Musliu cofounder at Lucky Media
Arlind Musliu

Cofounder and CFO of Lucky Media

Stay up-to-date

Be updated with all news, products and tips we share!

Let’s chat

We partner with a limited number of brands each quarter to ensure senior-level attention on every project.

lokman and arlind headshots
Teamwork

Related posts

March 21, 2025

How to maintain enterprise websites
Statamic
Business

March 13, 2025

Why do we use Astro?
Astro