Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/josemmo/Verifactu-PHP/llms.txt

Use this file to discover all available pages before exploring further.

RegistrationRecord (josemmo\Verifactu\Models\Records\RegistrationRecord) is the primary object for reporting a new or corrected invoice to the AEAT VERI*FACTU system. Each instance corresponds to a single RegistroAlta XML element and must be chained to its predecessor in the billing sequence before being exported or submitted.

Inherited properties (from Record)

All record types extend the abstract josemmo\Verifactu\Models\Records\Record base class, which provides the chaining and hash fields common to both registrations and cancellations.
invoiceId
InvoiceIdentifier
required
Identifies the invoice this record refers to. Maps to the <IDFactura> XML element. Must pass NotBlank and Valid constraints — all three sub-fields (issuerId, invoiceNumber, issueDate) must be populated.
previousInvoiceId
?InvoiceIdentifier
The InvoiceIdentifier of the immediately preceding record in the billing chain (<Encadenamiento/RegistroAnterior>). Set to null when this is the first record in the chain; both previousInvoiceId and previousHash must be null together (or both set).
previousHash
?string
The SHA-256 hash (uppercase hex, exactly 64 characters, /^[0-9A-F]{64}$/) of the previous record in the chain (<Huella> inside <RegistroAnterior>). null when this is the first record. Must be provided together with previousInvoiceId.
hash
string
required
The SHA-256 hash of this record’s canonical payload (uppercase hex, exactly 64 characters). Computed by calculateHash() and stored in <Huella>. Subject to NotBlank and regex /^[0-9A-F]{64}$/ constraints; validation also confirms the value equals calculateHash().
hashedAt
DateTimeImmutable
required
The timestamp at which the hash was generated, serialised as an ISO 8601 string in <FechaHoraHusoGenRegistro>. Subject to NotBlank.

RegistrationRecord-specific properties

isCorrection
bool
default:"false"
Set to true to flag this record as a re-submission (<Subsanacion>) correcting a previously generated registration record. Exported as S / N. Must not be null.
isPriorRejection
?bool
default:"false"
Indicates that this is a resubmission of a registration record that was previously rejected by AEAT (<RechazoPrevio>). Valid values:
  • true — the prior record was submitted to AEAT and rejected (exported as S)
  • false — not a prior-rejection resubmission (exported as N, or omitted)
  • null — the rejected record was never sent to AEAT (exported as X)
Can only be non-false when isCorrection is also true.
issuerName
string
required
Legal name or trading name of the party obliged to issue the invoice (<NombreRazonEmisor>). Subject to NotBlank and Length(max: 120).
invoiceType
InvoiceType
required
The invoice classification (<TipoFactura>). Subject to NotBlank. See InvoiceType for all accepted values (F1, F2, F3, R1R5).
operationDate
?DateTimeImmutable
default:"null"
The calendar date on which the underlying commercial operation took place (<FechaOperacion>, formatted dd-mm-yyyy). The time component is ignored. Optional.
description
string
required
Free-text description of the invoice subject matter (<DescripcionOperacion>). Subject to NotBlank and Length(max: 500).
recipients
array
default:"[]"
Array of up to 1 000 invoice recipients (<Destinatarios>), each being either a FiscalIdentifier (Spanish NIF) or a ForeignFiscalIdentifier (foreign entity). Subject to Valid and Count(max: 1000).
Recipients are not allowed for Simplificada (F2) and R5 invoice types. All other invoice types require at least one recipient.
correctiveType
?CorrectiveType
default:"null"
Whether a corrective invoice works by full substitution or by differences (<TipoRectificativa>). Required when invoiceType is one of R1R5; must be null for all other types. See CorrectiveType.
correctedInvoices
InvoiceIdentifier[]
default:"[]"
List of original invoices being corrected (<FacturasRectificadas>). Used for R1R5 invoice types. Must be empty for non-corrective types.
correctedBaseAmount
?string
default:"null"
The corrected taxable base amount (<ImporteRectificacion/BaseRectificada>), formatted as a decimal string matching /^-?\d{1,12}\.\d{2}$/. Required when correctiveType is Substitution; must be null otherwise.
correctedTaxAmount
?string
default:"null"
The corrected tax quota (<ImporteRectificacion/CuotaRectificada>), formatted as a decimal string matching /^-?\d{1,12}\.\d{2}$/. Required when correctiveType is Substitution; must be null otherwise.
replacedInvoices
InvoiceIdentifier[]
default:"[]"
List of simplified invoices being replaced (<FacturasSustituidas>). Only applicable when invoiceType is Sustitutiva (F3). Must be empty for all other types.
breakdown
BreakdownDetails[]
required
Tax breakdown lines (<Desglose>), each describing a taxable base, rate, and quota. Subject to Valid, Count(min: 1), and Count(max: 12). At least one line is always required.
totalTaxAmount
string
required
Sum of all tax quotas across the breakdown lines (<CuotaTotal>), formatted as /^-?\d{1,12}\.\d{2}$/. Must equal the sum of taxAmount + surchargeAmount across all BreakdownDetails entries. Subject to NotBlank.
totalAmount
string
required
Total invoice amount including tax (<ImporteTotal>), formatted as /^-?\d{1,12}\.\d{2}$/. Validated against the sum of all base amounts plus totalTaxAmount (±€0.02 tolerance). Subject to NotBlank.

calculateHash() algorithm

The calculateHash(): string method computes the SHA-256 fingerprint that AEAT uses to verify record integrity. The input string is built by concatenating key–value pairs in the exact order shown below, separated by &, with no URL encoding applied:
IDEmisorFactura={issuerId}
&NumSerieFactura={invoiceNumber}
&FechaExpedicionFactura={dd-mm-yyyy}
&TipoFactura={invoiceType.value}
&CuotaTotal={totalTaxAmount}
&ImporteTotal={totalAmount}
&Huella={previousHash or empty string}
&FechaHoraHusoGenRegistro={hashedAt ISO 8601}
The resulting UTF-8 string is hashed with SHA-256, then uppercased. The method is defined as:
public function calculateHash(): string
{
    $payload  = 'IDEmisorFactura='         . $this->invoiceId->issuerId;
    $payload .= '&NumSerieFactura='        . $this->invoiceId->invoiceNumber;
    $payload .= '&FechaExpedicionFactura=' . $this->invoiceId->issueDate->format('d-m-Y');
    $payload .= '&TipoFactura='            . $this->invoiceType->value;
    $payload .= '&CuotaTotal='             . $this->totalTaxAmount;
    $payload .= '&ImporteTotal='           . $this->totalAmount;
    $payload .= '&Huella='                 . ($this->previousHash ?? '');
    $payload .= '&FechaHoraHusoGenRegistro=' . $this->hashedAt->format('c');
    return strtoupper(hash('sha256', $payload));
}
AEAT explicitly requires that values are not URL-encoded or escaped in any way before hashing — use raw field values exactly as stored.

Static methods

RegistrationRecord::fromXml(UXML $xml): self
static
Parses a <sum1:RegistroAlta> XML element and returns a fully populated RegistrationRecord instance. All sub-elements are mapped to their corresponding PHP properties.Throws josemmo\Verifactu\Exceptions\ImportException if any required element is missing or contains an invalid value.

Instance methods

MethodReturn typeDescription
export(UXML $xml, ComputerSystem $system): voidvoidSerialises this record into a <sum1:RegistroAlta> child element appended to $xml, including the IDVersion, chaining block, system info, timestamp, and hash.
calculateHash(): stringstringReturns the expected SHA-256 hash for this record’s canonical payload.
validate(): voidvoidRuns all Symfony Validator constraints; throws InvalidModelException on failure.

Code example

use josemmo\Verifactu\Models\Records\BreakdownDetails;
use josemmo\Verifactu\Models\Records\FiscalIdentifier;
use josemmo\Verifactu\Models\Records\InvoiceIdentifier;
use josemmo\Verifactu\Models\Records\InvoiceType;
use josemmo\Verifactu\Models\Records\OperationType;
use josemmo\Verifactu\Models\Records\RegimeType;
use josemmo\Verifactu\Models\Records\RegistrationRecord;
use josemmo\Verifactu\Models\Records\TaxType;

$record = new RegistrationRecord();

// Invoice identity
$record->invoiceId = new InvoiceIdentifier(
    issuerId:      'B12345678',
    invoiceNumber: 'SERIES-001',
    issueDate:     new DateTimeImmutable('2024-06-15'),
);

// Chain to the previous record (null for the very first record)
$record->previousInvoiceId = null;
$record->previousHash      = null;

// Issuer and type
$record->issuerName  = 'Empresa Ejemplo S.L.';
$record->invoiceType = InvoiceType::Factura;
$record->description = 'Servicios de consultoría — junio 2024';

// Recipient (required for F1)
$record->recipients[] = new FiscalIdentifier('Cliente S.A.', 'A98765432');

// Tax breakdown
$line = new BreakdownDetails();
$line->taxType       = TaxType::IVA;
$line->regimeType    = RegimeType::C01;
$line->operationType = OperationType::Subject;
$line->baseAmount    = '1000.00';
$line->taxRate       = '21.00';
$line->taxAmount     = '210.00';
$record->breakdown[] = $line;

// Totals
$record->totalTaxAmount = '210.00';
$record->totalAmount    = '1210.00';

// Hash (set after all fields are finalised)
$record->hashedAt = new DateTimeImmutable();
$record->hash     = $record->calculateHash();

// Validate before submission
$record->validate();

Build docs developers (and LLMs) love