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.

CancellationRecord (josemmo\Verifactu\Models\Records\CancellationRecord) represents a billing record that annuls a previously registered invoice in the AEAT VERI*FACTU system. Each instance maps to a <RegistroAnulacion> XML element. Unlike registration records, a cancellation always requires both previousInvoiceId and previousHash — there is no concept of a “first” cancellation without a chain predecessor.

Inherited properties (from Record)

All record types extend the abstract josemmo\Verifactu\Models\Records\Record base class. The fields below are inherited from it.
invoiceId
InvoiceIdentifier
required
Identifies the invoice being cancelled (<IDFactura>). All three sub-fields — issuerId (9-character NIF), invoiceNumber (max 60 chars), and issueDate — must be populated. Subject to NotBlank and Valid constraints.
previousInvoiceId
InvoiceIdentifier
required
The InvoiceIdentifier of the immediately preceding record in the billing chain (<Encadenamiento/RegistroAnterior>).
Unlike RegistrationRecord, this field is mandatory for every CancellationRecord. Passing null will cause a validation failure with the message “Previous invoice ID is required for all cancellation records”.
previousHash
string
required
SHA-256 hash of the preceding record (uppercase hex, exactly 64 characters, /^[0-9A-F]{64}$/), stored in <Encadenamiento/RegistroAnterior/Huella>.
Like previousInvoiceId, this field is mandatory for every CancellationRecord. Passing null will cause a validation failure.
hash
string
required
SHA-256 hash of this record’s canonical payload (uppercase hex, 64 characters), stored in <Huella>. Computed by calculateHash(). Subject to NotBlank and regex /^[0-9A-F]{64}$/; validation also confirms the value matches the calculated hash.
hashedAt
DateTimeImmutable
required
Timestamp at which the hash was generated, serialised as ISO 8601 in <FechaHoraHusoGenRegistro>. Subject to NotBlank.

CancellationRecord-specific properties

withoutPriorRecord
bool
default:"false"
Set to true when the original registration record does not exist in AEAT or the billing information system (SIF) — for example, when cancelling an invoice that was never successfully submitted (<SinRegistroPrevio>, exported as S). Must not be null.
isPriorRejection
bool
default:"false"
Set to true when resubmitting a cancellation record that was previously rejected by AEAT in its most recent submission (<RechazoPrevio>, exported as S). Must not be null.
Unlike RegistrationRecord::$isPriorRejection, this property is a plain boolnull is not a valid value for cancellation records.

calculateHash() algorithm

The cancellation hash input string uses the cancelled invoice’s identity fields and omits the invoice type and amount fields present in the registration hash. Key–value pairs are concatenated in order with & separators and no URL encoding:
IDEmisorFacturaAnulada={invoiceId.issuerId}
&NumSerieFacturaAnulada={invoiceId.invoiceNumber}
&FechaExpedicionFacturaAnulada={dd-mm-yyyy}
&Huella={previousHash}
&FechaHoraHusoGenRegistro={hashedAt ISO 8601}
The resulting UTF-8 string is hashed with SHA-256, then uppercased:
public function calculateHash(): string
{
    $payload  = 'IDEmisorFacturaAnulada='         . $this->invoiceId->issuerId;
    $payload .= '&NumSerieFacturaAnulada='        . $this->invoiceId->invoiceNumber;
    $payload .= '&FechaExpedicionFacturaAnulada=' . $this->invoiceId->issueDate->format('d-m-Y');
    $payload .= '&Huella='                        . ($this->previousHash ?? '');
    $payload .= '&FechaHoraHusoGenRegistro='      . $this->hashedAt->format('c');
    return strtoupper(hash('sha256', $payload));
}
The previousHash field is used in the hash payload even though it is always required — if somehow null (invalid state), the empty string is used as a fallback.

Static methods

CancellationRecord::fromXml(UXML $xml): self
static
Parses a <sum1:RegistroAnulacion> XML element and returns a fully populated CancellationRecord instance.Throws josemmo\Verifactu\Exceptions\ImportException if any required element is missing or contains an invalid value (e.g. unknown <RechazoPrevio> value).

Instance methods

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

Code example

use josemmo\Verifactu\Models\Records\CancellationRecord;
use josemmo\Verifactu\Models\Records\InvoiceIdentifier;

$cancellation = new CancellationRecord();

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

// Previous record in the chain — REQUIRED for all cancellations
$cancellation->previousInvoiceId = new InvoiceIdentifier(
    issuerId:      'B12345678',
    invoiceNumber: 'SERIES-000',
    issueDate:     new DateTimeImmutable('2024-06-10'),
);
$cancellation->previousHash = 'A3F2...'; // 64-char uppercase hex hash of previous record

// Optional flags
$cancellation->withoutPriorRecord = false; // true if original was never sent to AEAT
$cancellation->isPriorRejection   = false; // true if this cancellation was previously rejected

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

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

Build docs developers (and LLMs) love