GoCardless Direct Debit — Setup Tutorial
Module: Payment Gateway Integration
Document Type: Tutorial (Learning-Oriented)
Audience: Backend developers setting up GoCardless for the first time
Time to complete: ~30 minutes
Prerequisite: Local Laragon environment running,.envfile accessible
Introduction
By the end of this tutorial, you will have a fully working GoCardless Direct Debit payment flow running in sandbox mode on your local machine. You will be able to:
- Open an invoice payment page and see the GoCardless Direct Debit button
- Click the button and be redirected to the GoCardless hosted mandate page
- Complete a sandbox bank authorisation and be redirected back to Waste Vantage
- Use the Artisan polling command to detect the payment confirmation and mark the invoice as paid
We will use API Polling throughout this tutorial (no public webhook URL required on localhost).
What we will NOT cover in this tutorial:
Production webhook setup, credit limit bypass logic, or Xero invoice syncing. Those are separate concerns.
Step 1 — Create a GoCardless Sandbox Account
-
Go to https://manage-sandbox.gocardless.com/ and sign up for a free account.
-
After logging in, navigate to Developers → Access Tokens.
-
Click Create token → give it a name like
waste-vantage-local→ select Read/Write → click Create. -
Copy the token. You will use it in the next step.
Keep this token safe — it grants full API access to your sandbox account.
Step 2 — Configure Your .env File
Open .env at the project root and add the following block. Place it after the Stripe configuration:
# GoCardless Direct Debit — Sandbox
GOCARDLESS_ACCESS_TOKEN=sandbox_your_token_here
GOCARDLESS_ENVIRONMENT=sandbox
GOCARDLESS_WEBHOOK_SECRET=
GOCARDLESS_ENABLE_POLLING=true
GOCARDLESS_POLL_INTERVAL_MINUTES=5
Replace sandbox_your_token_here with the token you copied in Step 1.
Leave GOCARDLESS_WEBHOOK_SECRET empty for now — we will use polling instead.
Now clear the config cache so the application picks up the new values:
php artisan config:clear
Step 3 — Verify the GoCardless SDK is Installed
The GoCardless PHP SDK should already be installed. Confirm this:
composer show gocardless/gocardless-pro
Expected output includes gocardless/gocardless-pro 7.x.x. If it is missing, run:
composer require gocardless/gocardless-pro
Step 4 — Seed the Payments Table
The GoCardless button on the payment page only appears when the payments table has a row with name = 'GoCardless' and status = 1.
Check if it already exists:
php artisan tinker
>>> App\Payment::where('name', 'GoCardless')->first()
If the result is null, create the row:
php artisan tinker
>>> App\Payment::create(['name' => 'GoCardless', 'status' => 1])
Type exit to leave Tinker.
Step 5 — Verify the Routes Are Registered
Confirm that the GoCardless routes are registered correctly:
php artisan route:list --name=gocardless
You should see exactly 5 routes:
POST payment/choose/gocardless
GET|HEAD payment/choose/gocardless/cancel
GET|HEAD payment/choose/gocardless/confirm
GET|HEAD payment/gocardless/status/{invoiceId}
POST payment/gocardless/webhook
If any routes are missing, check that the GoCardlessController and GoCardlessWebhookController classes exist and the route definitions are present in routes/web.php.
Step 6 — Start the Application and Queue Worker
Open two terminal windows:
Terminal 1 — Web server (Laragon is already running this)
Laragon handles the web server. No action needed.
Terminal 2 — Queue worker
php artisan queue:work --verbose
Leave this running. The ProcessGoCardlessPayment job needs this to execute.
Step 7 — Navigate to an Invoice Payment Page
-
Log in to the Waste Vantage frontend (e.g.,
http://waste-vantage.test/{slug}/order/continue-to-payment). -
Find an unpaid invoice. The URL will contain the invoice code in the query string.
-
Click the Pay Now button (or the button that opens the payment method modal).
-
You should now see the GoCardless Direct Debit option in the modal, below EFT.
If you do not see it:
- Confirm the
paymentstable row from Step 4 exists - Confirm the invoice status is
unpaid - Check the browser console for errors
- Confirm the
Step 8 — Complete the Sandbox Mandate Flow
-
Click the Pay Now button next to GoCardless Direct Debit.
-
The page will redirect to the GoCardless hosted payment page (sandbox URL:
https://pay-sandbox.gocardless.com/...). -
On the GoCardless page, enter the sandbox test bank account details:
- BSB:
000-000 - Account Number:
00000000 - Fill in any name/email
- BSB:
-
Proceed through the steps and confirm the mandate.
-
GoCardless will redirect you back to
/payment/choose/gocardless/confirm?invoice_id=X. -
You will then be redirected to the loading/processing page.
Step 9 — Confirm the Pending Payment Record Was Created
At this point, the order_payments table should have a new row for this payment. Let us verify:
php artisan tinker
>>> App\OrderPayment::where('transaction_type', 'GoCardless')->latest()->first()
You should see:
App\OrderPayment {
charge_id: "BRQ0017XXXXXXXXXXX",
transaction_id: null, ← Not yet set
transaction_type: "GoCardless",
transaction_status: "Not Processed",
uid_accounting_system: null, ← Not yet set
...
}
This is correct. The mandate details are not known until GoCardless fires the billing_requests.fulfilled event.
Step 10 — Run the Poller to Detect the Status Change
Since we are using API Polling (no webhook), we need to run the command manually to simulate what the scheduler does automatically:
php artisan gocardless:poll
Expected output:
GoCardless Poller starting…
Found 1 pending GoCardless payment(s) to check.
[→] OrderPayment #12 — dispatching [billing_requests.fulfilled]
+---------+------------+---------+--------+
| Polled | Dispatched | Skipped | Errors |
+---------+------------+---------+--------+
| 1 | 1 | 0 | 0 |
+---------+------------+---------+--------+
Switch to Terminal 2 (queue worker) and you should see:
[2026-06-09 08:30:00] Processing: App\Jobs\ProcessGoCardlessPayment
[2026-06-09 08:30:01] Processed: App\Jobs\ProcessGoCardlessPayment
Step 11 — Verify the Payment Record Was Updated
php artisan tinker
>>> App\OrderPayment::where('transaction_type', 'GoCardless')->latest()->first()
Now you should see the GoCardless IDs populated:
App\OrderPayment {
charge_id: "BRQ0017XXXXXXXXXXX", ← GoCardless Billing Request ID
transaction_id: null, ← GoCardless Payout ID (set later)
transaction_type: "GoCardless",
transaction_status: "Processing",
uid_accounting_system: "PM001XXXXXXXXXXX", ← GoCardless Payment ID
...
}
Step 12 — Simulate the Final Payment Confirmation
In sandbox, GoCardless payments move through statuses over time. To trigger the final paid_out status in sandbox:
- Go to your GoCardless Sandbox Dashboard
- Navigate to Payments → find the payment for the mandate you just created
- Use the Scenario simulator (gear icon) → select
Payment paid out→ Simulate
Now run the poller again:
php artisan gocardless:poll
Run the queue worker if it is not still running. Then verify the final state:
php artisan tinker
>>> App\OrderPayment::where('transaction_type', 'GoCardless')->latest()->first()->transaction_status
# Expected: "succeeded"
>>> App\OrderInvoice::find($invoiceId)->status
# Expected: "paid"
Checkpoint — What You Accomplished
Congratulations! You have successfully:
✅ Configured GoCardless sandbox credentials
✅ Seeded the payments table to show the button
✅ Completed a full mandate authorisation flow
✅ Verified a pending order_payments record was created
✅ Used the Artisan poller to detect the fulfilled billing request
✅ Simulated payment confirmation and verified the invoice was marked as paid
Next Steps
- Set up webhooks for production — see How to Register a GoCardless Webhook Endpoint in the How-To Guides.
- Understand the full architecture — read the Explanation document.
- Look up a specific method or route — consult the Reference document.