Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/eclipxe13/CfdiUtils/llms.txt

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

The Mexican tax authority (SAT) defines a second category of electronic document alongside the familiar cfdi:Comprobante invoice: the CFDI de Retenciones e Información de Pagos. These documents report withholding taxes under the LIVA, LISR, and LIEPS laws and are structurally distinct from regular CFDI — they use a different XML namespace, different attribute names, their own catalogs, and SHA-1 rather than SHA-256 for digest calculation. Despite these differences, CfdiUtils provides a reader class — CfdiUtils\Retenciones\Retenciones — that mirrors the Cfdi class API, so the navigation patterns you already know apply here too.
The SAT does not provide a free tool for generating Retenciones documents (unlike regular CFDI). You will need a PAC’s services to stamp the Pre-CFDI and receive the final document including the Timbre Fiscal Digital.

Key Differences from Regular CFDI

CharacteristicRegular CFDICFDI Retenciones
XML namespacehttp://www.sat.gob.mx/cfd/3 or /cfd/4http://www.sat.gob.mx/esquemas/retencionpago/1 or /retencionpago/2
Namespace prefixcfdiretenciones
Root elementcfdi:Comprobanteretenciones:Retenciones
Certificate attributecfdi:Comprobante@Certificadoretenciones:Retenciones@Cert
Digest algorithmSHA-256SHA-1
Date formatISO-8601 without offsetISO-8601 with offset (e.g. -06:00)
Supported versions3.2, 3.3, 4.01.0, 2.0
Retenciones documents share several traits with regular CFDI: they support complements, may include an Addenda, and require a Timbre Fiscal Digital from a PAC.

The Retenciones Class

CfdiUtils\Retenciones\Retenciones uses the same XmlReaderTrait as the Cfdi class, meaning it exposes the same four primary getters and supports both formal node navigation and quick reading. It validates at construction time that:
  • The document implements the Retenciones namespace (/retencionpago/1 or /retencionpago/2) using the prefix retenciones.
  • The root element is retenciones:Retenciones.
The internal DOMDocument is cloned at construction time, so changes made to the original document after instantiation are not reflected in the reader — and vice versa.

Create a Retenciones Object

Use the static factory Retenciones::newFromString() to parse a raw XML string, or pass a DOMDocument directly to the constructor.
<?php
$xmlContent = file_get_contents('cfdi-retenciones-e-informacion-de-pagos.xml');
$reader     = \CfdiUtils\Retenciones\Retenciones::newFromString($xmlContent);
The object exposes the same four getters as Cfdi:
MethodReturn typeDescription
getVersion()stringDetected version string: "1.0" or "2.0", empty string if unrecognised
getDocument()DOMDocumentA clone of the internal DOMDocument
getSource()stringThe original XML source string
getNode()NodeInterfaceRoot node for the retenciones:Retenciones element
<?php
$xmlContent = file_get_contents('cfdi-retenciones-e-informacion-de-pagos.xml');
$reader     = \CfdiUtils\Retenciones\Retenciones::newFromString($xmlContent);

$reader->getVersion();   // (string) "2.0"
$reader->getDocument();  // clone of the DOMDocument
$reader->getSource();    // (string) original XML content
$node = $reader->getNode(); // NodeInterface for retenciones:Retenciones
The formal reader uses getNode() to obtain the root NodeInterface. Element names include their namespace prefix; attribute names use their exact casing from the XML.
<?php
$xmlContent       = file_get_contents('cfdi-retenciones-e-informacion-de-pagos.xml');
$reader           = \CfdiUtils\Retenciones\Retenciones::newFromString($xmlContent);

// Obtain the root node
$nodeRetenciones  = $reader->getNode();

// Read an attribute on a child element via searchAttribute
echo $nodeRetenciones->searchAttribute('retenciones:Emisor', 'RFCEmisor'); // e.g. EKU9003173C9

// Navigate to a child node and read its attributes
$emisor = $nodeRetenciones->searchNode('retenciones:Emisor');
if ($emisor !== null) {
    echo $emisor['RFCEmisor'];
    echo $emisor['NomDenRazSocE'];
}
Call getQuickReader() for the same case-insensitive, namespace-agnostic access available on regular CFDI:
<?php
$xmlContent    = file_get_contents('cfdi-retenciones-e-informacion-de-pagos.xml');
$reader        = \CfdiUtils\Retenciones\Retenciones::newFromString($xmlContent);

$qrRetenciones = $reader->getQuickReader();

// Attributes are case-insensitive
echo $qrRetenciones->emisor['NomDenRazSocE']; // Nombre del emisor

// Navigate into complements
$tfd = $qrRetenciones->complemento->timbreFiscalDigital;
if (isset($qrRetenciones->complemento->timbreFiscalDigital)) {
    echo $tfd['UUID'];
}
See the Quick Reader page for the full QuickReader API reference.

Version Detection Without Constructing the Object

If you need to detect the Retenciones version before constructing a full Retenciones object, use CfdiUtils\Retenciones\RetencionVersion:
MethodUse when you have…
getFromXmlString(string $xml)Raw XML string
getFromNode(NodeInterface $node)An existing NodeInterface
getFromDOMDocument(DOMDocument $doc)A loaded DOMDocument
getFromDOMElement(DOMElement $el)A DOMElement
<?php
$xmlContents = file_get_contents('/retencion/archivo-retencion.xml');

$retencionVersion = new \CfdiUtils\Retenciones\RetencionVersion();
$version          = $retencionVersion->getFromXmlString($xmlContents);

if ('' === $version) {
    echo 'Versión de retenciones no reconocida';
} else {
    echo 'Versión: ', $version; // "1.0" or "2.0"
}
Retenciones::newFromString() performs version detection internally. Only use RetencionVersion directly if you need the version without constructing the full reader object.

Version Differences (1.0 vs 2.0)

Versions 1.0 and 2.0 of the Retenciones schema have structural differences — many attribute names and their contents changed between versions. Use separate reader logic for each version rather than assuming a single path works for both.
If getVersion() returns an empty string, the document could not be matched to either supported namespace. This mirrors the behaviour of the Cfdi class when presented with an unrecognised version.

Build docs developers (and LLMs) love