aichadigital / lara-verifactu
Laravel package for AEAT Verifactu compliance with agnostic architecture
Requires
- php: ^8.3
- ext-dom: *
- ext-libxml: *
- ext-openssl: *
- ext-soap: *
- bacon/bacon-qr-code: ^3.0
- illuminate/contracts: ^12.0||^13.0
- phpseclib/phpseclib: ^3.0
- robrichards/xmlseclibs: ^3.1
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.8
- laravel/pint: ^1.13
- nunomaduro/collision: ^8.0
- nunomaduro/phpinsights: ^2.13
- orchestra/testbench: ^10.0||^11.0
- pestphp/pest: ^4.1
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.3
- phpstan/phpstan: ^2.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- spatie/laravel-ray: ^1.35
- spatie/pest-plugin-test-time: ^2.1
This package is auto-updated.
Last update: 2026-06-25 04:54:52 UTC
README
Laravel package for VERI*FACTU (AEAT) compliance — Spain's invoicing records regulation (Real Decreto 1007/2023). It generates AEAT-conformant chained fingerprints (huella), validation QR codes and registration XML, and submits registration and cancellation records to the AEAT web service.
Status: 0.11.x — sandbox-validated beta.
Every artifact this package produces has been validated against the official AEAT specifications, and the full submission flow has been accepted live by the AEAT external testing environment (Pruebas Externas): real registration and cancellation records submitted with a representative certificate, answered
Correctowith CSV, including AEAT-side validation of the chained fingerprint. Pending before 1.0: production hardening and high-volume testing.
Conformance
- Fingerprint (huella) per the official hash spec v0.1.2 — the three official AEAT test vectors are part of the test suite.
- QR per the official QR spec v0.4.7: cotejo URL with exactly
nif,numserie,fecha,importe; error correction level M. - XML validated against the official
SuministroLR.xsdin CI; the official schemas and WSDL are bundled with the package (offline SOAP client, endpoint forced per environment and certificate type). - Endpoints from the official WSDL port bindings:
sellocertificates use thewww10/prewww10hosts;ciudadano/representanteusewww1/prewww1.
Regulatory deadlines
The adaptation deadline for obligated taxpayers was extended to 2027 by Real Decreto-ley 15/2025 (2 December 2025), which amended the final provision of RD 1007/2023:
- July 29, 2025: invoicing software / SIF must already meet the requirements.
- January 1, 2027: mandatory for Corporate Income Tax payers (was 2026).
- July 1, 2027: mandatory for the remaining obligated parties, including freelancers (was 2026).
Support matrix (v1.0 honest core)
The package implements an honest core: it emits only what it can produce
correctly and rejects fail-loud (a ValidationException) anything it cannot,
rather than sending XSD-valid XML the AEAT would reject — or accept con errores
(subsanable).
Core — supported and emitted
| Area | Supported in the core |
|---|---|
TipoFactura |
F1, F2, F3, R1, R5 |
Impuesto |
01 IVA, 02 IPSI, 03 IGIC |
ClaveRegimen |
01 (general regime) |
CalificacionOperacion |
S1; N2 (no sujeta por reglas de localización — intra-EU B2B services) |
Recipient (Destinatario) |
Spanish NIF; IDOtro for a foreign counterpart (NIF-IVA 02 without CodigoPais; 04 / 06 with a non-ES CodigoPais) |
OperacionExenta |
E1, E4, E6 (E2/E3 only with IPSI; rule 1199) |
| Recargo de equivalencia | 21% → 5,2 / 1,75 · 10% → 1,4 · 4% → 0,5 |
| Anulación | RegistroAnulacion + SinRegistroPrevio / RechazoPrevio + GeneradoPor / Generador |
| Rectificativas | R1 (S/I), F3 substitution of simplified invoices |
| Coherence guards (fail-loud) | ImporteTotal/CuotaTotal (±10 €), per-line CuotaRepercutida (±10 €), Macrodato (≥ 100 M), F2 ≤ 3.000 €, date validity |
Rejected fail-loud (post-1.0)
TipoFacturaR2 / R3 / R4 (Art. 80.3 / 80.4 / Resto)Impuesto05 (Otros)ClaveRegimen≠ 01 (special regimes)CalificacionOperacionS2 / N1 (inversión del sujeto pasivo / no sujeta art. 7, 14)OperacionExentaE2 / E3 with IVA or IGIC, and E5 (intra-community supply of goods)Generadoridentified byIDOtroin the anulación block (the recipientIDOtrois now core)- Recipient
IDType07 (No Censado — Spanish non-censused, a domestic edge) - Date-windowed surcharge rates (5 %, 0 %, 2 %, 7,5 % — rules 1165-1170 / 1277)
Out of scope
- TicketBAI (Basque Country) — a different system.
- SII del IGIC (Canary libros registro, remitted to the ATC) — see the note.
Notes
- IGIC: VeriFactu (RD 1007/2023) applies to Canary issuers, with references
to IVA read as IGIC; IGIC (
Impuesto=03) is emitted in the VeriFactu flow to the AEAT like any other tax. The separate SII del IGIC — the Canary libros-registro reporting to the Agencia Tributaria Canaria (ATC) — is a different obligation and is out of scope here. (AEAT ámbitos de aplicación, BOE RD 1007/2023, SII del IGIC — ATC) - XSD vs official lists: the XSD
OperacionExentaTypeaccepts E7/E8, but AEAT list L10 documents only E1-E6 (the enum models E1-E6). The XSD admitsClaveRegimen21 (IGIC simplified), absent from list L8A. Neither is emitted by the core.
Requirements
- PHP 8.3+
- Laravel 12.x or 13.x
- Extensions:
soap,openssl,dom,libxml - A digital certificate (PKCS#12) for AEAT submission:
representante,selloorciudadano
Installation
composer require aichadigital/lara-verifactu php artisan verifactu:install
Configure your environment:
# Environment: production | sandbox (AEAT Pruebas Externas) VERIFACTU_ENVIRONMENT=sandbox # PKCS#12 certificate (keep it OUTSIDE the project tree) VERIFACTU_CERT_PATH=/secure/path/company-representative.p12 VERIFACTU_CERT_PASSWORD=secret VERIFACTU_CERT_TYPE=representante # ciudadano | representante | sello # Issuer (obligado a expedir factura) — must match the AEAT census VERIFACTU_COMPANY_TAX_ID=B00000000 VERIFACTU_COMPANY_NAME="YOUR COMPANY SL"
Note: macOS Keychain exports
.p12files with legacy RC2-40 encryption that OpenSSL 3.x rejects (misleadingly reported as a wrong password). Re-package with modern encryption:/usr/bin/openssl pkcs12 -in legacy.p12 -nodes -out tmp.pem \ && openssl pkcs12 -export -in tmp.pem -out modern.p12 && rm tmp.pem
Verify your setup against AEAT:
php artisan verifactu:test-connection # full check incl. mutual-TLS probe php artisan verifactu:test-connection --cert-info # certificate details only
Usage
Facade
use AichaDigital\LaraVerifactu\Facades\Verifactu; // Register an invoice (creates the chained registry record, the QR and // the XML; submits to AEAT unless told otherwise) $registry = Verifactu::register($invoice); $registry = Verifactu::register($invoice, submitToAeat: false); // Cancel an invoice (RegistroAnulacion, chained like any other record) $registry = Verifactu::cancel($invoice); // Latest registry of an invoice (null if never registered) $registry = Verifactu::status($invoice); // AEAT cotejo QR for an invoice (SVG or PNG per config) $qr = Verifactu::qr($invoice); // Verify the integrity of the whole fingerprint chain ['valid' => $valid, 'errors' => $errors] = Verifactu::validateChain();
Testing your integration
use AichaDigital\LaraVerifactu\Facades\Verifactu; Verifactu::fake(); // ... code under test ... Verifactu::assertRegistered($invoice); Verifactu::assertCancelled($invoice); Verifactu::assertNotSent($otherInvoice);
Artisan commands
php artisan verifactu:test-connection # certificate + mutual-TLS check php artisan verifactu:register {id} # register an invoice php artisan verifactu:retry-failed # retry failed submissions php artisan verifactu:verify-blockchain # verify the fingerprint chain php artisan verifactu:status # system status
Custom invoice models
Any model implementing
AichaDigital\LaraVerifactu\Contracts\InvoiceContract can be registered —
the bundled Invoice model (native mode) is optional. See
config/verifactu.php for the model bindings.
Architecture notes
- VERI*FACTU mode does not sign records: the chained fingerprint
replaces the signature. XAdES signing is available behind
verifactu.signing.enabled(defaultfalse) for the non-Verifactu modality. - Cancellations are links of the fingerprint chain in their own right
(
registry_type), keeping the chain verifiable end to end. - Submissions are sequential by design (dedicated
fiscal_verificationqueue with a unique lock) to preserve chain ordering. AceptadoConErroresresponses map to success: AEAT registered the record, so it must not be resubmitted; the error details are persisted.- Rectifications:
TipoRectificativaderives fromgetRectificationType()(Ssubstitution /Iincremental). A substitution (S) requires the substituted amounts viagetRectificationAmounts()(native mode readsmetadata['rectification_amounts']), emitted asImporteRectificacion; a missing block raisesValidationException. - Substitution of simplified invoices (
F3): invoice typeInvoiceTypeEnum::SUBSTITUTEemitsFacturasSustituidas(the substituted simplified invoices viagetSubstitutedInvoices(); native mode readsmetadata['substituted_invoices']). An F3 requires a recipient (AEAT rule 1189) and at least one substituted invoice, otherwise it raisesValidationException.
Sandbox validation
The suite includes tests/Feature/RealSandboxSubmissionTest.php, which
performs a real registration and cancellation against AEAT Pruebas
Externas. It is skipped automatically unless a real certificate is
configured through the VERIFACTU_* environment variables — CI never
contacts AEAT.
set -a; source .env; set +a vendor/bin/pest tests/Feature/RealSandboxSubmissionTest.php
Testing & quality
composer test # Pest composer phpstan # PHPStan level 8 (no baseline debt on new code) composer format # Laravel Pint composer quality # all of the above + coverage
Security
If you discover a security vulnerability, please email
security@aichadigital.com instead of using the issue tracker. Never
commit certificates or credentials; keep your .p12 outside the project
tree with restrictive permissions.
Logging & privacy
Third-party fiscal PII never reaches the logs unredacted. The SOAP
request/response payload (NIF, names, amounts, dates, signature) is not
logged by default; enable it with VERIFACTU_LOG_SOAP_PAYLOAD=true and even
then it is always passed through a redactor — there is no raw-payload mode.
Full stack traces and AEAT error text are gated behind VERIFACTU_LOG_DEBUG
and a redactor respectively.
What the logs do carry are the issuer's own operational identifiers —
invoice_number, invoice_serie, registry_number, invoice_id and the AEAT
csv (cotejo) code. These are the issuer's references to its own operations,
not third-party personal data, and they are kept on purpose for support and
operational audit ("what happened to invoice FA-2026-001 / CSV A-xxx?"). They
are controlled by the log level (VERIFACTU_LOG_LEVEL, default info):
- Intermediate steps and idempotency skips are logged at
debug(off by default); real transitions (submitted to AEAT, registered) atinfo; failures atwarning/error/critical. - For restricted environments (a shared SIEM, external support without strict
access/retention controls) set
VERIFACTU_LOG_LEVEL=warningso only failures are persisted. Field-level redaction of these operational ids is intentionally not done in v1.0; it would only be warranted if your logs leave to an uncontrolled third party.
Changelog
See CHANGELOG.md.
License
The MIT License (MIT). See LICENSE.md.
Credits
- Aicha Digital
- Built against the official AEAT VERI*FACTU specifications
- Package skeleton by Spatie
Made with ❤️ by Aicha Digital