This is a legacy Omnipay driver for the old GoCardless API, not GoCardless Pro. Last release was 2015, so treat it as an existing-integration maintenance package rather than something to adopt for a new Laravel app.
composer require omnipay/gocardless
Check what Composer resolves for omnipay/common and Guzzle; older Omnipay versions often conflict with modern Laravel dependencies.
vendor/omnipay/gocardless/src/Gateway.php — supported gateway parameters and request methods.vendor/omnipay/gocardless/src/Message/ — the exact request/response classes and parameter names.// app/Providers/OmnipayServiceProvider.php
public function register(): void
{
$this->app->singleton('omnipay.gocardless', function () {
return \Omnipay\Omnipay::create('GoCardless')->initialize([
'merchantId' => config('services.gocardless.merchant_id'),
'appId' => config('services.gocardless.app_id'),
'appSecret' => config('services.gocardless.app_secret'),
'accessToken' => config('services.gocardless.access_token'),
'testMode' => (bool) config('services.gocardless.test'),
]);
});
}
$response = app('omnipay.gocardless')->purchase([
'amount' => '10.00',
'currency' => 'GBP',
'description' => 'Invoice #123',
'returnUrl' => route('gocardless.callback'),
'cancelUrl' => route('gocardless.cancel'),
])->send();
if ($response->isRedirect()) {
return redirect($response->getRedirectUrl());
}
The exact callback parameters depend on the legacy API flow; confirm them in CompletePurchaseRequest rather than guessing from GoCardless Pro documentation.
Bind the gateway, don’t instantiate it in controllers. Use a singleton or a small GoCardlessGateway wrapper so credentials, test mode, and logging live in one place.
Use an action/service class for the two-step flow. Keep controllers thin:
class StartGoCardlessPayment
{
public function __construct(private \Omnipay\Common\GatewayInterface $gateway) {}
public function handle(Order $order): \Omnipay\Common\Message\ResponseInterface
{
return $this->gateway->purchase([
'amount' => number_format($order->total / 100, 2, '.', ''),
'currency' => 'GBP',
'description' => "Order {$order->id}",
'returnUrl' => route('gocardless.callback'),
'cancelUrl' => route('gocardless.cancel'),
])->send();
}
}
Persist state before redirecting. Store the order ID, amount, currency, and a local correlation/state token in your database. On return, validate that state before calling completePurchase(). Do not trust query parameters alone.
Store the gateway reference and status. After a successful response, save:
$response = app('omnipay.gocardless')->completePurchase($params)->send();
if ($response->isSuccessful()) {
$order->update([
'gateway_reference' => $response->getTransactionReference(),
'status' => 'paid',
]);
}
Use a unique index on your own order/reference column so duplicate callbacks cannot double-process a payment.
Decouple with Laravel events. Dispatch PaymentCompleted / PaymentFailed events from the action class, then handle emails, invoicing, and fulfilment in listeners. This makes it easier to replace the gateway later.
Test with a fake binding. Unit tests should not hit GoCardless. Bind a fake GatewayInterface that returns a mocked ResponseInterface:
$this->app->instance('omnipay.gocardless', $fakeGateway);
Reserve real sandbox tests for the redirect/complete round trip, if the legacy sandbox is still available to your account.
Keep secrets out of code and logs. Read credentials from config/services.php backed by .env. Redact accessToken, appSecret, and full gateway responses before logging.
gocardless/gocardless-pro SDK or direct API calls behind a Laravel service.omnipay/common and Guzzle versions that conflict with Laravel 8+. Run composer why-not omnipay/gocardless and test installation in a branch before committing.merchantId, appId, appSecret, and accessToken are not the same as a GoCardless Pro access token. Mixing documentation from Pro will waste time.0.01, 10.00, and 10.99.Omnipay\Common\Exception\InvalidRequestException and InvalidResponseException, log a redacted context, and show users a generic failure page.Gateway or register custom request classes via Omnipay’s gateway factory, but for a deprecated API the better extension point is usually a thin application-level adapter that lets you swap in GoCardless Pro later.How can I help you explore Laravel packages today?