Skip to main content

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, .env file 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

  1. Go to https://manage-sandbox.gocardless.com/ and sign up for a free account.

  2. After logging in, navigate to Developers → Access Tokens.

  3. Click Create token → give it a name like waste-vantage-local → select Read/Write → click Create.

  4. 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

  1. Log in to the Waste Vantage frontend (e.g., http://waste-vantage.test/{slug}/order/continue-to-payment).

  2. Find an unpaid invoice. The URL will contain the invoice code in the query string.

  3. Click the Pay Now button (or the button that opens the payment method modal).

  4. You should now see the GoCardless Direct Debit option in the modal, below EFT.

    If you do not see it:

    • Confirm the payments table row from Step 4 exists
    • Confirm the invoice status is unpaid
    • Check the browser console for errors

Step 8 — Complete the Sandbox Mandate Flow

  1. Click the Pay Now button next to GoCardless Direct Debit.

  2. The page will redirect to the GoCardless hosted payment page (sandbox URL: https://pay-sandbox.gocardless.com/...).

  3. On the GoCardless page, enter the sandbox test bank account details:

    • BSB: 000-000
    • Account Number: 00000000
    • Fill in any name/email
  4. Proceed through the steps and confirm the mandate.

  5. GoCardless will redirect you back to /payment/choose/gocardless/confirm?invoice_id=X.

  6. 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:

  1. Go to your GoCardless Sandbox Dashboard
  2. Navigate to Payments → find the payment for the mandate you just created
  3. Use the Scenario simulator (gear icon) → select Payment paid outSimulate

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.