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.

The VERI*FACTU regulation offers two valid compliance paths, and the mode your SIF adopts determines how billing records are handled after they are generated. In VERI*FACTU mode, records are submitted to the AEAT in real time as each invoice is issued. In Non-VERI*FACTU mode, records are kept locally with an electronic signature but are not sent unless the AEAT requests them. Both modes require a QR code on every invoice and the same chained-hash integrity mechanism.
Every registration record must be submitted to AEAT immediately after it is generated. Electronic signatures on individual records are not required because the act of transmission itself fulfils the integrity guarantee. The SIF must be able to reach the AEAT web service endpoint at the time of invoice issuance.

VERI*FACTU mode

In VERI*FACTU mode the SIF acts as a real-time relay: every new RegistrationRecord or CancellationRecord must be dispatched to the AEAT immediately after the invoice is issued, using AeatClient::send(). The AEAT responds with a Código Seguro de Verificación (CSV) that should be stored alongside the invoice. When your SIF operates exclusively in this mode, set $system->onlySupportsVerifactu = true on the ComputerSystem instance. This flag is included in every SOAP request and tells the AEAT that the system is incapable of operating offline.
use josemmo\Verifactu\Models\ComputerSystem;
use josemmo\Verifactu\Models\Records\FiscalIdentifier;
use josemmo\Verifactu\Models\Responses\ResponseStatus;
use josemmo\Verifactu\Services\AeatClient;

// Configure the SIF as VERI*FACTU-only
$system = new ComputerSystem();
$system->vendorName              = 'Acme Billing Solutions, S.L.';
$system->vendorNif               = 'B12345678';
$system->name                    = 'AcmeSIF';
$system->id                      = 'AC';
$system->version                 = '2.3.0';
$system->installationNumber      = 'INST-00042';
$system->onlySupportsVerifactu   = true;   // SIF cannot operate offline
$system->supportsMultipleTaxpayers = false;
$system->hasMultipleTaxpayers      = false;
$system->validate();

$taxpayer = new FiscalIdentifier('Acme Billing Solutions, S.L.', 'B12345678');
$client   = new AeatClient($system, $taxpayer);
$client->setCertificate(__DIR__ . '/cert.pfx', 'password');
$client->setProduction(true);

// Submit every new record immediately after invoice issuance
$aeatResponse = $client->send([$record])->wait();

if ($aeatResponse->status === ResponseStatus::Correct) {
    $csv = $aeatResponse->csv;
    echo "Record accepted. CSV: $csv\n";
} else {
    $errorDescription = $aeatResponse->items[0]->errorDescription;
    echo "Record rejected: $errorDescription\n";
}
ComputerSystem::$supportsMultipleTaxpayers must be true if the SIF is capable of managing billing for more than one taxpayer, regardless of the compliance mode. ComputerSystem::$hasMultipleTaxpayers must additionally be true at the moment a given record is generated if the SIF is actively handling more than one taxpayer at that time.

Non-VERI*FACTU mode

In Non-VERI*FACTU mode, the SIF retains all billing records locally. Each record must carry a valid electronic signature before it is stored. The SIF is also required to maintain an event log (libro de registro de eventos) that the AEAT can inspect. Set $system->onlySupportsVerifactu = false to declare that the SIF supports offline operation. Records are created and chained in exactly the same way as in VERI*FACTU mode — the difference is that AeatClient::send() is only called when responding to an AEAT requirement.
use josemmo\Verifactu\Models\ComputerSystem;

$system = new ComputerSystem();
$system->vendorName              = 'Acme Billing Solutions, S.L.';
$system->vendorNif               = 'B12345678';
$system->name                    = 'AcmeSIF';
$system->id                      = 'AC';
$system->version                 = '2.3.0';
$system->installationNumber      = 'INST-00042';
$system->onlySupportsVerifactu   = false;  // SIF can operate without real-time submission
$system->supportsMultipleTaxpayers = false;
$system->hasMultipleTaxpayers      = false;
$system->validate();

Switching modes

A SIF that has been operating in VERI*FACTU mode and needs to switch to Non-VERI*FACTU mode must notify the AEAT of the end date of voluntary remission. Use AeatClient::setVoluntaryRemissionEndDate() and then call send() with an empty array to dispatch the notification without attaching any billing records.
use DateTimeImmutable;

// Notify AEAT of the last date on which VERI*FACTU remission will occur
$client->setVoluntaryRemissionEndDate(new DateTimeImmutable('2026-12-31'));

// Send the notification immediately (no billing records attached)
$client->send([])->wait();
If the end of voluntary remission was affected by a technical incident at any point, pass true as the second argument:
// Indicate that a technical incident affected the voluntary remission period
$client->setVoluntaryRemissionEndDate(new DateTimeImmutable('2026-12-31'), true);
$client->send([])->wait();

Responding to AEAT requirements

In Non-VERI*FACTU mode the AEAT may issue a formal requirement (remisión por requerimiento) asking the SIF to submit a set of billing records. Use AeatClient::setRequirementReference() with the reference string provided by the AEAT. Because a requirement may involve many records, submissions are paginated — use the $isLastRequirementSubmission flag to signal the final batch.
1

Set the requirement reference

Call setRequirementReference() with the reference code included in the AEAT requirement document before each send() call.
// First page (or any intermediate page) of records
$client->setRequirementReference('REF00001ABDEAF1234');
$aeatResponse = $client->send($firstBatchOfRecords)->wait();
2

Send intermediate batches

Continue calling send() with successive batches. As long as more records remain, leave $isLastRequirementSubmission as false (the default).
$client->setRequirementReference('REF00001ABDEAF1234');
$aeatResponse = $client->send($nextBatchOfRecords)->wait();
3

Signal the last submission

On the final batch, pass true as the second argument to setRequirementReference(). This tells the AEAT there are no more records to submit for this requirement.
// Last page — no more records to submit for this requirement
$client->setRequirementReference('REF00001ABDEAF1234', true);
$aeatResponse = $client->send($lastBatchOfRecords)->wait();
4

Clear the requirement reference

After the requirement has been fully satisfied, clear the reference so that subsequent send() calls are not mistakenly tagged as requirement responses.
// Return to normal (non-requirement) operation
$client->setRequirementReference(null);

Build docs developers (and LLMs) love