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

Chargify Bundle Laravel Package

flapjacklabs/chargify-bundle

Symfony bundle for integrating Chargify billing into your application, providing services and configuration to manage subscriptions, customers, and Chargify API interactions with less boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony 2.x bundle, not a Laravel package. There is no service provider, no facade, and no config/chargify.php — so the realistic "day one" move for a Laravel developer is to treat it as a reference implementation for wiring the Chargify API client, not as a dependency you install.

Where to look first (read the repo, don't require it):

  1. DependencyInjection/Configuration.php — the exact config tree (subdomain, api_key, shared_secret, environment/test_mode flags). This is your config schema.
  2. Resources/config/services.yml — the service IDs and constructor arguments. This tells you what the bundle actually wrapped (usually a Chargify\Client / ChargifyAPI instance plus a thin wrapper service).
  3. DependencyInjection/FJLChargifyExtension.php — how config is turned into client construction. This is the 20 lines you will rewrite as a Laravel service provider.
  4. composer.json — the underlying Chargify PHP SDK version it pins. That vendor library is the only part with real residual value.

Minimal reimplementation in Laravel (the practical equivalent of the bundle):

// composer require chargify/chargify-php   (or use Guzzle directly)

// config/services.php
'chargify' => [
    'subdomain' => env('CHARGIFY_SUBDOMAIN'),
    'api_key'   => env('CHARGIFY_API_KEY'),
    'shared_secret' => env('CHARGIFY_SHARED_SECRET'),
    'test_mode' => env('CHARGIFY_TEST_MODE', false),
],

// app/Providers/ChargifyServiceProvider.php
public function register(): void
{
    $this->app->singleton(ChargifyClient::class, fn () => new ChargifyClient(
        config('services.chargify.subdomain'),
        config('services.chargify.api_key'),
    ));
}

First use case: read a subscription by ID and create a customer + subscription, behind a small ChargifyService class so you never touch the SDK directly from a controller. That single abstraction is the thing the bundle gave Symfony users — recreate it once and you're done.

Implementation Patterns

Thin service wrapper, never call the SDK from controllers. The bundle's whole job was injecting a configured client into services. Do the same: a ChargifyService (or several focused ones: Subscriptions, Customers, Products) that owns error translation and logging.

class ChargifyService
{
    public function __construct(private ChargifyClient $client) {}

    public function subscription(int $id): Subscription
    {
        try {
            return $this->client->subscriptions->read($id);
        } catch (ChargifyException $e) {
            throw new ChargifyApiException($e->getMessage(), $e->getCode(), $e);
        }
    }
}

Webhook-first state. Chargify is the source of truth. Mirror only what you need locally (chargify_subscription_id, state, product_id, current_period_ends_at) and update it from webhooks. Keep a chargify_events table keyed on Chargify's event/subscription IDs so replayed deliveries are no-ops.

Route::post('/webhooks/chargify', ChargifyWebhookController::class)
    ->middleware('verify.chargify.signature');

Verify X-Chargify-Webhook-Signature — HMAC-SHA256 of the raw body using the shared secret, compared with hash_equals() against the hex digest. Symfony bundle config exposes the shared secret for exactly this; in Laravel it belongs in config/services.php.

Queue the handler. Dispatch an event-specific job (SubscriptionStateChanged, PaymentFailed, SubscriptionCanceled) so a slow handler doesn't time out the webhook and trigger Chargify retries. Match on the event string, not the HTTP verb.

Interface the client for tests. Bind ChargifyClientInterface in the container and swap in a fake in tests; the SDK is Guzzle-based, so Http::fake() doesn't apply — you either fake at the interface boundary or point the client at a local stub subdomain.

Multi-tenant / multi-subdomain. The bundle assumes one client per container, which breaks the moment you serve several Chargify sites. Register the client as a factory keyed by subdomain rather than a singleton:

$this->app->bind(ChargifyClient::class, fn ($app, $params) => new ChargifyClient(...));

Reconciliation job. A nightly chargify:reconcile command that pulls subscriptions updated since the last run catches dropped webhooks. Cheap insurance; the bundle had no equivalent, so it's on you.

Gotchas and Tips

  • Do not install this in a Laravel app. Symfony bundles register via AppKernel, use Symfony\Component\DependencyInjection for config, and depend on Symfony 2.x-era contracts. Pulling it in means dragging in a dead Symfony stack for zero Laravel benefit. Copy the config schema and the client wiring; leave the bundle.
  • Abandonment is the headline risk. 0 stars, 0 dependents, Flapjack Labs is long gone, and it targets an API surface from roughly 2011–2013. Treat every class name as historical, not authoritative.
  • The upstream API moved. Chargify is now Maxio Advanced Billing. Hostnames, API versions, and several v1 endpoints changed; a client library pinned by a 2013 bundle will hit deprecated paths. Pin and verify against the current Maxio docs before assuming any endpoint still exists.
  • Amounts are integers in cents. The classic bug is storing Chargify's price_in_cents as dollars. Convert at the boundary and pick one canonical unit for your DB.
  • 422 is not an exception by default. Validation failures come back as {"errors": [...]} with a 422. Wrap them into a domain exception with the array intact — you will need those messages for user-facing billing errors.
  • Webhook signature is over the raw body. $request->all() re-encodes JSON and breaks the HMAC. Use $request->getContent(). Also guard against replays: store the event ID and reject duplicates, and don't trust ordering — a subscription.canceled can arrive before the subscription.created you're expecting.
  • Test mode is a real switch. Chargify sandboxes use a separate subdomain and API key, and some charge operations gate on a test=true parameter. Keep separate env vars (CHARGIFY_SANDBOX_*) rather than a boolean flag threaded through the client.
  • Pagination and rate limits. List endpoints default to a page at a time; a naive ->all() style call silently truncates or hammers the API. Always paginate explicitly and back off on 429.
  • Debugging. Log request URL + response status + body (redact the API key and any card data) via a Guzzle middleware. That middleware is also the cleanest extension point — retries, tracing, and request IDs all live there rather than in the wrapper service.
  • PCI boundary. Never post raw card numbers through your server. Use Chargify.js / hosted pages / Chargify Direct and let the token come back to you. The bundle predates modern hosted-field guidance, so don't copy its payment flows blindly.
  • Secrets. The bundle's config lived in parameters.yml in the repo; in Laravel use env vars plus config:cache, and never log services.chargify.api_key.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource