Currently Available: Need a skilled Software Developer for your next project?
Categories
Laravel

Should I Implement Soft Deletes In My Laravel Spark-Based Billing System?

I recently investigated whether I should (or needed to) add soft deletes to my Laravel Spark-based billing system.

Laravel Spark for Paddle is a higher-level layer on top of Laravel Cashier Paddle.

Spark provides a billing portal out of the box, while Cashier handles most of the underlying payment logic and Eloquent models.

Spark’s Hidden Models

When you look at Spark’s codebase, you’ll see migrations for tables such as:

  • customers
  • subscriptions
  • subscription_items
  • transactions

But you won’t see Eloquent models named Customer, Subscription, etc. That’s because Spark defers to the actual Eloquent models defined in Laravel Cashier Paddle. Specifically:

  • Laravel\Paddle\Customer maps to the customers table.
  • Laravel\Paddle\Subscription maps to subscriptions.
  • Laravel\Paddle\Transaction maps to transactions.
  • Laravel\Paddle\SubscriptionItem maps to subscription_items.

Spark simply publishes and references the same migrations that Cashier needs. That explains why Spark’s repository lacks explicit model files even though migrations exist: they live in the Cashier Paddle package.

How Cashier Paddle Handles Data

  1. Billable Trait
    Typically, your User model uses the Laravel\Paddle\Billable trait. This trait ties your user to the Customer, Subscription, Transaction, and SubscriptionItem models. You get methods like $user->subscriptions() or $user->transactions() for free.

  2. Database Tables
    Cashier’s Eloquent models store all billing and subscription data in a standard set of tables (customers, subscriptions, etc.). Each row includes status columns like status, timestamps for trial_ends_at, paused_at, ends_at, and so on.

  3. Webhook Synchronization
    When a user subscribes or completes a checkout, Paddle sends webhook events. The WebhookController in Cashier processes these events, updates local Eloquent models, and fires events like SubscriptionCreated, SubscriptionCanceled, etc.

Why No Soft Deletes?

By default, neither Spark nor Cashier Paddle implements soft deletes—there is no deleted_at column in the migrations, and the Eloquent models don’t include SoftDeletes.

1. Permanent Records for Billing

In subscription/payment systems, the common practice is to keep a permanent ledger of historical data for regulatory and compliance reasons. Even if someone cancels a subscription or if a user is “removed,” the subscription and transaction records remain in the database.

2. Using Status Fields

Instead of soft deleting, the code uses status fields such as status = 'canceled' and timestamps like ends_at or paused_at. This allows your app to cleanly check if a subscription is active, canceled, trialing, or paused, without actually removing any record from storage.

3. Historical & Accounting Needs

Transactions in particular are almost always kept forever so that you can reference them for tax calculations, auditing, invoice lookups, or user support.

So the answer is, no we don't need soft deletes here as the system is already set up to keep historical data, just not using soft deletes for this.

What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Subscribe to my newsletter

Get new posts when I publish them.

I respect your privacy. Unsubscribe at any time.

Leave a Reply

Your email address will not be published. Required fields are marked *