Skip to main content

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.

HashGeneratorService computes the huella (fingerprint / hash) that AEAT requires on every invoice and cancellation record submitted via VERI*FACTU. The hash chains consecutive records together, creating an immutable audit trail. Namespace: eseperio\verifactu\services\HashGeneratorService
Under normal usage you never need to call HashGeneratorService directly. Both VerifactuService::registerInvoice() and VerifactuService::cancelInvoice() call it automatically before serialising and signing the XML. The generated hash is written back to $record->hash before the record is submitted.

Method

generate(InvoiceRecord $record): string

Generates the SHA-256 huella for the given record and returns it as an uppercase hexadecimal string.
use eseperio\verifactu\services\HashGeneratorService;

$hash = HashGeneratorService::generate($invoice);
// e.g. "3B4C1A2D9E..."  (64 uppercase hex characters)
The method accepts either an InvoiceSubmission or an InvoiceCancellation. It automatically selects the correct field set and concatenation format for each type. Passing any other InvoiceRecord subclass throws \InvalidArgumentException.

Concatenation format

The hash is computed as strtoupper(hash('sha256', $concatenatedString)) where $concatenatedString is built by joining key=value pairs with & separators. No trailing & is added.

InvoiceSubmission (RegistroAlta)

Fields are concatenated in this exact order:
IDEmisorFactura=<issuerNif>&NumSerieFactura=<seriesNumber>&FechaExpedicionFactura=<issueDate>&TipoFactura=<invoiceType>&CuotaTotal=<taxAmount>&ImporteTotal=<totalAmount>&Huella=<previousHash>&FechaHoraHusoGenRegistro=<recordTimestamp>
Field keySource propertyNotes
IDEmisorFacturaInvoiceId::$issuerNifTrimmed.
NumSerieFacturaInvoiceId::$seriesNumberTrimmed.
FechaExpedicionFacturaInvoiceId::$issueDateTrimmed. Format: DD-MM-YYYY.
TipoFacturaInvoiceSubmission::$invoiceTypeThe enum’s string value (e.g. "F1").
CuotaTotalInvoiceSubmission::$taxAmountNormalised to 2 decimal places, dot separator (e.g. "21.00").
ImporteTotalInvoiceSubmission::$totalAmountNormalised to 2 decimal places, dot separator.
HuellaPrevious record’s hash via Chaining::getPreviousInvoice()->hashEmpty string "" when firstRecord = 'S' or no chaining is set.
FechaHoraHusoGenRegistroInvoiceSubmission::$recordTimestampTrimmed. ISO 8601 with timezone offset, e.g. "2024-01-01T10:00:00+01:00".
Example concatenated string:
IDEmisorFactura=B12345678&NumSerieFactura=FACT-2024-001&FechaExpedicionFactura=01-01-2024&TipoFactura=F1&CuotaTotal=21.00&ImporteTotal=121.00&Huella=&FechaHoraHusoGenRegistro=2024-01-01T10:00:00+01:00

InvoiceCancellation (RegistroAnulacion)

Fields are concatenated in this exact order:
IDEmisorFacturaAnulada=<issuerNif>&NumSerieFacturaAnulada=<seriesNumber>&FechaExpedicionFacturaAnulada=<issueDate>&Huella=<previousHash>&FechaHoraHusoGenRegistro=<recordTimestamp>
Field keySource propertyNotes
IDEmisorFacturaAnuladaInvoiceId::$issuerNifTrimmed.
NumSerieFacturaAnuladaInvoiceId::$seriesNumberTrimmed.
FechaExpedicionFacturaAnuladaInvoiceId::$issueDateTrimmed.
HuellaPrevious record’s hash via Chaining::getPreviousInvoice()->hashEmpty string "" for first record.
FechaHoraHusoGenRegistroInvoiceCancellation::$recordTimestampTrimmed.

Decimal normalisation

All monetary amounts (taxAmount, totalAmount) are normalised before concatenation:
  • Converted to float.
  • Formatted with exactly 2 decimal places.
  • Dot (.) as decimal separator — no thousands separator.
// Internal normalisation (equivalent to):
number_format((float) $value, 2, '.', '');

// Examples:
"21" "21.00"
"121.5" "121.50"
"0" "0.00"
"-10.1" "-10.10"
Ensure taxAmount and totalAmount contain numeric values before calling generate(). Non-numeric strings will be cast to 0.0 by PHP and produce an incorrect hash.

When to call manually

Call HashGeneratorService::generate() directly when you need the hash before submitting — for example, to print the QR code on an invoice PDF at time of issue, or to pre-populate a database record with the hash for later chaining.
use eseperio\verifactu\services\HashGeneratorService;
use eseperio\verifactu\models\InvoiceSubmission;
use eseperio\verifactu\models\InvoiceId;
use eseperio\verifactu\models\Chaining;
use eseperio\verifactu\models\enums\InvoiceType;
use eseperio\verifactu\models\enums\HashType;

$id = new InvoiceId('B12345678', 'FACT-2024-042', '15-06-2024');

$invoice = new InvoiceSubmission();
$invoice->setInvoiceId($id);
$invoice->issuerName           = 'Mi Empresa, S.L.';
$invoice->invoiceType          = InvoiceType::STANDARD;
$invoice->taxAmount            = 42.00;
$invoice->totalAmount          = 242.00;
$invoice->recordTimestamp      = '2024-06-15T12:30:00+02:00';
$invoice->hashType             = HashType::SHA_256;
$invoice->setChaining(new Chaining(firstRecord: 'S'));

// Compute the hash manually
$hash = HashGeneratorService::generate($invoice);
$invoice->hash = $hash;

// $hash is now available for QR generation, database storage, or chaining
echo $hash; // "3B4C1A2D..."

// Later, submit — VerifactuService will re-generate and verify internally
// $response = VerifactuService::registerInvoice($invoice);

Chaining between records

The Huella field in the concatenation string is the hash of the immediately preceding record in the submission sequence. This is what creates the tamper-evident chain.
use eseperio\verifactu\models\Chaining;
use eseperio\verifactu\models\PreviousInvoice;

// First invoice in a chain
$firstInvoice->setChaining(new Chaining(firstRecord: 'S'));
// → Huella= (empty string)

// Subsequent invoice — reference the previous record's hash
$prev = new PreviousInvoice(
    issuerNif:    'B12345678',
    seriesNumber: 'FACT-2024-001',
    issueDate:    '01-01-2024',
    hash:         $firstInvoice->hash   // set after calling generate()
);
$chaining = new Chaining();
$chaining->setPreviousInvoice($prev);
$secondInvoice->setChaining($chaining);
// → Huella=<hash of first invoice>
Always generate and store the hash of each record before constructing the next one. Submitting records out of order will cause AEAT to reject the chain with an integrity error.

Build docs developers (and LLMs) love