Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Subkit Laravel Package

karpovigorok/subkit

SubKit is a lightweight subscription toolkit for Laravel. Define plans and features, attach subscribers, run trials, and gate access with simple middleware — all through a clean, framework-native API with no billing provider lock-in. Ships with migrations, models, and Blade helpers.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites (verify against the README before installing):

  • PHP 8.1+, Laravel 10/11/12
  • laravel/cashier (Stripe) installed and configured
  • Filament 3.x (the package's panel resources are the bulk of its value — confirm the supported major)
  • A Stripe account with test keys in .env: STRIPE_KEY, STRIPE_SECRET, STRIPE_WEBHOOK_SECRET, and CASHIER_CURRENCY

Minimal install sequence:

composer require karpovigorok/subkit
php artisan vendor:publish --tag="subkit-config"
php artisan vendor:publish --tag="subkit-migrations"
php artisan migrate
php artisan subkit:install   # if provided; otherwise register the plugin manually

Register the Filament plugin in your panel provider (usually app/Providers/Filament/AdminPanelProvider.php):

->plugin(\Karpovigorok\Subkit\SubkitPlugin::make())

Add the Billable trait (Cashier's, or the package's variant) to your User model, then point your webhook at the package route or Cashier's /stripe/webhook.

Where to look first, in order:

  1. config/subkit.php — plan definitions, currency, billable model, feature list. This is the whole contract of the package.
  2. The published migrations — understand the plans / subscriptions / features schema before writing any business logic on top of it.
  3. The Filament resources under src/Filament/ — that's where plan CRUD lives, and where you'll extend admin UX.
  4. The webhook controller / listener registration — confirm which Stripe events are handled out of the box and which are silently dropped.

First use case to ship: a plan in the Filament admin → assign it to a user → redirect to Stripe Checkout → feature flag flips on after webhook. Do this end-to-end with stripe listen --forward-to localhost:8000/stripe/webhook before touching anything else. If that loop works, the rest of the package is mostly UI sugar.

Implementation Patterns

Plans as data, not code. Keep stripe_price_id, interval, amount, and a JSON features column in the plans table and edit them through Filament. Resist hardcoding prices in config — the value of the package is that a non-developer can create a plan. Reference plans by slug (Plan::whereSlug('pro')->first()), never by ID.

Feature gating. The typical pattern is a hasFeature(string $feature): bool on the billable model or a Subkit facade, then a thin middleware:

Route::middleware(['auth', 'subscribed:pro'])->group(...);

In Blade, prefer @if(auth()->user()->subscribedTo('pro')) over embedding feature strings inline — wrap it in your own helper so the package call sites are greppable if you ever swap it out.

Checkout and portal flows. Keep two named routes — one that creates a Checkout Session for a plan slug, one that redirects to the Stripe Billing Portal. Both should accept a return_url. Don't build custom card forms; let Stripe host them.

Upgrade / downgrade. Handle plan swaps via Cashier's swap() with proration, and after a swap always redirect through the portal or a confirmation screen rather than assuming the webhook has already landed. Treat the webhook as the source of truth for the final state, and the request as optimistic UI.

Webhook handling. If the package ships its own webhook endpoint, you still own idempotency. Store processed event.ids in a table (or cache) and skip duplicates. For anything the package ignores — invoice.payment_failed, customer.subscription.trial_will_end — add your own listeners rather than forking. Cashier's event classes are the clean extension point.

Admin vs. tenant panels. If you have a customer-facing Filament panel, do not expose SubkitPlugin there wholesale. Either scope the resources behind a policy, or register only the read-only "current subscription" widget on the tenant panel and keep plan CRUD on the admin panel.

Trials and coupons. Store trial length on the plan, not in Stripe's product config, so it's editable in Filament. Coupons are best passed as a Checkout Session parameter (allow_promotion_codes => true) instead of managing them in the DB.

Testing. Use Cashier::fake() for unit tests of gates and middleware. For the checkout redirect and webhook loop, run against Stripe test mode with a dedicated test clock — mocking Stripe's subscription lifecycle reliably is more work than it's worth.

Gotchas and Tips

  • Webhook secret is the #1 silent failure. In local dev, run stripe listen and copy the ephemeral secret into .env on each session, or your features will never activate. Add a log line in your webhook handler that fires on every received event so you can see immediately whether Stripe is reaching you.
  • Migration conflicts with Cashier. Both publish subscriptions-ish migrations. Publish Cashier first, then Subkit, and diff the files — a duplicated subscriptions table will fail on migrate with a confusing "table already exists" if you're not looking. If the package owns the schema, remove Cashier's migration before running.
  • Billable model assumption. Most of these packages default to App\Models\User. If you're billing a Team/Tenant, set subkit.billable_model (or the equivalent) before migrating — the foreign keys are baked into the published migrations.
  • Filament version pinning. Filament's plugin API churned between 3.x and 4.x. Pin both filament/filament and the package to compatible constraints in composer.json; a composer update on Filament alone can break the panel with a fatal error in the plugin register().
  • Policy / authorization gaps. Filament resources are authorized through Laravel policies. Shipping Subkit's resources without a PlanPolicy can expose plan creation to any panel user. Write the policies explicitly.
  • N+1 on feature checks. hasFeature() called per-row in a Filament table will re-query the subscription each time. Cache the active subscription on the model instance ($user->relationLoaded('subscription') / a memoized property) or eager-load it in the table query.
  • Subscription status is not boolean. active, trialing, past_due, incomplete, and canceled are all distinct. Only active + trialing should unlock features by default; treat past_due as grace-period-with-banner and incomplete as locked. If the package collapses this to a boolean, wrap it.
  • Proration surprises. Swapping plans mid-cycle charges or credits immediately with the default settings. Set proration_behavior explicitly (create_prorations vs always_invoice) — the default changed across Stripe API versions.
  • Escape hatches / bus factor. With 37 stars and a single maintainer, assume this package may go stale. Keep your integration behind a thin internal service class (BillingService) so the day you swap to raw Cashier you change one file. Publish the views and the config rather than relying on package defaults, and don't subclass package classes — compose around them.
  • Currency and tax. Set CASHIER_CURRENCY and the package's currency config consistently at install time; changing it later doesn't reprice existing Stripe prices.
  • Extension points worth publishing up front: config, migrations, views, and any Filament resource classes you intend to override. Publishing on day one is cheap; publishing after you've customized is a merge.
  • Debugging checklist when a feature won't unlock: (1) is the user's stripe_id set? (2) does the local subscriptions row exist and match the Stripe subscription ID? (3) did the webhook 200 or 400? (4) is the feature slug spelled identically in the plan JSON and the gate call? That last one accounts for a surprising share of "bugs."
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route