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.

CfdiCreator40 is the primary high-level API for generating CFDI version 4.0 XML documents in PHP. It acts as a “glue” object that brings together the CfdiUtils\Elements\Cfdi40\Comprobante element tree, the XML resolver, the XSLT cadena-de-origen builder, the certificate management, and the validation pipeline into a single, easy-to-use class. Rather than manually constructing XML nodes, you work with typed element helpers (addEmisor, addReceptor, addConcepto, etc.) and let CfdiCreator40 handle serialization, signing, and XSD validation. Under the hood it implements CertificadoPropertyInterface, XmlResolverPropertyInterface, and XsltBuilderPropertyInterface through the shared CfdiCreatorTrait.
CFDI 4.0 is the current SAT standard for electronic invoicing in Mexico. All new development should target CFDI 4.0. See CFDI 3.3 (Legacy) if you need to maintain an older integration.

Constructor

new CfdiCreator40(
    array $comprobanteAttributes = [],
    ?Certificado $certificado = null,
    ?XmlResolver $xmlResolver = null,
    ?XsltBuilderInterface $xsltBuilder = null,
)
comprobanteAttributes
array
default:"[]"
Key/value pairs that are set as attributes on the root cfdi:Comprobante element. Typical keys include Serie, Folio, Fecha, FormaPago, SubTotal, Total, TipoDeComprobante, etc. Fixed attributes such as Version, xmlns:cfdi, and xsi:schemaLocation are set automatically.
certificado
Certificado|null
default:"null"
An optional CfdiUtils\Certificado\Certificado instance. When provided, the constructor immediately calls putCertificado() to populate NoCertificado, Certificado, and (by default) the Emisor RFC and name from the certificate. Can be passed later via putCertificado().
xmlResolver
XmlResolver|null
default:"null"
Controls where XSD/XSLT resources are resolved and cached locally. Defaults to a new XmlResolver with the standard SAT remote resources. Pass null explicitly only if you later call setXmlResolver(null) — note that this disables cadena-de-origen generation and most validators.
xsltBuilder
XsltBuilderInterface|null
default:"null"
The engine used to apply the SAT XSLT stylesheet and produce the cadena de origen string. Defaults to CfdiUtils\CadenaOrigen\DOMBuilder. Override this if you need a custom XSLT processor.

Helper Methods

comprobante(): Comprobante

Returns the root CfdiUtils\Elements\Cfdi40\Comprobante element. Every other operation on the document structure goes through this object. All typed helper methods (addEmisor, addReceptor, addConcepto, etc.) are exposed on this element.

putCertificado(Certificado $certificado, bool $putEmisorRfcNombre = true): void

Sets the NoCertificado and Certificado attributes on the comprobante using the serial number and PEM content of the certificate. When $putEmisorRfcNombre is true (the default), it also writes the Rfc and Nombre attributes on the Emisor node — stripping the capital-regime suffix from the name when the RFC is 12 characters long and the document version is 4.0.
certificado
Certificado
A loaded CfdiUtils\Certificado\Certificado instance pointing to your CSD .cer file.
putEmisorRfcNombre
bool
default:"true"
When true, the Emisor’s Rfc and Nombre are taken from the certificate automatically. Set to false when you need to supply those values manually.

asXml(): string

Serializes the entire comprobante element tree to an XML string and returns it. Uses CfdiUtils\Nodes\XmlNodeUtils::nodeToXmlString() internally. Call this after addSello() to obtain the final signed XML.

saveXml(string $filename): bool

Writes the output of asXml() to a file. Returns true on success, false on failure (mirrors file_put_contents).
filename
string
Absolute or relative filesystem path where the XML file should be saved.

buildCadenaDeOrigen(): string

Applies the SAT XSLT stylesheet for CFDI 4.0 to the current XML, producing the cadena de origen string that is later hashed and signed. Throws \LogicException if no XSLT builder is configured.

buildSumasConceptos(int $precision = 2): SumasConceptos

Creates and returns a CfdiUtils\SumasConceptos\SumasConceptos object calculated from the Conceptos already added to the comprobante. Useful when you need to inspect the computed totals before writing them to the document.
precision
int
default:"2"
Number of decimal places used when rounding monetary amounts.

addSumasConceptos(?SumasConceptos $sumasConceptos = null, int $precision = 2): void

Computes the document totals and writes them back to the comprobante. When $sumasConceptos is null, it calls buildSumasConceptos($precision) internally. The values written include SubTotal, Total, Descuento, the Impuestos node at the comprobante level, and the TotaldeRetenciones/TotaldeTraslados fields of the local-tax complement when present.
sumasConceptos
SumasConceptos|null
default:"null"
A pre-built SumasConceptos object, or null to have the method calculate it automatically.
precision
int
default:"2"
Decimal precision forwarded to buildSumasConceptos() when $sumasConceptos is null.

addSello(string $key, string $passPhrase = ''): void

Signs the document. Internally it calls buildCadenaDeOrigen(), signs the resulting string with SHA-256 using the provided PEM private key, and stores the Base64-encoded result in the Sello attribute of the comprobante. If a certificate was previously set, it verifies that the key belongs to that certificate before signing.
key
string
The PEM private key content or a file:// URI pointing to a PEM-formatted .key file. Example: 'file:///path/to/key.pem'.
passPhrase
string
default:"''"
The passphrase protecting the private key. Leave empty if the key is unencrypted.

validate(): Asserts

Runs the full CFDI 4.0 validation suite (MultiValidatorFactory::newCreated40()), which includes XSD schema validation plus additional CfdiUtils business-rule validators. Returns a CfdiUtils\Validate\Asserts collection. Check $asserts->hasErrors() before proceeding to seal or store the document.

moveSatDefinitionsToComprobante(): void

Moves all xmlns:* namespace declarations and xsi:schemaLocation values that live on child nodes up to the root cfdi:Comprobante element. The SAT technical specification lists this as required, even though standard XML allows namespace declarations anywhere in the tree.
Call moveSatDefinitionsToComprobante() after adding all complements and before saving or transmitting the XML. Namespace locations do not participate in cadena-de-origen generation, so this step does not invalidate an existing Sello.

Creation Recipe

The typical CFDI 4.0 creation workflow follows these steps:
1

Instantiate CfdiCreator40 with your certificate

<?php
use CfdiUtils\CfdiCreator40;
use CfdiUtils\Certificado\Certificado;

$certificado = new Certificado('/path/to/certificate.cer');

$creator = new CfdiCreator40(
    [
        'Serie'          => 'A',
        'Folio'          => '000001',
        'Fecha'          => '2024-01-15T10:00:00',
        'FormaPago'      => '01', // efectivo
        'MetodoPago'     => 'PUE',
        'LugarExpedicion' => '06600',
        'TipoDeComprobante' => 'I', // Ingreso
        'Moneda'         => 'MXN',
    ],
    $certificado
);
2

Add Emisor and Receptor

$comprobante = $creator->comprobante();

// RFC and Nombre are automatically populated from the certificate
$comprobante->addEmisor([
    'RegimenFiscal' => '601', // General de Ley Personas Morales
]);

$comprobante->addReceptor([
    'Rfc'                   => 'XAXX010101000',
    'Nombre'                => 'PUBLICO EN GENERAL',
    'DomicilioFiscalReceptor' => '06600',
    'RegimenFiscalReceptor' => '616',
    'UsoCFDI'               => 'G03', // Gastos en general
]);
3

Add Conceptos with taxes

$comprobante->addConcepto([
    'ClaveProdServ'   => '84111506',
    'Cantidad'        => '1',
    'ClaveUnidad'     => 'ACT',
    'Descripcion'     => 'Servicio de consultoría',
    'ValorUnitario'   => '1000.00',
    'Importe'         => '1000.00',
    'ObjetoImp'       => '02', // Sí objeto de impuesto
])->addTraslado([
    'Base'       => '1000.00',
    'Impuesto'   => '002', // IVA
    'TipoFactor' => 'Tasa',
    'TasaOCuota' => '0.160000',
    'Importe'    => '160.00',
]);
4

Calculate and write totals

// Automatically sums Conceptos and writes SubTotal, Total, and Impuestos nodes
$creator->addSumasConceptos(null, 2);
5

Sign the document

$creator->addSello('file:///path/to/key.pem', 'my-passphrase');
6

Move SAT namespace definitions to root

$creator->moveSatDefinitionsToComprobante();
7

Validate and save

$asserts = $creator->validate();

if ($asserts->hasErrors()) {
    foreach ($asserts->errors() as $assert) {
        echo $assert->getCode() . ': ' . $assert->getStatus()->getMessage() . PHP_EOL;
    }
    return;
}

$creator->saveXml('/output/cfdi_A_000001.xml');
// Or get the XML as string:
$xmlString = $creator->asXml();

Full Working Example

The following self-contained example demonstrates a minimal but complete CFDI 4.0 income invoice:
<?php
declare(strict_types=1);

use CfdiUtils\CfdiCreator40;
use CfdiUtils\Certificado\Certificado;

// Load the CSD certificate
$certificado = new Certificado('/certs/my_cert.cer');

// Instantiate the creator with comprobante attributes
$creator = new CfdiCreator40(
    [
        'Serie'             => 'F',
        'Folio'             => '00042',
        'Fecha'             => '2024-06-01T09:30:00',
        'FormaPago'         => '03', // Transferencia electrónica
        'MetodoPago'        => 'PUE',
        'LugarExpedicion'   => '64000',
        'TipoDeComprobante' => 'I',
        'Moneda'            => 'MXN',
    ],
    $certificado
);

$comprobante = $creator->comprobante();

// Emisor — RFC and Nombre come from the certificate
$comprobante->addEmisor([
    'RegimenFiscal' => '601',
]);

// Receptor
$comprobante->addReceptor([
    'Rfc'                     => 'CACX7605101P8',
    'Nombre'                  => 'CARLOS CALDERA',
    'DomicilioFiscalReceptor' => '64000',
    'RegimenFiscalReceptor'   => '605',
    'UsoCFDI'                 => 'G03',
]);

// Concepto with IVA 16%
$comprobante->addConcepto([
    'ClaveProdServ' => '84111506',
    'Cantidad'      => '2',
    'ClaveUnidad'   => 'E48', // Unidad de servicio
    'Descripcion'   => 'Desarrollo de software',
    'ValorUnitario' => '5000.00',
    'Importe'       => '10000.00',
    'Descuento'     => '0.00',
    'ObjetoImp'     => '02',
])->addTraslado([
    'Base'       => '10000.00',
    'Impuesto'   => '002',
    'TipoFactor' => 'Tasa',
    'TasaOCuota' => '0.160000',
    'Importe'    => '1600.00',
]);

// Compute and write SubTotal, Total, and Impuestos
$creator->addSumasConceptos(null, 2);

// Sign using the PEM private key
$creator->addSello('file:///certs/my_key.pem', 'secret-passphrase');

// Move namespace declarations to root node (SAT requirement)
$creator->moveSatDefinitionsToComprobante();

// Validate before saving
$asserts = $creator->validate();
if ($asserts->hasErrors()) {
    print_r($asserts->errors());
    exit(1);
}

// Save to disk
$creator->saveXml('/output/cfdi_F_00042.xml');

echo 'CFDI saved successfully.' . PHP_EOL;

Comprobante Node Methods

Once you have the Comprobante element from $creator->comprobante(), the most commonly used helper methods are:
MethodDescription
addEmisor(array $attributes): EmisorCreates or updates the single Emisor node.
addReceptor(array $attributes): ReceptorCreates or updates the single Receptor node.
addConcepto(array $attributes): ConceptoAppends a new Concepto child inside Conceptos. Returns the Concepto element so you can chain .addTraslado() or .addRetencion() directly.
addComplemento(NodeInterface $node): ComprobanteInserts $node inside the Complemento wrapper element.
addCfdiRelacionados(array $attributes): CfdiRelacionadosAdds a CfdiRelacionados node for related documents.
addInformacionGlobal(array $attributes): InformacionGlobalAdds global invoice information (used for simplified invoices).
A typical Concepto→tax chain:
$comprobante->addConcepto([
    'ClaveProdServ' => '01010101',
    'Cantidad'      => '1',
    'ClaveUnidad'   => 'ACT',
    'Descripcion'   => 'Servicio',
    'ValorUnitario' => '500.00',
    'Importe'       => '500.00',
    'ObjetoImp'     => '02',
])
->addTraslado([
    'Base'       => '500.00',
    'Impuesto'   => '002',
    'TipoFactor' => 'Tasa',
    'TasaOCuota' => '0.160000',
    'Importe'    => '80.00',
]);

Using phpcfdi/credentials

The SAT delivers CSD files as a .cer certificate and a binary .key file. The companion library phpcfdi/credentials provides a convenient way to load both:
<?php
use CfdiUtils\CfdiCreator40;
use CfdiUtils\Certificado\Certificado;
use PhpCfdi\Credentials\Credential;

$csd = Credential::openFiles(
    '/certs/my_cert.cer',
    '/certs/my_key.key',
    'my-passphrase'
);

$creator = new CfdiCreator40();

$creator->putCertificado(
    new Certificado($csd->certificate()->pem()),
    true  // populate Emisor RFC and Nombre from the certificate
);

// ... build the document ...

$creator->addSello(
    $csd->privateKey()->pem(),
    $csd->privateKey()->passPhrase()
);
Use phpcfdi/credentials when working directly with binary .key files provided by the SAT. CfdiUtils’ own Certificado class only reads PEM-encoded .cer files.

XML Resource Resolver

The XmlResolver manages local caching of SAT XSD and XSLT files. The default resolver downloads resources from the SAT’s public servers and stores them in a local cache directory. You can customise this:
use CfdiUtils\XmlResolver\XmlResolver;

// Use a custom local cache path
$resolver = new XmlResolver('/tmp/sat-resources/');
$creator = new CfdiCreator40([], null, $resolver);

// Disable local caching (always fetch from SAT)
$resolver->setLocalPath('');

// Disable the resolver entirely (validation and cadena-de-origen will not work)
$creator->setXmlResolver(null);
If XmlResolver is disabled, buildCadenaDeOrigen() and addSello() will throw a \LogicException, and validators that require schema files will be skipped silently.

Build docs developers (and LLMs) love