klinche/wells-fargo-ach-bundle
Symfony bundle for integrating Wells Fargo ACH payments, helping you generate and manage ACH files and payment batches within your PHP application.
The first thing to internalize: this is not a Laravel package. klinche/wells-fargo-ach-bundle is a Symfony bundle — it ships a Bundle class and a DI Extension, registers services via XML/YAML config, and depends on symfony/framework-bundle from the Symfony 2.x era. There is no service provider, no facade, no publishable config. You cannot composer require it into a modern Laravel app and start resolving services; the constraints will almost certainly fail against PHP 8.x and Symfony 6/7 components.
So "getting started" means deciding how you're going to consume it:
DependencyInjection/*Extension.php) — this tells you the exact config keys (customer ID, company ID, originator credentials, endpoint URL, WSDL path) and the service IDs it wires.SoapClient instantiation; the WSDL URL there is the real contract.app/Services/WellsFargoAch/, drop the DI extension, and bind it yourself in a service provider.First use case to target: submit a NACHA-formatted ACH batch and retrieve its status. Everything else (returns, prenotes, reporting) hangs off that one round trip. Find the single method that takes a file/batch and returns a reference ID — that's your integration point.
Bind it as a singleton with config from config/services.php.
// config/services.php
'wells_fargo_ach' => [
'endpoint' => env('WF_ACH_ENDPOINT'), // separate test vs. production
'customer_id' => env('WF_ACH_CUSTOMER_ID'),
'company_id' => env('WF_ACH_COMPANY_ID'),
'username' => env('WF_ACH_USERNAME'),
'password' => env('WF_ACH_PASSWORD'),
],
Wrap the vendored client behind your own interface (AchGateway) so the rest of the app never touches SOAP types. This is the single most valuable refactor you can do with a bundle this old.
Never submit inline in a request. ACH submission is a multi-second SOAP call with a hard cut-off window; put it in a queued job with a generous timeout, then poll status in a separate scheduled job.
Persist every payload. Store the request XML, response XML, your internal batch ID, and the gateway's reference ID in an ach_submissions table. ACH is a regulated, auditable money-movement rail — you will be asked "what exactly did we send on the 14th" and the answer must come from your database, not the vendor's.
Idempotency by construction. Generate a unique file/batch ID before submission (Str::uuid() stored on the row) and check for an existing submission with that ID before calling out. Retrying a submit after a timeout can double-pay.
Adapter pattern for domain → ACH mapping. Keep a class like DisbursementToAchEntry that turns your payments rows into entries with the right SEC code (PPD for consumer, CCD for B2B, WEB for web-authorized debits). When you eventually migrate off this bundle — and you will — only the adapter and the gateway interface need to change.
Reconciliation flow: submit → store reference → poll status → map return codes (R01 insufficient funds, R02 account closed, etc.) into a lookup table → update your payment records. Build the return-code table early; it's the part you'll touch most in production.
Testing: write a FakeAchGateway implementing your interface, returning canned SOAP responses. Keep a handful of real (redacted) response fixtures for snapshot tests of your parsing. You should never need the live Wells Fargo test environment to run CI.
array() syntax, possibly SoapClient without SSL options. Budget time for a modernization pass if you copy classes instead of just reading them.['trace' => 1, 'exceptions' => true] and log __getLastRequest() / __getLastResponse(). Without trace, failures give you a useless Could not connect to host with nothing to act on.soap.wsdl_cache_enabled=1 writes to a temp dir that may not exist or may be stale inside containers. Set soap.wsdl_cache_enabled=0 in non-production, or point the cache dir at a writable path..env / a secrets manager and out of any committed file.$this->app->bind(AchGateway::class, ...) — the container is your swap point either way.How can I help you explore Laravel packages today?