Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Verifactu Php Laravel Package

josemmo/verifactu-php

Josemmo Verifactu PHP is a framework-agnostic library for Spain’s VERI*FACTU e-invoicing rules, helping generate QR codes and XML, sign invoices, validate records, and submit them to AEAT. It simplifies compliance for PHP and Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

josemmo/Verifactu-PHP is a pure-PHP implementation of Spain's Verifactu invoice-record submission flow (XML generation, hash chain, QR, SOAP to AEAT). v0.3.4 is still a 0.x release, so treat the API as moving and pin the exact version in composer.json.

Minimal setup:

  1. composer require josemmo/verifactu-php:0.3.4
  2. Confirm runtime requirements: PHP 8.1+, ext-soap, ext-openssl, ext-dom. AEAT communication is SOAP + signed XML, so a missing soap extension is the usual first failure.
  3. Load your certificado digital (PKCS#12 .pfx/.p12 plus passphrase) and the issuing NIF/NombreRazon into a config object. There is no "driver" abstraction here — the certificate is the identity of the emitter, not of the app.
  4. Point at the AEAT test environment first (entorno: test / preproducción endpoint). Never develop against production.
  5. Read the docs first. v0.3.4 added usage guides to the documentation (commits b3e98a9, 92f3809) — start there rather than at the source, because the guides cover the end-to-end flow (alta → huella → QR → envío → respuesta) that is tedious to reconstruct from class names alone.

First use case to implement: emit a single registro de alta for a simplified invoice (FacturaSimplificada), compute the huella (chained hash), render the QR, and submit it. Get that round-trip green in test before you touch the chain, cancellations, or requerimientos — the chain logic is where the real complexity lives and you want a known-good single record to compare against.

Implementation Patterns

Model the registro, don't pass invoices around. Convert your domain invoice into the package's factura/registro object in one dedicated mapper class. Every future change from AEAT (new fields, new record types) lands in that mapper instead of leaking into controllers and jobs.

Persist before you transmit. Store the generated XML, the huella, the timestamp, the environment, and the AEAT response (CSV, EstadoEnvio, EstadoRegistro, error list) in your own table before calling AEAT. AEAT is the source of truth for acceptance, but your database has to be the source of truth for what you sent and when. Audits (and the 4-year retention rule) depend on it.

Treat the hash chain as a database concern. The huella of invoice n depends on the previous record's huella for the same NIF + environment + record type. In practice:

  • Keep a verifactu_chain table keyed by (nif, entorno, tipo_registro) holding the last hash.
  • Wrap "read previous hash → build record → write new hash → dispatch" in a single DB transaction with lockForUpdate().
  • Never compute the chain in a queued job without a lock; two concurrent jobs will fork the chain and AEAT will reject the second record.

Queue the submissions. A SubmitInvoiceRecord job with retries and exponential backoff (backoff(), retryUntil()), plus a distinct terminal-failure job for records AEAT rejects on content. Distinguish transport failures (retry) from validation failures (do not retry — resubmitting a rejected record just burns rate limit).

Handle the response states explicitly. AEAT returns both an overall send status and a per-record status; a 200 SOAP response can still contain Incorrecto. Map both into your own enum and drive UI/status from that, not from "did the call throw".

Use the new requerimiento support as a first-class flow, not an exception path. v0.3.4 adds support for requerimientos de información (8eec20b1): AEAT can ask you to submit records you previously sent voluntarily or not at all. Pattern:

  • A separate command/job polls or accepts the requerimiento payload (from AEAT's notification system or a manual upload).
  • You resolve it to a set of registros and submit them with the requerimiento's identifier attached.
  • Persist the requerimiento → registros mapping so you can prove the response was complete.

Keep this in its own service. Mixing "normal day-to-day submission" and "respond to an inspection request" in the same method makes both untestable.

Distinguish voluntary submission from legal obligation. Verifactu records are normally sent voluntarily (remisión voluntaria) as you issue invoices. The new baja de remisión voluntaria (e6ac51a7) lets you cancel such a submission. Note the vocabulary in your code: Anulacion (record-level cancellation of an invoice), Baja (cancellation of a submission), Sustitucion (correcting a record). They are not interchangeable and using the wrong one is not a recoverable mistake at AEAT.

Use factories/fixtures for tests. Build registros from fixtures and assert on generated XML + huella against a small set of golden files. The chain algorithm is deterministic — pin its output in tests so a package upgrade that changes serialization shows up as a test failure rather than a production rejection.

Expose an Artisan command for reconciliation. verifactu:reconcile that pulls pending/unconfirmed records and re-queries their state. Cheap to write, saves hours when a queue worker dies mid-send.

Gotchas and Tips

  • Pre-1.0 churn. 0.3.4 changed behaviour around submission cancellation. Pin "josemmo/verifactu-php": "0.3.4" (not ^0.3) and read the changelog diff of each release; minor bumps have added record types and altered method signatures before. There is no deprecation policy yet to rely on.
  • Certificate handling bites. PKCS#12 with a passphrase, and PHP's OpenSSL needs the legacy provider / correct cipher for some older Spanish CA certs. Test with your actual production certificate in preproducción — a cert that loads in openssl CLI can still fail in PHP. Also: certificates expire. Add a check on boot or in a health command; an expired cert surfaces as an opaque SOAP fault.
  • Environment mixing is the classic outage. A test-environment NIF/chain pushed to production will be rejected, and a production record accidentally sent to test silently "succeeds" against a fake. Make the environment a required, non-defaulted constructor argument.
  • Clocks. AEAT validates timestamps and the FechaHoraHusoGenRegistro must be Europe/Madrid with correct DST offset. Don't let config('app.timezone') leak into record generation; compute explicitly and store UTC separately.
  • QR is part of the legal artifact. Generate it from the same data as the record and render it on the invoice PDF. If they diverge (e.g. QR generated from your ORM model, record from the mapper) you have a non-compliant invoice that looks fine.
  • Error-code handling is the whole game. AEAT returns coded errors; some mean "resend later", some mean "your XML is wrong", some mean "record already exists" (idempotent, treat as success). Log codes verbatim, map them in one place, and never pattern-match on the human-readable message — it is Spanish prose and changes.
  • Idempotency. Assign your own stable identifier per record before sending and check it before resubmit. Retrying a record that AEAT already accepted returns a duplicate error, which is fine — but retrying past a rejected record can break the chain.
  • The chain is per NIF, per environment, and includes cancellations. A Baja record is itself a chained record with its own huella, so your cancellation flow must go through the exact same chain-locking path as an Alta. Anywhere you write a record, use the same code path.
  • Baja voluntaria has a window. The new voluntary-cancellation support only applies to records you submitted voluntarily and within the permitted period; it is not a general "undo". Validate preconditions before building the record, and surface a clear domain error rather than an AEAT rejection.
  • Extension points are thin by design. This is a focused library, not a framework integration — there is no Laravel service provider. Expect to write your own provider, config file, and facades/wrappers; that's the intended shape, not a missing feature.
  • Where to debug first: the generated XML and the raw SOAP request/response. Enable the library's XML dumping (or capture the SOAP client's __getLastRequest()/__getLastResponse()) and log both at debug alongside the huella. Nine out of ten rejections are visible in the XML in under a minute; the tenth is the hash chain, which you debug by replaying fixtures through your own chain table.
  • Version-specific: when upgrading from 0.3.3 to 0.3.4, existing Alta/Baja flows are unaffected, but re-run your golden-file tests — the new requerimiento and baja-voluntaria types touch shared record-building code, and serialization regressions are silent until AEAT rejects them.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels