SAT-CFDI Python Library

repository·main·Indexed 41 days ago

https://github.com/sat-cfdi/python-satcfdi

An open-source Python library for generating and processing Mexican SAT CFDI (Comprobante Fiscal Digital por Internet) documents, supporting versions 3.2, 3.3, and 4.0. It provides tools to create invoices, payroll, and payment documents, sign them using digital certificates, and export them to XML, JSON, HTML, and PDF formats.

Tokens
16.6K
Snippets
43
Records
43
Agent score
51%

What's inside SAT-CFDI

  1. Perform Mass Download of Emitted CFDIs

    main

    To request a mass download of CFDIs you have issued (emitted), use sat_service.recover_comprobante_emitted_request().

    Required Parameters:

    • fecha_inicial: Start date (datetime.date).
    • fecha_final: End date (datetime.date).
    • rfc_emisor: The RFC of the issuer (can be retrieved via sat_service.signer.rfc).
    • tipo_solicitud: Use TipoDescargaMasivaTerceros.CFDI.
    • estado_comprobante: Use EstadoComprobante.VIGENTE (mandatory for CFDI downloads).

    The response contains an IdSolicitud which must be stored to check the status later.

    from datetime import date
    from satcfdi.pacs.sat import SAT, TipoDescargaMasivaTerceros, EstadoComprobante
    
    sat_service = SAT(signer=signer)
    
    response = sat_service.recover_comprobante_emitted_request(
        fecha_inicial=date(2020, 1, 1),
        fecha_final=date(2020, 12, 1),
        rfc_emisor=sat_service.signer.rfc,
        tipo_solicitud=TipoDescargaMasivaTerceros.CFDI,
        estado_comprobante=EstadoComprobante.VIGENTE,
    )
    
    id_solicitud = response['IdSolicitud']
  2. Load a FIEL/CSD using Signer.load

    main

    To interact with SAT services, you must first authenticate using a digital certificate (FIEL or CSD). Use Signer.load() to create a Signer instance by providing the certificate file content, the private key file content, and the password as strings or bytes.

    from satcfdi.models import Signer
    
    signer = Signer.load(
        certificate=open('path/to/cert.cer', 'rb').read(),
        key=open('path/to/key.key', 'rb').read(),
        password=open('path/to/password.txt', 'r').read()
    )
  3. Create a CFDI 4.0 Payroll (Nomina) document

    main

    Payroll documents are created using the cfdi40.Comprobante.nomina() factory method. This method requires a complemento_nomina object (from nomina12.Nomina) which contains specific employee data, perceptions, and deductions.

    Key steps:

    1. Use cfdi40.Comprobante.nomina(...).
    2. Provide complemento_nomina with percepciones and deducciones.
    3. Sign and process as usual.
    from datetime import date
    from decimal import Decimal
    from satcfdi.models import Signer
    from satcfdi.create.cfd import cfdi40, nomina12
    
    # Load signing certificate
    signer = Signer.load(
        certificate=open('csd/xiqb891116qe4_csd.cer', 'rb').read(),
        key=open('csd/xiqb891116qe4_csd.key', 'rb').read(),
        password=open('csd/xiqb891116qe4_csd.txt', 'r').read()
    )
    
    # create Comprobante
    invoice = cfdi40.Comprobante.nomina(
        emisor=cfdi40.Emisor(
            rfc=signer.rfc,
            nombre=signer.legal_name,
            regimen_fiscal="601"
        ),
        receptor=cfdi40.Receptor(
            rfc='KIJ0906199R1',
            nombre='KIJ, S.A DE C.V.',
            uso_cfdi='G03',
            domicilio_fiscal_receptor="59820",
            regimen_fiscal_receptor="601"
        ),
        lugar_expedicion="56820",
        complemento_nomina=nomina12.Nomina(
            emisor=nomina12.Emisor(
                registro_patronal='Z1234567890'
            ),
            receptor=nomina12.Receptor(
                cuenta_bancaria='0001000200030004',
                curp='XIQB891116MCHZRL72',
                clave_ent_fed='MOR',
                num_empleado='12345678',
                periodicidad_pago='04',
                tipo_contrato='01',
                tipo_regimen='02'
            ),
            percepciones=nomina12.Percepciones(
                percepcion=nomina12.Percepcion(
                    tipo_percepcion='001',
                    clave='001',
                    concepto='SUELDO',
                    importe_gravado=Decimal('1200'),
                    importe_exento=Decimal('400')
                )
            ),
            deducciones=nomina12.Deducciones(
                deduccion=nomina12.Deduccion(
                    tipo_deduccion='002',
                    clave='300',
                    concepto='ISR A CARGO',
                    importe=Decimal('1234.73')
                )
            ),
            tipo_nomina='O',
            fecha_pago=date(2020, 1, 30),
            fecha_final_pago=date(2020, 1, 31),
            fecha_inicial_pago=date(2020, 1, 16),
            num_dias_pagados=Decimal('16.000')
        ),
        serie="A",
        folio="123456"
    )
    invoice.sign(signer)
    invoice = invoice.process()
  4. Install SAT-CFDI

    main

    You can install the library from PyPI using pip, or install directly from the source repository.

    From PyPI:

    python -m pip install satcfdi

    From Source:

    git clone https://github.com/SAT-CFDI/python-satcfdi
    cd python-satcfdi
    python -m pip install .
    python -m pip install satcfdi
  5. Create a CFDI 4.0 Invoice

    main

    To create a standard CFDI 4.0 invoice, instantiate cfdi40.Comprobante with the required emisor (issuer), receptor (receiver), and conceptos (concepts/items). After instantiation, you must call .sign(signer) using a loaded Signer object, followed by .process() to finalize the document.

    Key components include:

    • emisor: Uses signer.rfc and signer.legal_name for convenience.
    • receptor: Requires rfc, nombre, uso_cfdi, domicilio_fiscal_receptor, and regimen_fiscal_receptor.
    • conceptos: A list of cfdi40.Concepto objects containing product/service keys, quantities, and impuestos (taxes).
    from decimal import Decimal
    from satcfdi.models import Signer
    from satcfdi.create.cfd import cfdi40
    from satcfdi.create.cfd.catalogos import RegimenFiscal, UsoCFDI, MetodoPago, Impuesto, TipoFactor
    
    # Load signing certificate
    signer = Signer.load(
        certificate=open('csd/xiqb891116qe4_csd.cer', 'rb').read(),
        key=open('csd/xiqb891116qe4_csd.key', 'rb').read(),
        password=open('csd/xiqb891116qe4_csd.txt', 'r').read()
    )
    
    # create Comprobante
    invoice = cfdi40.Comprobante(
        emisor=cfdi40.Emisor(
            rfc=signer.rfc,
            nombre=signer.legal_name,
            regimen_fiscal=RegimenFiscal.GENERAL_DE_LEY_PERSONAS_MORALES
        ),
        lugar_expedicion="56820",
        receptor=cfdi40.Receptor(
            rfc='KIJ0906199R1',
            nombre='KIJ, S.A DE C.V.',
            uso_cfdi=UsoCFDI.GASTOS_EN_GENERAL,
            domicilio_fiscal_receptor="59820",
            regimen_fiscal_receptor=RegimenFiscal.GENERAL_DE_LEY_PERSONAS_MORALES
        ),
        metodo_pago=MetodoPago.PAGO_EN_PARCIALIDADES_O_DIFERIDO,
        serie="A",
        folio="123456",
        conceptos=[
            cfdi40.Concepto(
                clave_prod_serv='84111506',
                cantidad=Decimal('1.00'),
                clave_unidad='E48',
                descripcion='SERVICIOS DE FACTURACION',
                valor_unitario=Decimal('1250.30'),
                impuestos=cfdi40.Impuestos(
                    traslados=cfdi40.Traslado(
                            impuesto=Impuesto.IVA,
                            tipo_factor=TipoFactor.TASA,
                            tasa_o_cuota=Decimal('0.160000'),
                        ),
                    retenciones=[
                        cfdi40.Retencion(
                            impuesto=Impuesto.ISR,
                            tipo_factor=TipoFactor.TASA,
                            tasa_o_cuota=Decimal('0.100000'),
                        ),
                        cfdi40.Retencion(
                            impuesto=Impuesto.IVA,
                            tipo_factor=TipoFactor.TASA,
                            tasa_o_cuota=Decimal('0.106667'),
                        )
                    ],
                )
            )
        ]
    )
    invoice.sign(signer)
    invoice = invoice.process()
  6. Create a CFDI 4.0 Payment (Pago) document

    main

    To create a payment document, use the cfdi40.Comprobante.pago() factory method. This requires a complemento_pago object (from pago20.Pagos) which contains the payment details and related documents (docto_relacionado).

    from datetime import date, datetime
    from decimal import Decimal
    from satcfdi.models import Signer
    from satcfdi.create.cfd import cfdi40, pago20
    
    # Load signing certificate
    signer = Signer.load(
        certificate=open('csd/xiqb891116qe4_csd.cer', 'rb').read(),
        key=open('csd/xiqb891116qe4_csd.key', 'rb').read(),
        password=open('csd/xiqb891116qe4_csd.txt', 'r').read()
    )
    
    # create Comprobante
    invoice = cfdi40.Comprobante.pago(
        emisor=cfdi40.Emisor(
            rfc=signer.rfc,
            nombre=signer.legal_name,
            regimen_fiscal="601"
        ),
        receptor=cfdi40.Receptor(
            rfc='KIJ0906199R1',
            nombre='KIJ, S.A DE C.V.',
            uso_cfdi='G03',
            domicilio_fiscal_receptor="59820",
            regimen_fiscal_receptor="601"
        ),
        lugar_expedicion="56820",
        complemento_pago=pago20.Pagos(
            pago=pago20.Pago(
                fecha_pago=datetime(2020, 1, 1),
                forma_de_pago_p='03',
                moneda_p='MXN',
                tipo_cambio_p=1,
                docto_relacionado=pago20.DoctoRelacionado(
                    id_documento='d6042dc8-d525-4e78-8d1b-092c878bd518',
                    imp_pagado=Decimal("100.3"),
                    imp_saldo_ant=Decimal("203.45"),
                    num_parcialidad=3,
                    moneda_dr="MXN",
                    objeto_imp_dr="01"
                )
            )
        ),
        serie="A",
        folio="123456"
    )
    invoice.sign(signer)
    invoice = invoice.process()
  7. Authenticate with the SAT Factura Electrónica Portal

    main

    To interact with the SAT Factura Electrónica portal, you must first create a Signer instance using your electronic signature files (FIEL/CSD) and then initialize a SATFacturaElectronica session.

    1. Load the Signer: Use Signer.load() providing the raw bytes of the .cer certificate, the .key private key, and the password string.
    2. Initialize Session: Pass the signer instance to SATFacturaElectronica().
    3. Login: Call .login() to establish the session.
    from satcfdi.models import Signer
    from satcfdi.portal import SATFacturaElectronica
    
    # Load Fiel
    signer = Signer.load(
        certificate=open('csd/xiqb891116qe4.cer', 'rb').read(),
        key=open('csd/xiqb891116qe4.key', 'rb').read(),
        password=open('csd/xiqb891116qe4.txt', 'r').read()
    )
    
    sat_session = SATFacturaElectronica(signer)
    sat_session.login()
  8. Create a Certificate Request (Solicitud de Certificado)

    main

    Once you have a loaded Signer instance, you can use the Certifica class to generate a certificate request. This is used to interact with SAT processes for obtaining or renewing certificates.

    Initialize Certifica with your Signer instance and call solicitud_certificado with the following parameters:

    • sucursal: The branch name (e.g., "MiSucursal").
    • password: The password to be used for the request.
    • dirname: The directory where files should be saved (set to None to return data without saving to disk).
    from satcfdi.models import Signer
    from satcfdi.certifica import Certifica
    
    # 1. Load the credentials
    fiel = Signer.load(
        certificate=open('csd/xiqb891116qe4.cer', 'rb').read(),
        key=open('csd/xiqb891116qe4.key', 'rb').read(),
        password=open('csd/xiqb891116qe4.txt', 'r').read()
    )
    
    # 2. Initialize Certifica
    certifica = Certifica(fiel)
    
    # 3. Generate the request
    certifica.solicitud_certificado(
        sucursal="MiSucursal",
        password="IsTHisSecure!32?",
        dirname=None
    )
  9. Perform Mass Download of Emitted Retentions

    main

    To request a mass download of Retentions you have issued, use sat_service.recover_retencion_emitted_request().

    Required Parameters:

    • fecha_inicial: Start date (datetime.date).
    • fecha_final: End date (datetime.date).
    • rfc_emisor: The RFC of the issuer.
    • tipo_solicitud: Use TipoDescargaMasivaTerceros.CFDI.

    The response contains an IdSolicitud which must be stored to check the status later.

    from datetime import date
    from satcfdi.pacs.sat import SAT, TipoDescargaMasivaTerceros
    
    sat_service = SAT(signer=signer)
    
    response = sat_service.recover_retencion_emitted_request(
        fecha_inicial=date(2020, 1, 1),
        fecha_final=date(2020, 12, 1),
        rfc_emisor=sat_service.signer.rfc,
        tipo_solicitud=TipoDescargaMasivaTerceros.CFDI,
    )
    
    id_solicitud = response['IdSolicitud']