stev/lista-firme-bundle
stev/lista-firme-bundle is a Symfony bundle for managing signature lists: collect and organize firmatari, handle petition-style workflows, and integrate ready-to-use entities and forms into your PHP application.
stevro/ListaFirme gives you a single, Laravel-friendly entry point for Romanian company lookups by CIF/CUI — VAT payer status, registration data, and the source-specific checkers (ANAF, ListaFirme, and now a fallback chain in front of them).
composer require stevro/lista-firme
php artisan vendor:publish --tag=listafirme-config
Package discovery registers the service provider; the only thing you normally need to touch is config/listafirme.php, where the active checker(s) and their credentials/timeouts live.
Where to look first, in this order:
config/listafirme.php — checker list, ordering, timeout, cache settings.CifChecker-style interface) — this is what you bind/fake in tests and what you implement for a custom source.First real use case: validating a customer at onboarding or checkout. Before you let a B2B customer register, or before you issue an invoice, resolve their CIF and assert "active + VAT payer". That single check catches most bad company data before it reaches your accounting export.
Inject the checker, don't new it up. Resolve through the container so config and fallbacks apply:
public function __construct(private CifChecker $checker) {}
For one-off checks in a controller or command, the facade (ListaFirme::check($cif)) is fine, but services/actions should take the contract.
Lean on the fallback chain rather than building your own. v4.1's headline change is that a CIF checker can now be backed by multiple fallbacks — if the primary source times out or returns an unusable payload, the next one in the configured list is tried. In practice this means: put the authoritative source first (ANAF), then a secondary, and stop writing try/catch + retry ladders around individual checkers. Treat the configured chain as your availability strategy and keep your own code single-path.
Cache aggressively, but scope it correctly. Company status changes on the order of days, not seconds. Cache per-CIF for ~12–24h in a tagged store (or with a company-status:{cif} key prefix) and bust deliberately. For a checkout path this turns a 1–3s network call into a Redis hit.
Form request rule. Wrap the lookup in a custom validation rule (CifValid) that normalizes, checks format, then optionally checks existence. Keep "format valid" and "company exists / is VAT payer" as two separate rules — you'll want the cheap one on every form and the expensive one only on submit.
Batch work belongs in a job. For customer imports or nightly re-validation of your supplier list, dispatch a job per chunk with ->onQueue('lookups') and $tries = 3, backoff() of a few minutes. Never loop 500 CIFs inside a web request.
Persist the raw response. Store the checker name, timestamp, and the raw payload alongside your normalized fields. When accounting asks why a company was flagged non-VAT six months ago, you need the evidence, not the current answer.
Audit which checker answered. v4.1 added fallbacks, so log checker_used on every call. A quiet shift from ANAF to a fallback source is the signal you want in your dashboards.
Testing. Bind a fake checker in the container (or use Http::fake() if the checker goes over HTTP). Don't let the suite hit live endpoints — they're slow and the upstreams are flaky, which will make your CI red for reasons unrelated to your code.
ANAF v8 is an upstream schema change, not a package-internal refactor. The checker upgrade tracks the ANAF web service version, and these versions have historically changed element names and the WSDL location. If you've got custom code reading the raw response, verify your field access after upgrading. If you only use the normalized output, you're fine — but re-run your lookup tests against a live sample once after deploy.
Fallbacks can hide outages. The whole point of v4.1 is graceful degradation, which also means your primary source can be down for days without anyone noticing. Alert on checker selection and on "all checkers failed", not just on exceptions. A fallback that returns stale or thinner data silently is worse than a hard failure in some flows.
ANAF is genuinely unreliable. Maintenance windows, timeouts, and 5xx responses are routine. Design assuming at least one failure per week: generous timeouts (but not infinite — 5–10s), queued retries, and a UI that says "we'll verify shortly" rather than blocking the user.
Normalize the CIF before you do anything. Strip RO prefix (case-insensitive), whitespace, dots and dashes, and leading zeros. Store the canonical numeric form; keep the display form separately. Half of "company not found" reports are formatting.
Watch negative caching. A brand-new company may not appear in a source for hours or days. Don't cache "not found" for 24h — use a short TTL (minutes) for misses and the long TTL only for positive, complete responses.
Timeouts and rate limits are your real constraint. Both checkers and fallbacks mean more outbound calls per lookup on the unhappy path. Set per-checker connect/read timeouts in config, cap total attempts, and respect the source's terms — scraping-backed sources will throttle or block a noisy client, and that block will look exactly like an outage.
Extension point: custom checkers. Implement the checker contract and register your class in the config list. That's the clean way to add an internal company registry or a paid provider as the last fallback, and it keeps the call sites unchanged.
Exceptions vs. results. Decide early whether a failed lookup throws or returns a null/result object with a status. Mixing the two leads to catch blocks that swallow "company is not VAT payer" as if it were a network error. The distinction between could not determine and determined to be inactive must survive to your business logic.
How can I help you explore Laravel packages today?