VerifactuMidAPI/test/test_directo_aeat.py

155 lines
5.9 KiB
Python

#!/usr/bin/env python3
"""
Peticion directa al endpoint VeriFactu de la AEAT (sin pasar por la API).
Usa mTLS con los PEM ya convertidos en data/certs/.
"""
import hashlib
from datetime import datetime, timezone
import requests
# Certificado (los PEM no tienen contrasena)
CERT_PEM = "data/certs/cert_cert.pem"
KEY_PEM = "data/certs/cert_key.pem"
# Identidad
NIF = "53950250R"
NOMBRE = "JOSEP VICENT MESTRE LLOBELL"
# Endpoint de pruebas AEAT
URL = "https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP"
# Datos de la factura de prueba
NUM_SERIE = "TEST-DIRECTO-003"
FECHA_EXP = "13-05-2026"
TIPO_FACTURA = "F1"
BASE = 100.00
CUOTA = 21.00
TOTAL = 121.00
# Registro anterior (encadenamiento)
PREV_NUM_SERIE = "TEST-DIRECTO-002"
PREV_FECHA_EXP = "13-05-2026"
PREV_HUELLA = "B4F12C2C6407438501BBB5C81A8443E78860CD2D736D614C032CEDB4CC521D90"
# Timestamp generacion (mismo formato para hash y XML)
_now = datetime.now(timezone.utc)
FECHA_GEN = _now.strftime("%Y-%m-%dT%H:%M:%S+00:00")
def calcular_huella(nif, num_serie, fecha_exp, tipo, cuota, total, prev_hash, fecha_gen):
"""SHA-256 segun la especificacion VeriFactu (formato key=value&)."""
campos = (
f"IDEmisorFactura={nif}&"
f"NumSerieFactura={num_serie}&"
f"FechaExpedicionFactura={fecha_exp}&"
f"TipoFactura={tipo}&"
f"CuotaTotal={cuota:.2f}&"
f"ImporteTotal={total:.2f}&"
f"Huella={prev_hash}&"
f"FechaHoraHusoGenRegistro={fecha_gen}"
)
print(f" Campos huella : {campos}")
return hashlib.sha256(campos.encode()).hexdigest().upper()
huella = calcular_huella(NIF, NUM_SERIE, FECHA_EXP, TIPO_FACTURA, CUOTA, TOTAL, PREV_HUELLA, FECHA_GEN)
SOAP = f"""<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:sum="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd"
xmlns:sum1="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd">
<soapenv:Header/>
<soapenv:Body>
<sum:RegFactuSistemaFacturacion>
<sum:Cabecera>
<sum1:ObligadoEmision>
<sum1:NombreRazon>{NOMBRE}</sum1:NombreRazon>
<sum1:NIF>{NIF}</sum1:NIF>
</sum1:ObligadoEmision>
</sum:Cabecera>
<sum:RegistroFactura>
<sum1:RegistroAlta>
<sum1:IDVersion>1.0</sum1:IDVersion>
<sum1:IDFactura>
<sum1:IDEmisorFactura>{NIF}</sum1:IDEmisorFactura>
<sum1:NumSerieFactura>{NUM_SERIE}</sum1:NumSerieFactura>
<sum1:FechaExpedicionFactura>{FECHA_EXP}</sum1:FechaExpedicionFactura>
</sum1:IDFactura>
<sum1:NombreRazonEmisor>{NOMBRE}</sum1:NombreRazonEmisor>
<sum1:TipoFactura>{TIPO_FACTURA}</sum1:TipoFactura>
<sum1:DescripcionOperacion>Factura de prueba directa</sum1:DescripcionOperacion>
<sum1:Destinatarios>
<sum1:IDDestinatario>
<sum1:NombreRazon>{NOMBRE}</sum1:NombreRazon>
<sum1:NIF>{NIF}</sum1:NIF>
</sum1:IDDestinatario>
</sum1:Destinatarios>
<sum1:Desglose>
<sum1:DetalleDesglose>
<sum1:ClaveRegimen>01</sum1:ClaveRegimen>
<sum1:CalificacionOperacion>S1</sum1:CalificacionOperacion>
<sum1:TipoImpositivo>21.00</sum1:TipoImpositivo>
<sum1:BaseImponibleOimporteNoSujeto>{BASE:.2f}</sum1:BaseImponibleOimporteNoSujeto>
<sum1:CuotaRepercutida>{CUOTA:.2f}</sum1:CuotaRepercutida>
</sum1:DetalleDesglose>
</sum1:Desglose>
<sum1:CuotaTotal>{CUOTA:.2f}</sum1:CuotaTotal>
<sum1:ImporteTotal>{TOTAL:.2f}</sum1:ImporteTotal>
<sum1:Encadenamiento>
<sum1:RegistroAnterior>
<sum1:IDEmisorFactura>{NIF}</sum1:IDEmisorFactura>
<sum1:NumSerieFactura>{PREV_NUM_SERIE}</sum1:NumSerieFactura>
<sum1:FechaExpedicionFactura>{PREV_FECHA_EXP}</sum1:FechaExpedicionFactura>
<sum1:Huella>{PREV_HUELLA}</sum1:Huella>
</sum1:RegistroAnterior>
</sum1:Encadenamiento>
<sum1:SistemaInformatico>
<sum1:NombreRazon>{NOMBRE}</sum1:NombreRazon>
<sum1:NIF>{NIF}</sum1:NIF>
<sum1:NombreSistemaInformatico>VerifactuMidAPI</sum1:NombreSistemaInformatico>
<sum1:IdSistemaInformatico>01</sum1:IdSistemaInformatico>
<sum1:Version>1.0.0</sum1:Version>
<sum1:NumeroInstalacion>1</sum1:NumeroInstalacion>
<sum1:TipoUsoPosibleSoloVerifactu>S</sum1:TipoUsoPosibleSoloVerifactu>
<sum1:TipoUsoPosibleMultiOT>N</sum1:TipoUsoPosibleMultiOT>
<sum1:IndicadorMultiplesOT>N</sum1:IndicadorMultiplesOT>
</sum1:SistemaInformatico>
<sum1:FechaHoraHusoGenRegistro>{FECHA_GEN}</sum1:FechaHoraHusoGenRegistro>
<sum1:TipoHuella>01</sum1:TipoHuella>
<sum1:Huella>{huella}</sum1:Huella>
</sum1:RegistroAlta>
</sum:RegistroFactura>
</sum:RegFactuSistemaFacturacion>
</soapenv:Body>
</soapenv:Envelope>"""
print(f"\n{'='*60}")
print(f"Endpoint : {URL}")
print(f"NIF : {NIF}")
print(f"Num serie : {NUM_SERIE}")
print(f"Huella : {huella}")
print(f"Fecha gen : {FECHA_GEN}")
print(f"{'='*60}\n")
try:
resp = requests.post(
URL,
data=SOAP.encode("utf-8"),
headers={"Content-Type": "text/xml; charset=utf-8", "SOAPAction": ""},
cert=(CERT_PEM, KEY_PEM),
verify=True,
timeout=30,
)
print(f"HTTP Status : {resp.status_code}")
print(f"\nRespuesta AEAT:\n{resp.text}")
except requests.exceptions.SSLError as e:
print(f"[ERROR SSL] Problema con el certificado: {e}")
except requests.exceptions.ConnectionError as e:
print(f"[ERROR CONEXION] No se pudo conectar: {e}")
except Exception as e:
print(f"[ERROR] {type(e).__name__}: {e}")