from lxml import etree
from datetime import datetime
from lxml import etree
from lxml import etree as ET
from datetime import datetime
import os
import hashlib
import base64
import base64
import hashlib
import xml.etree.ElementTree as ET
import sys
import importlib
from M2Crypto.RSA import load_key_string
importlib.reload(sys)
class crear_cadena40(object):
def __init__(self):
self.now = datetime.now().replace(microsecond=0).isoformat('T')
self.XSLT_STYLE = etree.parse("/home/xslt/cfdi/v40/cadenaoriginal.xslt")
self.xml_string = open('CompraVentaDivisas.xml', 'rb').read()
xml_etree = etree.fromstring(self.xml_string)
self.cadena_original = self.obtener_cadena_original(xml_etree)
self.sello = self.obtener_sello(self.cadena_original) # Obtener el sello
# Parsear el XML antes de agregar el sello
xml_etree = ET.fromstring(self.xml_string)
self.agregar_sello_al_xml(xml_etree) # Agregar el sello al XML
def creacionXML():
"""relelnar atributos del XML"""
certificado_file = '/home/certificados/XIQB891116QE4/XIQB891116QE4.cer'
with open(certificado_file, 'rb') as f:
certificado_data = f.read()
certificado_base64 = base64.b64encode(certificado_data).decode('utf-8').replace('\n', '').replace('\r', '')
os.system('openssl x509 -inform DER -in '+ certificado_file + ' -noout -serial > "numero.txt"')
with open('numero.txt', 'r') as serial_file:
serial = serial_file.read()
no_certificado = serial[::2][4:]
ET.register_namespace('cfdi', "http://www.sat.gob.mx/cfd/4")
ET.register_namespace('divisas', 'http://www.sat.gob.mx/divisas')
namespace = {'xmlns:divisas': 'http://www.sat.gob.mx/divisas', 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation':'http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd http://www.sat.gob.mx/divisas http://www.sat.gob.mx/sitio_internet/cfd/divisas/divisas.xsd'}
Comprobante = ET.Element("{http://www.sat.gob.mx/cfd/4}Comprobante", namespace,
Version="4.0",
Serie="A",
Folio="167ABC",
Fecha=datetime.now().replace(microsecond=0).isoformat('T'),
SubTotal="100.00",
Sello="",
NoCertificado=no_certificado,
Certificado=certificado_base64,
Total="100.00" ,
Moneda="MXN",
TipoCambio="1",
TipoDeComprobante="I" ,
LugarExpedicion="45079",
FormaPago="01",
MetodoPago="PUE",
Exportacion="01"
)
Emisor = ET.SubElement(Comprobante, '{http://www.sat.gob.mx/cfd/4}Emisor',
Rfc="XIQB891116QE4" ,
Nombre="BERENICE XIMO QUEZADA",
RegimenFiscal="605"
)
Receptor = ET.SubElement(Comprobante, '{http://www.sat.gob.mx/cfd/4}Receptor',
Rfc="CTE950627K46" ,
Nombre="COMERCIALIZADORA TEODORIKAS" ,
UsoCFDI="G03",
RegimenFiscalReceptor="601",
DomicilioFiscalReceptor="57740"
)
Conceptos=ET.SubElement(Comprobante, '''cfdi:Conceptos''')
Concepto = ET.SubElement(Conceptos, '''cfdi:Concepto''',
ClaveProdServ="84121600",
Cantidad="1.00",
ClaveUnidad="C62",
Descripcion="Venta dólar americano",
ValorUnitario="100.00",
Importe="100.00",
ObjetoImp="02")
# =========================================
#IMPUESTOS CONCEPTO
# =========================================
Impuestos=ET.SubElement(Concepto, '''cfdi:Impuestos''')
Traslados=ET.SubElement(Impuestos, '''cfdi:Traslados''')
Traslado=ET.SubElement(Traslados, '''cfdi:Traslado''',
Base="100.00",
Impuesto="002",
TipoFactor="Exento")
# =========================================
#IMPUESTOS COMPROBANTE
# =========================================
Impuestos=ET.SubElement(Comprobante, '''cfdi:Impuestos''')
Traslados=ET.SubElement(Impuestos, '''cfdi:Traslados''')
Traslado=ET.SubElement(Traslados, '''cfdi:Traslado''',
Base="100.00",
Impuesto="002",
TipoFactor="Exento")
# =========================================
#COMPLEMENTO DIVISAS
# =========================================
Complemento=ET.SubElement(Comprobante, '''cfdi:Complemento''' )
Divisas=ET.SubElement(Complemento, '''divisas:Divisas''',
version="1.0",
tipoOperacion="venta"
)
xml_str = ET.tostring(Comprobante, encoding='utf-8', method='xml').decode('utf-8')
with open('CompraVentaDivisas.xml', 'w', encoding='utf-8') as archivo:
archivo.truncate(0)
archivo.write(xml_str)
archivo.close()
archivo=creacionXML()
def obtener_cadena_original(self, xml_etree):
result = etree.XSLT(self.XSLT_STYLE)
result = result(xml_etree)
original_string = str(result)
original_string = original_string.replace('<?xml version="1.0" encoding="UTF-8"?>\n', '')
original_string = original_string.replace("\n", "")
original_string = original_string.replace(""", '"')
original_string = original_string.replace("<", "<")
original_string = original_string.replace(">", ">")
original_string = original_string.replace("&", "&")
original_string = original_string.strip()
self.original_string_result = original_string
return self.original_string_result
def obtener_sello(self, cadena_original):
"""Generar sello del XML"""
key_file=open('/home/certificados/XIQB891116QE4/XIQB891116QE4.pem').read()
key_pem =bytes(key_file, encoding='utf8')
rsa = load_key_string(key_pem)
assert len(rsa) in (1024, 2048)
assert rsa.e == b'\000\000\000\003\001\000\001'
#The algorithm was changed from MD5 to SHA1
cadena_original_encode = cadena_original.encode('utf-8')
md5_digest = hashlib.sha256(cadena_original_encode).digest()
rsa_signature = rsa.sign( md5_digest, 'sha256')
signature = base64.b64encode(rsa_signature)
return signature
def agregar_sello_al_xml(self, xml_etree):
"""insertar sello al XML"""
xml_etree.set("Sello", self.sello.decode('utf-8'))
# Si deseas guardar el XML modificado, descomenta la siguiente línea:
# ET.ElementTree(xml_etree).write('ConsumoDeCombustibles_con_sello.xml', encoding='utf-8', xml_declaration=True)
instancia = crear_cadena40()
<?php
date_default_timezone_set("America/Mexico_City");
error_reporting(E_ALL & ~E_WARNING);
// Rutas
$rutaCer = "/home/certificados/XIQB891116QE4/XIQB891116QE4.cer"; // Ruta del certificado .cer
$rutaKey = "/home/certificados/XIQB891116QE4/XIQB891116QE4.pem"; // Ruta de la llave privada .key.pem
$rutaXML = "/home/certificados/XIQB891116QE4/CompraVentaDivisas.xml"; // Ruta donde se guardará el XML generado
$rutaCadena = "/home/certificados/XIQB891116QE4/CadenaOriginal.txt"; // Ruta donde se guardará la Cadena Original
$rutaXSLT = "/home/certificados/XIQB891116QE4/cadenaoriginal.xslt"; // Ruta del XSLT para generar la Cadena Original
if (!file_exists(dirname($rutaXML))) mkdir(dirname($rutaXML), 0777, true);
// =========================================
// Certificado Base64
// =========================================
$certificado = base64_encode(file_get_contents($rutaCer));
// =========================================
// Obtener NoCertificado con HEX2BIN
// =========================================
$certDer = file_get_contents($rutaCer);
$certPem = "-----BEGIN CERTIFICATE-----\n" .
chunk_split(base64_encode($certDer), 64, "\n") .
"-----END CERTIFICATE-----\n";
$parsed = @openssl_x509_parse($certPem);
if ($parsed !== false && !empty($parsed['serialNumberHex'])) {
$hex = preg_replace('/\s+/', '', $parsed['serialNumberHex']);
$noCertificado = strtoupper(hex2bin($hex));
}
$ns = "http://www.sat.gob.mx/cfd/4";
$nsDivisas = "http://www.sat.gob.mx/divisas";
// =========================================
// === CREAR XML === //
// =========================================
$xml = new DOMDocument("1.0", "UTF-8");
$xml->formatOutput = true;
// =========================================
// Crear nodo Comprobante
// =========================================
$comprobante = $xml->createElementNS($ns, "cfdi:Comprobante");
$xml->appendChild($comprobante);
// =========================================
// DECLARAR NAMESPACE
// =========================================
$comprobante->setAttributeNS(
"http://www.w3.org/2000/xmlns/",
"xmlns:divisas",
$nsDivisas
);
// =========================================
// Namespaces obligatorios
// =========================================
$comprobante->setAttribute("xmlns:cfdi", $ns);
$comprobante->setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
$comprobante->setAttribute("xsi:schemaLocation",
"$ns http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd "
. "$nsDivisas http://www.sat.gob.mx/sitio_internet/cfd/divisas/divisas.xsd");
// =========================================
// Atributos CFDI
// =========================================
$comprobante->setAttribute("Version", "4.0");
$comprobante->setAttribute("Serie", "A");
$comprobante->setAttribute("Folio", "167ABC");
$comprobante->setAttribute("Fecha", date('Y-m-d\TH:i:s'));
$comprobante->setAttribute("SubTotal", "100.00");
$comprobante->setAttribute("Total", "100.00");
$comprobante->setAttribute("Moneda", "MXN");
$comprobante->setAttribute("TipoCambio", "1");
$comprobante->setAttribute("TipoDeComprobante", "I");
$comprobante->setAttribute("LugarExpedicion", "45079");
$comprobante->setAttribute("FormaPago", "01");
$comprobante->setAttribute("MetodoPago", "PUE");
$comprobante->setAttribute("Exportacion", "01");
if (!empty($noCertificado)) {
$comprobante->setAttribute("NoCertificado", $noCertificado);
}
$comprobante->setAttribute("Certificado", $certificado);
// =========================================
// === Emisor ===
// =========================================
$emisor = $xml->createElementNS($ns, "cfdi:Emisor");
$emisor->setAttribute("Rfc", "XIQB891116QE4");
$emisor->setAttribute("Nombre", "BERENICE XIMO QUEZADA");
$emisor->setAttribute("RegimenFiscal", "605");
$comprobante->appendChild($emisor);
// =========================================
// === Receptor ===
// =========================================
$receptor = $xml->createElementNS($ns, "cfdi:Receptor");
$receptor->setAttribute("Rfc", "CTE950627K46");
$receptor->setAttribute("Nombre", "COMERCIALIZADORA TEODORIKAS");
$receptor->setAttribute("UsoCFDI", "G03");
$receptor->setAttribute("RegimenFiscalReceptor", "601");
$receptor->setAttribute("DomicilioFiscalReceptor", "57740");
$comprobante->appendChild($receptor);
// =========================================
// === Conceptos ===
// =========================================
$conceptos = $xml->createElementNS($ns, "cfdi:Conceptos");
$concepto = $xml->createElementNS($ns, "cfdi:Concepto");
$concepto->setAttribute("ClaveProdServ", "84121600");
$concepto->setAttribute("Cantidad", "1.00");
$concepto->setAttribute("ClaveUnidad", "C62");
$concepto->setAttribute("Descripcion", "Venta dólar americano");
$concepto->setAttribute("ValorUnitario", "100.00");
$concepto->setAttribute("Importe", "100.00");
$concepto->setAttribute("ObjetoImp", "02");
$conceptos->appendChild($concepto);
$comprobante->appendChild($conceptos);
// =========================================
// Impuestos del concepto
// =========================================
$impC = $xml->createElementNS($ns, "cfdi:Impuestos");
$tras = $xml->createElementNS($ns, "cfdi:Traslados");
$t = $xml->createElementNS($ns, "cfdi:Traslado");
$t->setAttribute("Base", "100.00");
$t->setAttribute("Impuesto", "002");
$t->setAttribute("TipoFactor", "Exento");
// $t->setAttribute("TasaOCuota", "0.160000");
// $t->setAttribute("Importe", "160.00");
$tras->appendChild($t);
// $retC = $xml->createElementNS($ns, "cfdi:Retenciones");
// $r = $xml->createElementNS($ns, "cfdi:Retencion");
// $r->setAttribute("Base", "1000.00");
// $r->setAttribute("Impuesto", "002");
// $r->setAttribute("TipoFactor", "Tasa");
// $r->setAttribute("TasaOCuota", "0.040000");
// $r->setAttribute("Importe", "40.00");
// $retC->appendChild($r);
$impC->appendChild($tras);
// $impC->appendChild($retC);
$concepto->appendChild($impC);
// Impuestos totales
$impTotal = $xml->createElementNS($ns, "cfdi:Impuestos");
// $impTotal->setAttribute("TotalImpuestosRetenidos", "40.00");
// $impTotal->setAttribute("TotalImpuestosTrasladados", "160.00");
$retencionesTotal = $xml->createElementNS($ns, "cfdi:Retenciones");
// $retencionTotal = $xml->createElementNS($ns, "cfdi:Retencion");
// $retencionTotal->setAttribute("Impuesto", "002");
// $retencionTotal->setAttribute("Importe", "40.00");
// $retencionesTotal->appendChild($retencionTotal);
// $impTotal->appendChild($retencionesTotal);
$trasladosTotal = $xml->createElementNS($ns, "cfdi:Traslados");
$trasladoTotal = $xml->createElementNS($ns, "cfdi:Traslado");
$trasladoTotal->setAttribute("Base", "100.00");
$trasladoTotal->setAttribute("Impuesto", "002");
$trasladoTotal->setAttribute("TipoFactor", "Exento");
// $trasladoTotal->setAttribute("TasaOCuota", "0.160000");
// $trasladoTotal->setAttribute("Importe", "160.00");
$trasladosTotal->appendChild($trasladoTotal);
$impTotal->appendChild($trasladosTotal);
$comprobante->appendChild($impTotal);
// ===========================================================
$Complemento = $xml->createElementNS($ns, "cfdi:Complemento");
$comprobante->appendChild($Complemento);
$DivisasN = $xml->createElementNS($nsDivisas, "divisas:Divisas");
$DivisasN->setAttribute("version", "1.0");
$DivisasN->setAttribute("tipoOperacion", "venta");
$Complemento->appendChild($DivisasN);
// =========================================
// === CADENA ORIGINAL ===
// =========================================
$xsl = new DOMDocument();
$xsl->load($rutaXSLT);
$proc = new XSLTProcessor();
$proc->importStylesheet($xsl);
$cadenaOriginal = trim($proc->transformToXML($xml));
file_put_contents($rutaCadena, $cadenaOriginal);
// === SELLO DIGITAL ===
$privKey = openssl_pkey_get_private(file_get_contents($rutaKey));
if (!$privKey) {
die("ERROR cargando la llave privada:\n" . openssl_error_string());
}
if (!openssl_sign($cadenaOriginal, $sello, $privKey, OPENSSL_ALGO_SHA256)) {
die("ERROR al firmar:\n" . openssl_error_string());
}
$sello = base64_encode($sello);
$comprobante->setAttribute("Sello", $sello);
// =========================================
// === GUARDAR XML ===
// =========================================
$xml->save($rutaXML);
@file_put_contents('/tmp/debug_divisas.xml', $xml->saveXML());
echo "\nXML sellado generado correctamente:\n$rutaXML\n";
echo "\nCadena Original generada:\n$cadenaOriginal\n";
?>