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 walkthrough | Tutorial |
| Do a specific task — configure an environment, add a button, debug webhooks | How-To Guides |
| Understand the design — why polling + webhooks, how idempotency works, BECS | Explanation |
| Look up a class, method, route, column, config key, or event type | Reference |
| Understand the admin "Check Status" button design and endpoint | Admin 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
paymentstable: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.