155 lines
5.9 KiB
Python
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}")
|