Skip to main content

GoCardless Direct Debit — Module Documentation

Module: Payment Gateway Integration
SDK: gocardless/gocardless-pro ^7.2
Status: Active
Last Updated: 2026-07-07


Documentation Map

This module is documented across four files following the Diátaxis framework. Choose the document that matches what you are trying to do:

I want to…Go to
Learn the integration for the first time — step-by-step sandbox walkthroughTutorial
Do a specific task — configure an environment, add a button, debug webhooksHow-To Guides
Understand the design — why polling + webhooks, how idempotency works, BECSExplanation
Look up a class, method, route, column, config key, or event typeReference
Understand the admin "Check Status" button design and endpointAdmin Check Status

Quick-Start Checklist

For a developer who needs to get up and running fast:

  • Add credentials to .env (see Reference → Configuration)
  • Run php artisan config:clear
  • Seed payments table: INSERT INTO payments (name, status) VALUES ('GoCardless', 1)
  • Verify routes: php artisan route:list --name=gocardless → expect at least 5 customer routes plus 1 admin route (admin.order.payment.gocardless.checkStatus)
  • Start queue worker: php artisan queue:work
  • Test poll: php artisan gocardless:poll --dry-run

Files in This Module

app/
├── Console/Commands/
│ └── PollGoCardlessPayments.php # Artisan command — scheduled poller
├── Http/Controllers/
│ ├── Admin/
│ │ └── OrderController.php # gocardlessCheckStatus — admin on-demand check
│ ├── Frontend/
│ │ └── GoCardlessController.php # Checkout, confirm, cancel, status-check
│ └── GoCardlessWebhookController.php # Webhook receiver with HMAC validation
├── Jobs/
│ └── ProcessGoCardlessPayment.php # Handles all GoCardless event types
├── Services/
│ ├── GoCardlessService.php # GoCardless SDK wrapper
│ └── GoCardlessPollingService.php # Per-payment poll logic (shared by command + admin button)
└── Http/Middleware/
└── VerifyCsrfToken.php # Webhook URI exempted here

config/
└── services.php # gocardless: { access_token, environment, ... }

routes/
└── web.php # 5 GoCardless routes registered here

resources/views/orders/
└── confirm-to-payment.blade.php # GoCardless payment button injected here

tests/Feature/GoCardless/
├── GoCardlessCheckoutTest.php
├── GoCardlessWebhookTest.php
├── ProcessGoCardlessPaymentTest.php
├── PollGoCardlessPaymentsTest.php
├── AdminCheckStatusTest.php
└── GoCardlessCustomerUpdateTest.php

tests/Unit/Services/
└── GoCardlessPollingServiceTest.php

Payment Status Lifecycle (Summary)

checkout() billing_requests.fulfilled payments.confirmed payments.paid_out
│ │ │ │
Not Processed ────────▶ Processing ──────────▶ Pending ──────────▶ succeeded
│ │
order_payments row created order_invoices.status = 'paid'
charge_id = BRQ… date_payment = now()
transaction_id = null transaction_id = PO…
customers.gocardless_customer_id = CU…
customers.gocardless_mandate_id = MD…
order_payments.uid_accounting_system = PM…

Event Resolution Methods

GoCardless → Webhook (push) → GoCardlessWebhookController


ProcessGoCardlessPayment Job


order_payments + order_invoices

Scheduler (every 5 min) → PollGoCardlessPayments Command → GoCardlessService API calls


ProcessGoCardlessPayment Job (same job)

Customer loading page JS (every 8s) → GET /payment/gocardless/status/{id}


ProcessGoCardlessPayment Job (same job)

Admin "Check Status" button (one-shot) → POST /admin/order/payment/gocardless/check-status/{id}


GoCardlessPollingService → GoCardlessPollingService → ProcessGoCardlessPayment (sync)

All three paths converge on one job. Business logic is never duplicated.