Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Eseperio/verifactu-php/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Every invoice submission (or cancellation) to AEAT follows the same seven-step pipeline. TheVerifactu facade
orchestrates all of this for you through a single method call — but understanding what happens at each stage helps you
handle errors, debug issues, and extend the library when needed.
VerifactuService, which is called by the Verifactu facade methods
(registerInvoice(), cancelInvoice(), queryInvoices()).
Step-by-Step Walkthrough
Build the Invoice Model
You construct an See the Chaining & Hashing page for how to set up the
InvoiceSubmission (for a new invoice) or InvoiceCancellation (to cancel one) and populate
all required fields. This includes the invoice ID, tax breakdown, chaining data, system information, and timestamp.The library uses strongly-typed model classes that map directly to the AEAT XSD schema. All models extend a base
Model class that provides field-level validation.Chaining block and the
Certificates page for ComputerSystem setup.Pre-Hash Validation
Before generating the hash, If validation fails during
VerifactuService calls $invoice->validate() to check that all required fields are
present and correctly typed. Validation errors are returned as an associative array keyed by property name.registerInvoice(), an \InvalidArgumentException is thrown with the full
validation report. This happens before any network call, so no partial submissions occur.The
hash field is intentionally excluded from pre-hash validation — it has not been calculated yet at this
stage. A second validation pass runs after hash generation to verify the complete record.Generate the SHA-256 Hash (Huella)
The library calls See Chaining & Hashing for the full field concatenation rules and the
cancellation variant.
HashGeneratorService::generate($invoice) to compute the invoice’s SHA-256 hash (huella)
and assigns the result to $invoice->hash. You do not need to call this yourself — it happens automatically
inside registerInvoice() and cancelInvoice().Internally, HashGeneratorService concatenates the required fields in the exact order mandated by the AEAT
specification, then runs strtoupper(hash('sha256', $dataString)).For an InvoiceSubmission the concatenated string looks like:Serialize to XML
InvoiceSerializer converts the validated, hashed model into a DOMDocument that matches the official AEAT XSD
schema. For registrations, toInvoiceXml() produces the RegistroAlta block; for cancellations,
toCancellationXml() produces the RegistroAnulacion block. The record block is later wrapped in the
RegFactuSistemaFacturacion envelope (with a Cabecera containing the issuer NIF and name) by
wrapXmlWithRegFactuStructure().InvoiceSerializer::toInvoiceXml($invoice) directly if you need to inspect or log the raw XML outside of the normal submission flow.Digitally Sign the XML (XAdES Enveloped)
XmlSignerService signs the XML using the XAdES Enveloped standard (via the robrichards/xmlseclibs
library). The <Signature> element is appended inside the document root, embedding the signature within the
XML itself. For invoice registrations, the RegistroAlta block is signed before being wrapped in the
RegFactuSistemaFacturacion envelope; the wrapping follows after signing. For cancellations, the fully
wrapped XML is signed in one step.The signing process:- Loads your PFX/P12 certificate and extracts the X.509 certificate and private key via
CertificateManagerService. - Creates an
XMLSecurityDSigobject withEXC_C14Ncanonicalization andSHA256digest. - Signs the document with
RSA_SHA256. - Attaches the X.509 certificate in
<KeyInfo>with the subject name.
Transmit to AEAT via SOAP
SoapClientFactoryService builds a PHP SoapClient configured with the SOAP endpoint URL and a temporary
PEM file (created by CertificateManagerService::createSoapCompatiblePemTemp()) that combines the certificate
and private key for mutual TLS authentication.The signed XML is sent as a raw SoapVar with type XSD_ANYXML to avoid double-encoding, using the
RegFactuSistemaFacturacion SOAP action for registrations/cancellations or
ConsultaFactuSistemaFacturacion for queries.SoapFault is thrown (network error, TLS failure, or AEAT-level SOAP rejection), the exception is
caught, logged via error_log(), and re-thrown as a \RuntimeException with a descriptive message. See the
error codes table for common AEAT SOAP error codes.Parse the AEAT Response
ResponseParserService reads the raw SOAP response XML and converts it into a typed PHP model:InvoiceResponse— returned byregisterInvoice()andcancelInvoice()QueryResponse— returned byqueryInvoices()
src/dictionaries/ErrorRegistry.php. Each line in $response->lineResponses is an associative array; the
getErrors() method on InvoiceResponse returns a [code => description] map of all errored lines.For queries, iterate $result->foundRecords to access the returned invoice records:Service Responsibilities at a Glance
| Service | Responsibility |
|---|---|
Verifactu | Public facade — thin wrapper around VerifactuService |
VerifactuService | Orchestrates the full pipeline; holds SOAP client and config |
HashGeneratorService | Builds the AEAT-compliant field string and computes SHA-256 |
InvoiceSerializer | Converts models to DOMDocument matching AEAT XSDs |
XmlSignerService | XAdES Enveloped signing with robrichards/xmlseclibs |
CertificateManagerService | Loads PFX/P12 certificates; creates temporary PEM for SOAP |
SoapClientFactoryService | Instantiates a SoapClient with TLS certificate options |
ResponseParserService | Parses raw SOAP response XML into InvoiceResponse/QueryResponse |
QrGeneratorService | Generates AEAT-compliant QR codes in GD, Imagick, or SVG format |
Cancellation Workflow
The cancellation workflow is identical in structure, usingInvoiceCancellation instead of InvoiceSubmission.
The hash concatenation format differs slightly (uses IDEmisorFacturaAnulada / NumSerieFacturaAnulada /
FechaExpedicionFacturaAnulada field names). See Chaining & Hashing for the
exact format.
Exception Handling
Wrap all submission calls in atry/catch to handle the different failure modes: