estratos/finkok-bundle
Laravel package integrating Finkok's CFDI web services for Mexican e-invoicing. It simplifies stamping, cancellation, and invoice management with configurable clients, clean service wrappers, reusable components, and straightforward configuration for reliable SAT-compliant workflows.
Framing note: the release is a Symfony 7.4 bundle, not a Laravel package. It ships a FinkokBundle registered via config/bundles.php and depends on Symfony components (http_client, logger). The Laravel fit must therefore be assessed as an adapter exercise, not a composer require and go.
Architecture fit. The bundle covers the two operations that matter for Mexican e-invoicing: timbrado (5 ops) and cancelación (6 ops) against Finkok as PAC. The design decisions are the right ones for a fiscal-critical path: immutable response DTOs instead of stdClass, typed error catalogs (24 stamping, 28 cancellation, 1000–1006 for accept_reject) with remediation hints, and business errors returned rather than thrown — correct, because Finkok routinely stamps successfully while reporting incidences. assertSuccess() gives teams an opt-in exception path. CredentialsProviderInterface keeping multi-issuer profiles (RFC/environment/CSD) out of bundle YAML is the single most important architectural choice: it maps cleanly onto tenant-scoped credential resolution from env, a secret manager, or the database.
Integration feasibility. Dropping ext-soap in favour of Symfony HttpClient is a genuine differentiator — it unblocks hardened PHP runtimes, container images without the extension, and shared hosting, and makes the transport testable with MockHttpClient. Envelope construction from the real WSDL plus local-name response parsing (prefix-agnostic) addresses the classic SOAP fragility. However: Laravel does not bind a http_client service (it ships Guzzle via Illuminate\Http\Client\Factory), so in a Laravel host the bundle will fall back to its own Symfony HttpClient instance. That means no shared proxy, TLS, timeout, or connection-pool configuration with the rest of the application unless explicitly wired. Verify this behavior before committing.
Technical risk. (1) Vendor maturity — estratos/finkok-bundle v1.0.0 is a first stable release with an apparent single-vendor origin; no license, support policy, release cadence, or maintainer count is stated in the notes. (2) Regulatory drift — SAT/CFDI rules change (4.0 revisions, payment complements, retention rules); a slow release cadence on a fiscal path is a compliance liability, not just a bug backlog. (3) Credential handling — CSD private keys are decrypted in-process (PEM + DES3 with the panel key); confirm keys are never logged or serialized into queue payloads. (4) Test evidence is strong but asymmetric — 219 offline tests / 880 assertions is credible; the 4 contract tests only prove envelopes deserialize (they assert 300 / invalid-credentials responses), not that a real stamp succeeds end-to-end.
Key questions. What is the license and stated support/versioning policy? Who maintains it and at what cadence? Does it cover CFDI 4.0 including payment complement (pagos 2.0) and retention workflows, or only base stamp/cancel? Is previewStampRequest() sufficient for our audit requirement of byte-level envelope retention? What is the dedup/idempotency contract on stamp if our queue retries after a timeout? Does the bundle expose the SAT acuse / acknowledgement XML for our 5-year retention obligation?
Stack fit. PHP ^8.2 and Symfony 7.4 components align with Laravel 11/12, which runs PHP 8.2+ and already pulls several Symfony components. There is no hard incompatibility, but there is no framework integration layer: no service provider, no publishable config, no facade. Treat this as adopting a library, not a package.
Migration path. Greenfield — no prior assessment, and the release notes confirm no published consumers exist for the two breaking changes (namespace Finkok\CfdiBundle → Estratos\FinkokBundle; finkok.profiles / finkok.default_profile removed from YAML in favour of an app-supplied CredentialsProviderInterface). If anyone prototyped on dev-main, those two changes are mandatory. Plan a build-vs-adopt checkpoint before committing: compare against a Laravel-native CFDI stack (phpcfdi/cfdiutils + a PAC SDK) so the decision is recorded, since the adapter cost is non-trivial.
Compatibility. Implement CredentialsProviderInterface as a Laravel binding resolving the active tenant's RFC/environment/CSD from config(), environment, or Vault — never from a checked-in file. Explicitly bind the bundle's HTTP client to a configured Symfony HttpClient (or a Guzzle-backed PSR-18 adapter) so proxy, timeout, and retry semantics are governed centrally. Reconcile the bundle's internal retries (transport errors + codes 205, 302, 708, 798) with Laravel queue retries to avoid compounding — cap one of them.
Sequencing. (1) DEMO spike with sandbox credentials: stamp one CFDI, confirm get_sat_status(), cancel, read all DTO fields. (2) Adapt the container wiring and credential provider; add a health check that calls previewStampRequest() without network I/O. (3) Staging soak with the 4 contract tests plus our own end-to-end stamp in Finkok DEMO. (4) Production with a single issuer behind a feature flag. (5) Multi-tenant rollout only after a full cancel-request/receipt cycle has been reconciled against the SAT portal manually.
Maintenance. Two ongoing streams: dependency maintenance and SAT schema maintenance. The latter is the real cost and is outside our control. Pin the version, subscribe to releases, and budget for a periodic review of docs/errores.md against official SAT publications. Budget also for owning the Laravel adapter ourselves — the bundle will not maintain it.
Support. Documentation is a genuine strength: README plus a dedicated docs/errores.md with full code catalogs, CSD encoding procedure, and DEMO behavior. It is in Spanish, which is appropriate for the domain but means the on-call runbook must be written in the team's working language. Community support is unknown; assume vendor-direct only until proven otherwise.
Scaling. Stamping is synchronous and PAC-bound; throughput is limited by Finkok, not by us. Model it as a queued job per CFDI with a hard timeout above the client timeout, and persist the resulting DTO (not the raw response object) for audit. Connection reuse depends on the HTTP client wiring flagged above.
Failure modes to design for explicitly. Business incidences returned as 2xx (300, 301, 307, 603, 705) — must be logged with ErrorCode and routed to a remediation queue, not a generic failure alert. Transient (205, 302, 708, 798) — retried automatically; safe because Finkok deduplicates by UUID, but we must not also retry at the job layer in a way that multiplies attempts. 799 — the 5-attempt cancellation ceiling is a hard stop: after the sixth attempt the CFDI can only be cancelled from the SAT portal, so the UI must surface remaining attempts. SOAP Fault, HTTP 502, and non-SOAP bodies are covered by tests but will still page someone. A 201 is not a cancellation — any dashboard showing "cancelled" must be driven by isCancelled() from get_sat_status(), never by request acceptance. Also enforce the 1 MB XML limit client-side; exceeding it can degrade subsequent stamps, which is a nasty blast-radius failure. Watch the 72-hour windows (stamping, and SAT taxpayer-list propagation after a new certificate) and the DEMO-only timings (1–5 min before cancel, 30 min to acceptance, 5 min auto-resolution) so QA expectations aren't set from DEMO behavior.
Ramp-up. The bundle itself is learnable in a day; the CFDI/SAT domain is not. Allocate real time for the team to internalize timbrado vs. cancellation semantics, EstatusCancelacion, CSD vs. panel certificate, and the acuse retention obligation. Expect 2–3 weeks for a developer new to Mexican e-invoicing to be productive, plus a domain reviewer (fiscal/accounting) on the acceptance criteria for any cancellation flow.
How can I help you explore Laravel packages today?