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

Gocardless Enterprise Bundle Laravel Package

vouchedfor/gocardless-enterprise-bundle

Symfony bundle that integrates the GoCardless Enterprise API for direct debit payments, giving you ready-made services and configuration to manage mandates, subscriptions, and payment flows from your Symfony application.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're actually adopting. Despite the Composer name, this is a Symfony 2 bundle, not a Laravel package. There is no ServiceProvider, no config/ publish tag, no facade, no auto-discovery. A Laravel app cannot boot a Symfony bundle's DependencyInjection/Extension — that only runs inside a Symfony Kernel. So "installing" it in Laravel means one of two things:

  1. Reuse the underlying GoCardless Enterprise SDK (gocardless/gocardless, the legacy GoCardless_Client class) and replicate the bundle's service wiring in a Laravel service provider.
  2. Vendor the bundle's small amount of glue code (config tree → client constructor arguments) into your app, ignoring the Symfony plumbing entirely.

Where to look first (in the repo):

  • DependencyInjection/Configuration.php — the real documentation of required config (app_id, app_secret, merchant_id, environment/sandbox, possibly redirect_uri).
  • DependencyInjection/*Extension.php — how those values are mapped onto the GoCardless client constructor.
  • Resources/config/services.yml — the service ID you'd otherwise inject; copy that argument order verbatim into Laravel.
  • The bundle class itself — usually a no-op Bundle subclass worth ignoring.

Minimal Laravel wiring, assuming the SDK is installed:

composer require gocardless/gocardless
// config/services.php
'gocardless' => [
    'app_id'      => env('GOCARDLESS_APP_ID'),
    'app_secret'  => env('GOCARDLESS_APP_SECRET'),
    'merchant_id' => env('GOCARDLESS_MERCHANT_ID'),
    'environment' => env('GOCARDLESS_ENV', 'sandbox'),
],
// app/Providers/GoCardlessServiceProvider.php
$this->app->singleton(\GoCardless_Client::class, fn () => new \GoCardless_Client([
    'app_id'      => config('services.gocardless.app_id'),
    'app_secret'  => config('services.gocardless.app_secret'),
    'merchant_id' => config('services.gocardless.merchant_id'),
    'environment' => config('services.gocardless.environment'),
]));

First use case worth proving out: fetch a merchant ($client->merchant->get()) in php artisan tinker against the sandbox, then create a single bill against a test pre-authorization. If that round-trips, the rest of the resource surface (bills, subscriptions, pre_authorizations, payouts, users) behaves the same way.

Implementation Patterns

Wrap it behind your own interface immediately. The bundle is a dead end; the Enterprise API is a legacy API. Define a DirectDebitGateway contract (createBill, createSubscription, cancelSubscription, findPayout) and implement it with the legacy client. When you inevitably migrate to GoCardless Pro (gocardless/gocardless-pro, API v2), only the implementation changes.

interface DirectDebitGateway {
    public function createBill(string $mandateId, int $amountPence, string $description): Bill;
}

Service provider as the only integration seam. Bind the raw client as a singleton, and bind your gateway on top of it. Never let GoCardless_Client leak into controllers or models — it's a static-ish, cURL-driven object with no testing hooks.

Webhook handling is the bulk of day-to-day work. Enterprise webhooks POST a payload parameter (JSON) to your endpoint. In Laravel:

// routes/api.php — outside the 'web' group so CSRF doesn't apply
Route::post('/webhooks/gocardless', [GoCardlessWebhookController::class, 'handle']);

Verify the Signature header against the raw request body ($request->getContent()) with hash_equals() before json_decode, then dispatch a job per resource. Idempotency is on you: record the resource id + event type in a unique index and drop duplicates, because GoCardless retries.

Resource-state reconciliation as a queued job. Don't trust the webhook to be your only source of truth. A nightly job that pulls payouts and bills for the last N days and upserts into your own tables saves you repeatedly during incident triage. Enterprise's list endpoints are simple and cheap.

Pre-authorization (mandate) flow. Redirect the customer to the GoCardless confirmation URL returned by the client, store the returned resource_id/pre_authorization id on redirect-back, then create bills against it. Keep the redirect-back route idempotent — customers double-submit.

Testing pattern. Because the client is constructed from a config array, you can swap the binding in tests:

$this->app->instance(DirectDebitGateway::class, new FakeDirectDebitGateway());

Reserve sandbox calls for a single, tagged integration test suite — not your default phpunit run.

Gotchas and Tips

  • Composer will fight you. A Symfony 2 bundle pins symfony/symfony or symfony/framework-bundle to ~2.3–~2.8, which conflicts with the Symfony components Laravel 5+/6+ requires. Expect composer why-not to be your first command. Do not force it in with --ignore-platform-reqs; pull the SDK instead.
  • The Enterprise API is legacy. GoCardless moved new integrations to the Pro API (v2) years ago. Confirm your account is even still on Enterprise before writing a line of code — if not, none of this applies and you want gocardless/gocardless-pro.
  • PHP version compatibility. Legacy bundles and the legacy SDK predate PHP 7 strictness; watch for removed constructs (each(), curly-brace string offsets) if you target PHP 8.
  • Signature verification must use the raw body. Laravel's $request->all() re-encodes JSON and will break the HMAC. Read getContent() first, verify, then decode. Use hash_equals, never ==.
  • Amounts are integers in minor units (pence/cents) and currencies are effectively GBP/EUR. Format at the presentation layer only.
  • Sandbox is a separate account, not a flag on production credentials. Never let a GOCARDLESS_ENV typo reach production — assert on boot:
abort_if(app()->isProduction() && config('services.gocardless.environment') !== 'live', 500);
  • Debugging is awkward. The old client uses raw cURL, so Guzzle middleware and Laravel's Http facade logging won't see anything. Route through a local proxy (mitmproxy) or patch in a CURLOPT_VERBOSE handler temporarily; don't leave verbose cURL logging on — it prints auth headers.
  • Never log app_secret or full request signatures. Log the resource id and your own correlation id; you'll need them to reconcile with GoCardless support.
  • Errors surface inconsistently. Expect a mix of thrown exceptions and arrays containing an error key. Normalise into a single GoCardlessException at the gateway boundary so callers have one thing to catch.
  • Config caching trap. Read everything through config() inside the provider, never env() — otherwise php artisan config:cache silently returns null credentials in production.
  • Extension points are essentially nil. There's no event dispatcher hook, no middleware layer, no retry policy. If you want retries/backoff, implement them in a queued job wrapping the gateway call, not inside the client.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
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