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

Charge Laravel Package

silentz/charge

Silentz Charge is a Laravel package for simple charge and payment tracking. It provides Eloquent models, migrations, and helpers to record charges, associate them with users, and manage balances, making lightweight billing workflows easier without a full subscription system.

View on GitHub
Deep Wiki
Context7

Getting Started

silentz/charge is a Statamic addon that wires Laravel Cashier (Stripe) into Statamic: Stripe Checkout, the customer billing portal, subscription-aware Antlers tags, and content gating by plan. With 4 stars, 0 dependents, and an archived repository, treat it as a reference implementation or a fork, not a dependency you install and forget. The fastest route to value is usually to read its source and copy the ~300 lines you need into your app, or vendor a fork you control.

Minimal steps (verify tag/route names against the service provider — the repo is archived, so README drift is likely):

  1. Confirm compatibility first. Check composer.json for supported statamic/cms, laravel/framework, and laravel/cashier constraints against your project. Statamic 5 / Laravel 11 projects often need manual constraint bumps.
  2. Install from a fork rather than Packagist, so you can patch:
    "repositories": [{ "type": "vcs", "url": "https://github.com/<you>/statamic-charge" }],
    "require": { "silentz/charge": "dev-main" }
    
  3. Publish assets and migrate:
    php artisan vendor:publish --provider="Silentz\Charge\ServiceProvider"
    php artisan migrate
    
    Look for config/charge.php and any Cashier migrations the provider loads.
  4. Set environment: STRIPE_KEY, STRIPE_SECRET, STRIPE_WEBHOOK_SECRET, plus your price IDs.
  5. Make your user model billable. This is the critical prerequisite: Charge assumes an Eloquent user model using Cashier's Billable trait. Statamic's default file-based users will not work — you need config/auth.php pointed at an Eloquent user repository and the trait applied.
  6. Map plans in config/charge.php (a handle → Stripe price ID map). Treat this file as the single source of truth for plan handles used in templates and middleware.
  7. Point Stripe at the webhook route registered by the package (typically /charge/webhook or /stripe/webhook — check routes/ in the package) and exclude it from CSRF.

Where to look first, in order: README.md → config/charge.php → routes/*.php → src/Http/Controllers/ (checkout, portal, webhook) → src/Tags/ (Antlers tags) → src/ServiceProvider.php (what's actually registered). The service provider tells you the truth faster than the docs.

First use case: gate a members-only collection behind an active subscription and add a "Subscribe" button. That exercises the whole loop — checkout, webhook, subscription state, authorization — in one sitting.

Implementation Patterns

Plan-gated routes via middleware. The canonical pattern is a route group middleware (e.g. charge.subscribed:pro) around protected Statamic routes:

Route::middleware(['web', 'charge.subscribed:pro'])->group(function () {
    Route::statamic('/members/{slug}', 'members/show');
});

Register aliases in app/Http/Kernel.php / bootstrap/app.php if the package doesn't auto-alias on your Laravel version.

Antlers-side gating. Use the package's tags for conditional UI rather than hiding pages only in routes — you want both, since collection listings leak titles otherwise:

{{ if logged_in }}
  {{ charge:subscribed plan="pro" }}
    <a href="/members">Members area</a>
  {{ else }}
    {{ charge:checkout plan="pro" }}<button>Upgrade</button>{{ /charge:checkout }}
  {{ /charge:subscribed }}
{{ /if }}

Verify the exact tag namespace (charge: vs subscriptions:) in src/Tags/; this is the most common source of "tag does nothing" confusion.

Checkout as a POST, not a link. Stripe Checkout sessions should be created server-side and redirected to. If the package exposes a form tag, post to it; if not, add a small controller calling $user->newSubscription('default', $priceId)->checkout([...]). Keep success/cancel URLs in config, not hardcoded in templates.

Billing portal for self-service. Route /billing to $user->billingPortalUrl(route('account')). This removes 90% of "how do I cancel?" support tickets and is the highest-ROI screen to build first.

Webhook handling — don't duplicate Cashier. Charge's webhook controller ultimately calls Cashier's WebhookController (or reimplements it). If you need custom behavior (provisioning, emails, Slack pings), listen to Cashier events (Cashier\Events\WebhookReceived, WebhookHandled) or Stripe events rather than editing the controller. Forking the webhook is the #1 way to break idempotency.

Testing pattern. Use Cashier's test helpers plus Stripe CLI locally:

stripe listen --forward-to localhost:8000/charge/webhook

In feature tests, $user->subscriptions()->create([...]) directly rather than mocking Stripe; only hit the Stripe API in a thin integration test.

Integration tips:

  • Combine with Statamic permissions: charge.subscribed answers "paying?", permission: answers "allowed?" — compose both.
  • Put plan metadata (name, price, features) in config/charge.php and render it in pricing pages so marketing copy and Stripe IDs never diverge.
  • Use Statamic's protect route group for the auth half and Charge middleware for the billing half.
  • For multi-tenant or team billing, the "billable" model is whatever owns the subscription — you may need to point Charge at a Team model instead of User; check for a config key before assuming.

Gotchas and Tips

Archived status is the headline gotcha. No upstream fixes, no Statamic 5/6 compatibility promises, no security patches. Concretely: pin the exact commit in composer.json, commit composer.lock, and budget a half-day to vendor the package into app/ if you plan to keep it past one release cycle. The "opportunity" here is real — a maintained Cashier-for-Statamic addon is a gap.

File-based users silently fail. Cashier's Billable trait needs Eloquent. If config/statamic/users.php uses the file repository, subscriptions can't be stored on the user and every check returns false. Switch to the Eloquent repository (or a custom one backed by Eloquent) before debugging anything else.

Webhook 419s. The Stripe webhook route must bypass CSRF. If the package registers it under web middleware without an exclusion, you'll see 419s in Stripe's dashboard and subscriptions that "work" in checkout but never persist. Verify in routes/ and add VerifyCsrfToken::$except.

Webhook secret mismatch is the silent killer. Signature verification failures return 400 and are easy to miss because checkout still redirects fine. Always run stripe listen locally with the CLI-provided secret, and log WebhookReceived during bring-up.

Migration collisions. Cashier migrations may already exist in your app (subscriptions, subscription_items, customer_columns). Publishing Charge's copies will throw "table already exists". Diff the migration filenames before running, and never re-publish after your first migrate.

Config caching. Plan maps in config/charge.php are read at boot. After editing plans, php artisan config:clear — a stale cached config is the usual reason a price ID "isn't found."

Trial and grace-period nuance. $user->subscribed() is not the same as $user->onTrial() or $user->onGracePeriod(). Decide explicitly whether trials get access (usually yes) and whether cancelled-but-not-expired users do (usually yes until ends_at). Encode that decision in one place — a hasAccess() method on the user or a small policy — rather than scattering Antlers conditionals.

Extension points to prefer: Cashier events, a custom middleware wrapping the package's, and overriding the package's views via vendor:publish --tag=charge-views. Avoid subclassing controllers; route model binding and middleware assumptions make it brittle.

Debugging checklist, in order: (1) Is the user Eloquent and billable? (2) Does the subscriptions row exist after checkout? (3) Did the webhook return 200 in stripe events resend? (4) Is the plan handle in config exactly matching the template/middleware string? (5) Is config cached? This sequence resolves the large majority of issues with this class of addon.

Verdict for day-to-day use: viable for a small memberships site where you're willing to fork and maintain it; not appropriate for anything where billing correctness is business-critical and you can't own the code. In that case, the pragmatic move is to skip the package entirely and wire Cashier into Statamic directly — it's a few routes, a middleware, and one webhook, and you keep the upgrade path.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle