Fix integración VeriFactu AEAT: XML, hash y encadenamiento
Bugs corregidos tras validación directa contra la plataforma de pruebas: - Hash: formato key=valor& en lugar de separador | - TipoHuella: código 01 en lugar de literal SHA-256 - FechaHoraHusoGenRegistro: formato ISO 8601 con huso horario - IdSistemaInformatico: 2 caracteres (01) - Namespaces SOAP: URLs correctas de www2.agenciatributaria.gob.es - Estructura XML: eliminados wrappers Content/AltaReq sobrantes - Encadenamiento: xs:choice correcto (PrimerRegistro XOR RegistroAnterior) - RegistroAnterior: referencia datos de la factura anterior, no la actual - Destinatarios: bloque obligatorio para facturas F1/F3/rectificativas - wsu:Id: eliminado atributo con namespace no declarado - Parser respuesta: mapeado a RespuestaRegFactuSistemaFacturacion real - Campo emisor_nombre añadido al input de factura La API envía y recibe correctamente contra prewww1.aeat.es con respuesta EstadoEnvio=Correcto y CSV asignado por la AEAT. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b9899684cc
commit
1ad84a3b14
27
README.md
27
README.md
|
|
@ -21,10 +21,12 @@ server:
|
||||||
port: 6789
|
port: 6789
|
||||||
|
|
||||||
verifactu:
|
verifactu:
|
||||||
environment: test
|
production: false # true para producción
|
||||||
|
|
||||||
certificates:
|
certificates:
|
||||||
storage_path: ./certs/
|
storage_path: ./data/certs/
|
||||||
|
cert_file: ./data/certs/personal.p12
|
||||||
|
cert_password: TU_CONTRASEÑA
|
||||||
|
|
||||||
crypto:
|
crypto:
|
||||||
keys_path: ./keys/
|
keys_path: ./keys/
|
||||||
|
|
@ -81,6 +83,7 @@ Authorization: Bearer <token>
|
||||||
"tipo": "alta",
|
"tipo": "alta",
|
||||||
"factura": {
|
"factura": {
|
||||||
"emisor_nif": "A12345678",
|
"emisor_nif": "A12345678",
|
||||||
|
"emisor_nombre": "NOMBRE REGISTRADO EN AEAT",
|
||||||
"num_serie": "2024-001",
|
"num_serie": "2024-001",
|
||||||
"fecha_expedicion": "13-09-2024",
|
"fecha_expedicion": "13-09-2024",
|
||||||
"tipo_factura": "F1",
|
"tipo_factura": "F1",
|
||||||
|
|
@ -95,7 +98,7 @@ Authorization: Bearer <token>
|
||||||
"importe_total": 121.00
|
"importe_total": 121.00
|
||||||
},
|
},
|
||||||
"sistema": {
|
"sistema": {
|
||||||
"nombre": "Software",
|
"nombre": "NOMBRE REGISTRADO EN AEAT PARA EL NIF PROVEEDOR",
|
||||||
"nif_proveedor": "A12345678",
|
"nif_proveedor": "A12345678",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -126,13 +129,17 @@ Authorization: Bearer <token>
|
||||||
| Campo | Tipo | Descripción |
|
| Campo | Tipo | Descripción |
|
||||||
|-------|------|-------------|
|
|-------|------|-------------|
|
||||||
| `tipo` | string | `alta` o `anulacion` |
|
| `tipo` | string | `alta` o `anulacion` |
|
||||||
| `emisor_nif` | string | NIF emisor (9 caracteres) |
|
| `factura.emisor_nif` | string | NIF emisor (9 caracteres) |
|
||||||
| `num_serie` | string | Número de serie |
|
| `factura.emisor_nombre` | string | Nombre del emisor exactamente como figura en el censo AEAT |
|
||||||
| `fecha_expedicion` | string | Fecha (`dd-mm-yyyy`) |
|
| `factura.num_serie` | string | Número de serie |
|
||||||
| `tipo_factura` | string | `F1`, `F2`, `R1`-`R5` |
|
| `factura.fecha_expedicion` | string | Fecha (`dd-mm-yyyy`) |
|
||||||
| `iva[]` | array | Al menos un registro |
|
| `factura.tipo_factura` | string | `F1`, `F2`, `R1`-`R5` |
|
||||||
| `importe_total` | number | > 0 |
|
| `factura.destinatario` | object | Obligatorio para F1, F3 y rectificativas |
|
||||||
| `sistema.*` | object | Datos del software |
|
| `factura.iva[]` | array | Al menos un registro |
|
||||||
|
| `factura.importe_total` | number | > 0 |
|
||||||
|
| `sistema.nombre` | string | Nombre del proveedor del software exactamente como figura en el censo AEAT para su NIF |
|
||||||
|
| `sistema.nif_proveedor` | string | NIF del proveedor del software |
|
||||||
|
| `sistema.version` | string | Versión del software |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,32 @@
|
||||||
# Obtener Certificado de Pruebas AEAT
|
# Certificado Digital para VeriFactu
|
||||||
|
|
||||||
## Introducción
|
## Introducción
|
||||||
|
|
||||||
Para enviar facturas al entorno de pruebas de VeriFactu (no en producción), necesitas un certificado digital de pruebas registrado en el sistema de la AEAT.
|
VeriFactu requiere autenticación mTLS: la API presenta tu certificado al conectarse con la AEAT. Tanto en pruebas como en producción se usa el mismo certificado real — no existe un certificado especial de pruebas.
|
||||||
|
|
||||||
## Opciones de Certificados
|
## Certificado válido: FNMT Persona Física
|
||||||
|
|
||||||
### Opción 1: Certificado FNMT (Fábrica Nacional de Moneda y Timbre)
|
El **Certificado de Ciudadano (Persona Física) de la FNMT** es gratuito y válido tanto para el entorno de pruebas como para producción.
|
||||||
|
|
||||||
Es el más común para empresas y autónomos en España.
|
**Obtención gratuita — opciones:**
|
||||||
|
|
||||||
**Pasos:**
|
1. **Presencial en oficina AEAT** (recomendado): llevar el DNI a cualquier delegación de la Agencia Tributaria. Trámite en 2 minutos, sin cita previa en la mayoría.
|
||||||
1. Ir a https://www.fnmt.es/consultas-y-resolucion-de-incidentes/centros-de-emision-y-oficinas
|
2. **Con DNIe + NFC**: si tu DNI es posterior a 2015, puedes hacer todo el proceso online desde `sede.fnmt.gob.es` usando el chip NFC del DNI con tu móvil o un lector de tarjetas.
|
||||||
2. Buscar una oficina cercana FNMT
|
3. **Videollamada** (coste 2,99 € + IVA): servicio de identificación remota sin desplazamiento.
|
||||||
3. Acudir presencialmente con DNI
|
|
||||||
4. Solicitar certificado de persona física
|
|
||||||
5. Te lo emiten en el acto
|
|
||||||
|
|
||||||
### Opción 2: Certificado de la AEAT (solo pruebas)
|
> La renovación online es gratuita mientras el certificado esté vigente (hasta 60 días antes de su vencimiento).
|
||||||
|
|
||||||
La AEAT ofrece certificados de pruebas:
|
## Verificar el certificado (PowerShell)
|
||||||
|
|
||||||
**Pasos:**
|
```powershell
|
||||||
1. Ir a https://preportal.aeat.es
|
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(".\data\certs\personal.p12", "TU_CONTRASEÑA")
|
||||||
2. Registro como usuario
|
Write-Host "Válido desde:" $cert.NotBefore
|
||||||
3. Solicitar certificado de prueba
|
Write-Host "Válido hasta:" $cert.NotAfter
|
||||||
4. Descargar archivo .p12
|
Write-Host "Asunto:" $cert.Subject
|
||||||
|
Write-Host "Vigente:" ($cert.NotAfter -gt (Get-Date))
|
||||||
|
```
|
||||||
|
|
||||||
> **Nota**: Los certificados de prueba de la AEAT suelen tener validez limitada.
|
El campo `Subject` debe contener `OU=CIUDADANOS` (persona física) y el NIF del titular.
|
||||||
|
|
||||||
### Opción 3: Certificado personal (prod)
|
|
||||||
|
|
||||||
Si ya tienes tu certificado personal (el que usaste antes), puede que funcione en producción.
|
|
||||||
|
|
||||||
**Verificar:**
|
|
||||||
1. ¿Está vigente? (no expired)
|
|
||||||
2. ¿Es cualificado? (firmado por autoridad reconocida)
|
|
||||||
|
|
||||||
## Registrar Nuevo Certificado
|
## Registrar Nuevo Certificado
|
||||||
|
|
||||||
|
|
@ -96,17 +87,6 @@ VerifactuMidAPI/
|
||||||
└── config.yml # Configuración
|
└── config.yml # Configuración
|
||||||
```
|
```
|
||||||
|
|
||||||
## Verificar Certificado
|
|
||||||
|
|
||||||
Para verificar que el certificado es válido:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
openssl pkcs12 -in .\data\certs\personal.p12 -passin pass:TU_PASS -nokeys -clcerts | openssl x509 -noout -dates
|
|
||||||
```
|
|
||||||
|
|
||||||
Muestra:
|
|
||||||
- Not Before: fecha de inicio de validez
|
|
||||||
- Not After: fecha de caducidad
|
|
||||||
|
|
||||||
## Problemas Comunes
|
## Problemas Comunes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,36 +33,42 @@ Buscar facturas previamente enviadas.
|
||||||
|
|
||||||
## Encadenamiento de Hash
|
## Encadenamiento de Hash
|
||||||
|
|
||||||
Cada factura incluye el hash de la anterior:
|
Cada factura incluye el hash de la anterior. El formato exacto validado por la AEAT es:
|
||||||
|
|
||||||
```
|
```
|
||||||
Hash(N) = SHA256(Datos(N) + Hash(N-1))
|
SHA256("IDEmisorFactura=NIF&NumSerieFactura=SERIE&FechaExpedicionFactura=dd-mm-yyyy&TipoFactura=F1&CuotaTotal=21.00&ImporteTotal=121.00&Huella=HASH_ANTERIOR&FechaHoraHusoGenRegistro=yyyy-mm-ddThh:mm:ss+hh:mm")
|
||||||
```
|
```
|
||||||
|
|
||||||
Esto garantiza la integridad del registro histórico.
|
Para el primer registro de un emisor, `Huella` se deja vacío y en el XML se envía `<PrimerRegistro>S</PrimerRegistro>`. Para el resto, se envía `<RegistroAnterior>` con los datos de la factura anterior (nunca ambos a la vez).
|
||||||
|
|
||||||
## URLs
|
## URLs
|
||||||
|
|
||||||
### Testing (Preproducción)
|
### Testing (Preproducción)
|
||||||
```
|
```
|
||||||
https://prewww2.aeat.es/.../SistemaFacturacion
|
https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP
|
||||||
```
|
```
|
||||||
|
|
||||||
### Producción
|
### Producción
|
||||||
```
|
```
|
||||||
https://www2.agenciatributaria.gob.es/.../SistemaFacturacion
|
https://www1.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP
|
||||||
```
|
```
|
||||||
|
|
||||||
## Formato
|
## Formato
|
||||||
|
|
||||||
SOAP 1.1 sobre HTTPS con:
|
SOAP 1.1 sobre HTTPS con mTLS (certificado cliente cualificado). Namespaces requeridos:
|
||||||
- Certificado cliente cualificado
|
|
||||||
- XML con namespaces específicos
|
- `xmlns:sum` → `https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd`
|
||||||
- Response con CSV (Código de Verificación)
|
- `xmlns:sum1` → `https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd`
|
||||||
|
|
||||||
|
Campos específicos a tener en cuenta:
|
||||||
|
- `TipoHuella`: valor `01` (no `SHA-256`)
|
||||||
|
- `FechaHoraHusoGenRegistro`: formato ISO 8601 con huso horario (`yyyy-mm-ddThh:mm:ss+hh:mm`)
|
||||||
|
- `IdSistemaInformatico`: 2 caracteres (`01`, no `1`)
|
||||||
|
- `Encadenamiento`: usa `xs:choice` — o `PrimerRegistro` o `RegistroAnterior`, nunca los dos
|
||||||
|
|
||||||
## CSV
|
## CSV
|
||||||
|
|
||||||
Código de Verificación de 13 caracteres (o hash SHA-256 en desarrollo) que acredita el registro en Hacienda.
|
Código de Verificación asignado por la AEAT con formato `A-XXXXXXXXXXXXX`. Acredita el registro de la factura en Hacienda. Cuando la API opera en modo fallback local devuelve el hash SHA-256 en su lugar y el estado indica `Correcto (local)`.
|
||||||
|
|
||||||
## Fallback Local
|
## Fallback Local
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -63,7 +64,8 @@ func (s *FacturaService) ProcessAlta(input AltaInput) (*AltaOutput, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
prevHash := ""
|
var prevHash, prevNumSerie string
|
||||||
|
var prevFecha time.Time
|
||||||
if s.hashStorage != nil {
|
if s.hashStorage != nil {
|
||||||
record, err := s.hashStorage.GetLastRecord(input.Factura.EmisorNIF)
|
record, err := s.hashStorage.GetLastRecord(input.Factura.EmisorNIF)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -74,6 +76,8 @@ func (s *FacturaService) ProcessAlta(input AltaInput) (*AltaOutput, error) {
|
||||||
}
|
}
|
||||||
if record != nil {
|
if record != nil {
|
||||||
prevHash = record.Huella
|
prevHash = record.Huella
|
||||||
|
prevNumSerie = record.NumSerie
|
||||||
|
prevFecha = record.Fecha
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,7 +96,7 @@ func (s *FacturaService) ProcessAlta(input AltaInput) (*AltaOutput, error) {
|
||||||
data.Huella = currentHash
|
data.Huella = currentHash
|
||||||
data.FechaGen = now
|
data.FechaGen = now
|
||||||
|
|
||||||
altaData := ToAltaData(&input.InvoiceInput, data, currentHash, prevHash)
|
altaData := ToAltaData(&input.InvoiceInput, data, currentHash, prevHash, prevNumSerie, prevFecha)
|
||||||
|
|
||||||
if s.verifactu != nil {
|
if s.verifactu != nil {
|
||||||
log.Printf("Sending to AEAT...")
|
log.Printf("Sending to AEAT...")
|
||||||
|
|
@ -108,8 +112,12 @@ func (s *FacturaService) ProcessAlta(input AltaInput) (*AltaOutput, error) {
|
||||||
goto saveLocal
|
goto saveLocal
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.Body.RegistroRespuesta != nil {
|
if resp.Body.Respuesta != nil {
|
||||||
if resp.Body.RegistroRespuesta.Estado == "Correcto" {
|
estado := resp.Body.Respuesta.EstadoEnvio
|
||||||
|
csv := resp.Body.Respuesta.CSV
|
||||||
|
log.Printf("AEAT EstadoEnvio: %s CSV: %s", estado, csv)
|
||||||
|
|
||||||
|
if estado == "Correcto" {
|
||||||
if s.hashStorage != nil {
|
if s.hashStorage != nil {
|
||||||
if err := s.hashStorage.SaveLastRecord(lastRecord); err != nil {
|
if err := s.hashStorage.SaveLastRecord(lastRecord); err != nil {
|
||||||
return &AltaOutput{
|
return &AltaOutput{
|
||||||
|
|
@ -118,17 +126,22 @@ func (s *FacturaService) ProcessAlta(input AltaInput) (*AltaOutput, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AltaOutput{
|
return &AltaOutput{
|
||||||
Success: true,
|
Success: true,
|
||||||
CSV: resp.Body.RegistroRespuesta.CSV,
|
CSV: csv,
|
||||||
Estado: resp.Body.RegistroRespuesta.Estado,
|
Estado: "Correcto",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errMsg := estado
|
||||||
|
if len(resp.Body.Respuesta.RespuestaLineas) > 0 {
|
||||||
|
l := resp.Body.Respuesta.RespuestaLineas[0]
|
||||||
|
errMsg = fmt.Sprintf("%s [%s] %s", estado, l.CodigoError, l.DescripcionError)
|
||||||
|
}
|
||||||
|
log.Printf("AEAT error: %s", errMsg)
|
||||||
return &AltaOutput{
|
return &AltaOutput{
|
||||||
Success: false,
|
Success: false,
|
||||||
Error: "aeat_error:" + resp.Body.RegistroRespuesta.Estado,
|
Error: "aeat_error: " + errMsg,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +201,7 @@ func (s *FacturaService) ProcessAnulacion(input AnulacionInput) (*AnulacionOutpu
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToAltaData(in *InvoiceInput, data *InvoiceData, huella, prevHash string) verifactu.AltaData {
|
func ToAltaData(in *InvoiceInput, data *InvoiceData, huella, prevHash, prevNumSerie string, prevFecha time.Time) verifactu.AltaData {
|
||||||
ivaData := make([]verifactu.IVARegularizacionData, len(data.IVA))
|
ivaData := make([]verifactu.IVARegularizacionData, len(data.IVA))
|
||||||
for i, iva := range data.IVA {
|
for i, iva := range data.IVA {
|
||||||
ivaData[i] = verifactu.IVARegularizacionData{
|
ivaData[i] = verifactu.IVARegularizacionData{
|
||||||
|
|
@ -208,7 +221,7 @@ func ToAltaData(in *InvoiceInput, data *InvoiceData, huella, prevHash string) ve
|
||||||
}
|
}
|
||||||
|
|
||||||
return verifactu.AltaData{
|
return verifactu.AltaData{
|
||||||
EmisorNombre: in.Factura.EmisorNIF,
|
EmisorNombre: in.Factura.EmisorNombre,
|
||||||
EmisorNIF: data.EmisorNIF,
|
EmisorNIF: data.EmisorNIF,
|
||||||
NumSerie: data.NumSerie,
|
NumSerie: data.NumSerie,
|
||||||
FechaExpedicion: data.Fecha,
|
FechaExpedicion: data.Fecha,
|
||||||
|
|
@ -229,6 +242,8 @@ func ToAltaData(in *InvoiceInput, data *InvoiceData, huella, prevHash string) ve
|
||||||
},
|
},
|
||||||
Huella: huella,
|
Huella: huella,
|
||||||
PrevHash: prevHash,
|
PrevHash: prevHash,
|
||||||
|
PrevNumSerie: prevNumSerie,
|
||||||
|
PrevFecha: prevFecha,
|
||||||
FechaGen: data.FechaGen,
|
FechaGen: data.FechaGen,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type HashService struct {
|
type HashService struct {
|
||||||
lastRecord *LastRecord
|
lastRecord *LastRecord
|
||||||
}
|
}
|
||||||
|
|
@ -37,9 +38,10 @@ func (s *HashService) GetLastRecord() *LastRecord {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HashService) CalculateHash(data *InvoiceData, previousHash string) string {
|
func (s *HashService) CalculateHash(data *InvoiceData, previousHash string) string {
|
||||||
fechaGen := data.FechaGen.Format(time.RFC3339)
|
fechaGen := data.FechaGen.Format("2006-01-02T15:04:05-07:00")
|
||||||
|
|
||||||
fields := fmt.Sprintf("%s|%s|%s|%s|%.2f|%.2f|%s|%s",
|
fields := fmt.Sprintf(
|
||||||
|
"IDEmisorFactura=%s&NumSerieFactura=%s&FechaExpedicionFactura=%s&TipoFactura=%s&CuotaTotal=%.2f&ImporteTotal=%.2f&Huella=%s&FechaHoraHusoGenRegistro=%s",
|
||||||
data.EmisorNIF,
|
data.EmisorNIF,
|
||||||
data.NumSerie,
|
data.NumSerie,
|
||||||
data.Fecha.Format("02-01-2006"),
|
data.Fecha.Format("02-01-2006"),
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ type InvoiceInput struct {
|
||||||
|
|
||||||
type FacturaInput struct {
|
type FacturaInput struct {
|
||||||
EmisorNIF string `json:"emisor_nif"`
|
EmisorNIF string `json:"emisor_nif"`
|
||||||
|
EmisorNombre string `json:"emisor_nombre"`
|
||||||
NumSerie string `json:"num_serie"`
|
NumSerie string `json:"num_serie"`
|
||||||
FechaExpedicion string `json:"fecha_expedicion"`
|
FechaExpedicion string `json:"fecha_expedicion"`
|
||||||
TipoFactura string `json:"tipo_factura"`
|
TipoFactura string `json:"tipo_factura"`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
#!/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}")
|
||||||
|
|
@ -97,14 +97,13 @@ type Reference2 struct {
|
||||||
type SOAPBody struct {
|
type SOAPBody struct {
|
||||||
XMLName xml.Name `xml:"soap:Body"`
|
XMLName xml.Name `xml:"soap:Body"`
|
||||||
Content interface{} `xml:",any"`
|
Content interface{} `xml:",any"`
|
||||||
Id string `xml:"wsu:Id,attr"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildSOAPEnvelope(payload interface{}) *SOAPEnvelope {
|
func BuildSOAPEnvelope(payload interface{}) *SOAPEnvelope {
|
||||||
return &SOAPEnvelope{
|
return &SOAPEnvelope{
|
||||||
XmlnsSOAP: "http://schemas.xmlsoap.org/soap/envelope/",
|
XmlnsSOAP: "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
XmlnsSUM: "https://www.agenciatributaria.gob.es/static/files/common/xsd/sum/information.xsd",
|
XmlnsSUM: "https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd",
|
||||||
XmlnsSUM1: "https://www.agenciatributaria.gob.es/static/files/common/xsd/sum/suministroInformacion.xsd",
|
XmlnsSUM1: "https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd",
|
||||||
Body: SOAPBody{
|
Body: SOAPBody{
|
||||||
Content: payload,
|
Content: payload,
|
||||||
},
|
},
|
||||||
|
|
@ -136,23 +135,11 @@ type EnviarAnulacionRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildAltaSOAPRequest(data AltaData) (*SOAPEnvelope, error) {
|
func BuildAltaSOAPRequest(data AltaData) (*SOAPEnvelope, error) {
|
||||||
altaReq := BuildAltaRequest(data)
|
return BuildSOAPEnvelope(BuildAltaRequest(data)), nil
|
||||||
|
|
||||||
env := BuildSOAPEnvelope(EnviarFacturaRequest{
|
|
||||||
AltaReq: altaReq,
|
|
||||||
})
|
|
||||||
|
|
||||||
return env, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildAnulacionSOAPRequest(data AltaData) (*SOAPEnvelope, error) {
|
func BuildAnulacionSOAPRequest(data AltaData) (*SOAPEnvelope, error) {
|
||||||
anulReq := BuildAnulacionRequest(data)
|
return BuildSOAPEnvelope(BuildAnulacionRequest(data)), nil
|
||||||
|
|
||||||
env := BuildSOAPEnvelope(EnviarAnulacionRequest{
|
|
||||||
AnulReq: anulReq,
|
|
||||||
})
|
|
||||||
|
|
||||||
return env, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseResponse(data []byte) (*Response, error) {
|
func ParseResponse(data []byte) (*Response, error) {
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type AltaRequest struct {
|
type AltaRequest struct {
|
||||||
Cabecera Cabecera `xml:"sum:RegFactuSistemaFacturacion>sum:Cabecera"`
|
XMLName xml.Name `xml:"sum:RegFactuSistemaFacturacion"`
|
||||||
RegistroAlta RegistroAlta `xml:"sum:RegFactuSistemaFacturacion>sum:RegistroFactura>sum1:RegistroAlta"`
|
Cabecera Cabecera `xml:"sum:Cabecera"`
|
||||||
|
RegistroFactura AltaRegistroFactura `xml:"sum:RegistroFactura"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AltaRegistroFactura struct {
|
||||||
|
RegistroAlta RegistroAlta `xml:"sum1:RegistroAlta"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Cabecera struct {
|
type Cabecera struct {
|
||||||
|
|
@ -26,6 +31,7 @@ type RegistroAlta struct {
|
||||||
NombreRazonEmisor string `xml:"sum1:NombreRazonEmisor"`
|
NombreRazonEmisor string `xml:"sum1:NombreRazonEmisor"`
|
||||||
TipoFactura string `xml:"sum1:TipoFactura"`
|
TipoFactura string `xml:"sum1:TipoFactura"`
|
||||||
DescripcionOperacion string `xml:"sum1:DescripcionOperacion"`
|
DescripcionOperacion string `xml:"sum1:DescripcionOperacion"`
|
||||||
|
Destinatarios *Destinatarios `xml:"sum1:Destinatarios,omitempty"`
|
||||||
Desglose Desglose `xml:"sum1:Desglose"`
|
Desglose Desglose `xml:"sum1:Desglose"`
|
||||||
CuotaTotal string `xml:"sum1:CuotaTotal"`
|
CuotaTotal string `xml:"sum1:CuotaTotal"`
|
||||||
ImporteTotal string `xml:"sum1:ImporteTotal"`
|
ImporteTotal string `xml:"sum1:ImporteTotal"`
|
||||||
|
|
@ -88,8 +94,13 @@ type SistemaInformatico struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnulacionRequest struct {
|
type AnulacionRequest struct {
|
||||||
Cabecera Cabecera `xml:"sum:RegFactuSistemaFacturacion>sum:Cabecera"`
|
XMLName xml.Name `xml:"sum:RegFactuSistemaFacturacion"`
|
||||||
RegistroAnulacion RegistroAnulacion `xml:"sum:RegFactuSistemaFacturacion>sum:RegistroFactura>sum1:RegistroAnulacion"`
|
Cabecera Cabecera `xml:"sum:Cabecera"`
|
||||||
|
RegistroFactura AnulacionRegistroFactura `xml:"sum:RegistroFactura"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnulacionRegistroFactura struct {
|
||||||
|
RegistroAnulacion RegistroAnulacion `xml:"sum1:RegistroAnulacion"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegistroAnulacion struct {
|
type RegistroAnulacion struct {
|
||||||
|
|
@ -115,8 +126,8 @@ type Response struct {
|
||||||
|
|
||||||
type ResponseBody struct {
|
type ResponseBody struct {
|
||||||
XMLName xml.Name `xml:"Body"`
|
XMLName xml.Name `xml:"Body"`
|
||||||
Fault *Fault `xml:"Fault,omitempty"`
|
Fault *Fault `xml:"Fault"`
|
||||||
RegistroRespuesta *RegistroRespuesta `xml:"RegistroRespuesta,omitempty"`
|
Respuesta *RespuestaRegFactu `xml:"RespuestaRegFactuSistemaFacturacion"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fault struct {
|
type Fault struct {
|
||||||
|
|
@ -124,9 +135,16 @@ type Fault struct {
|
||||||
FaultString string `xml:"faultstring"`
|
FaultString string `xml:"faultstring"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegistroRespuesta struct {
|
type RespuestaRegFactu struct {
|
||||||
CSV string `xml:"CSV"`
|
CSV string `xml:"CSV"`
|
||||||
Estado string `xml:"Estado"`
|
EstadoEnvio string `xml:"EstadoEnvio"`
|
||||||
|
RespuestaLineas []RespuestaLinea `xml:"RespuestaLinea"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RespuestaLinea struct {
|
||||||
|
EstadoRegistro string `xml:"EstadoRegistro"`
|
||||||
|
CodigoError string `xml:"CodigoErrorRegistro"`
|
||||||
|
DescripcionError string `xml:"DescripcionErrorRegistro"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AltaData struct {
|
type AltaData struct {
|
||||||
|
|
@ -145,6 +163,8 @@ type AltaData struct {
|
||||||
Huella string
|
Huella string
|
||||||
FechaGen time.Time
|
FechaGen time.Time
|
||||||
PrevHash string
|
PrevHash string
|
||||||
|
PrevNumSerie string
|
||||||
|
PrevFecha time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type IVARegularizacionData struct {
|
type IVARegularizacionData struct {
|
||||||
|
|
@ -166,7 +186,7 @@ type SistemaData struct {
|
||||||
|
|
||||||
func BuildAltaRequest(data AltaData) *AltaRequest {
|
func BuildAltaRequest(data AltaData) *AltaRequest {
|
||||||
fechaExp := data.FechaExpedicion.Format("02-01-2006")
|
fechaExp := data.FechaExpedicion.Format("02-01-2006")
|
||||||
fechaGen := data.FechaGen.Format("02-01-2006T15:04:05")
|
fechaGen := data.FechaGen.Format("2006-01-02T15:04:05-07:00")
|
||||||
|
|
||||||
ivaList := make([]DetalleDesglose, len(data.IVA))
|
ivaList := make([]DetalleDesglose, len(data.IVA))
|
||||||
for i, iva := range data.IVA {
|
for i, iva := range data.IVA {
|
||||||
|
|
@ -184,16 +204,24 @@ func BuildAltaRequest(data AltaData) *AltaRequest {
|
||||||
enc = Encadenamiento{PrimerRegistro: "S"}
|
enc = Encadenamiento{PrimerRegistro: "S"}
|
||||||
} else {
|
} else {
|
||||||
enc = Encadenamiento{
|
enc = Encadenamiento{
|
||||||
PrimerRegistro: "N",
|
|
||||||
RegistroAnterior: &RegistroAnterior{
|
RegistroAnterior: &RegistroAnterior{
|
||||||
IDEmisorFactura: data.EmisorNIF,
|
IDEmisorFactura: data.EmisorNIF,
|
||||||
NumSerieFactura: data.NumSerie,
|
NumSerieFactura: data.PrevNumSerie,
|
||||||
FechaExpedicionFactura: fechaExp,
|
FechaExpedicionFactura: data.PrevFecha.Format("02-01-2006"),
|
||||||
Huella: data.PrevHash,
|
Huella: data.PrevHash,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var destinatarios *Destinatarios
|
||||||
|
if data.DestinatarioNIF != "" {
|
||||||
|
destinatarios = &Destinatarios{
|
||||||
|
IDDestinatario: []IDDestinatario{
|
||||||
|
{NombreRazon: data.DestinatarioNombre, NIF: data.DestinatarioNIF},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
req := &AltaRequest{
|
req := &AltaRequest{
|
||||||
Cabecera: Cabecera{
|
Cabecera: Cabecera{
|
||||||
ObligadoEmision: ObligadoEmision{
|
ObligadoEmision: ObligadoEmision{
|
||||||
|
|
@ -201,6 +229,7 @@ func BuildAltaRequest(data AltaData) *AltaRequest {
|
||||||
NIF: data.EmisorNIF,
|
NIF: data.EmisorNIF,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
RegistroFactura: AltaRegistroFactura{
|
||||||
RegistroAlta: RegistroAlta{
|
RegistroAlta: RegistroAlta{
|
||||||
IDVersion: "1.0",
|
IDVersion: "1.0",
|
||||||
IDFactura: IDFactura{
|
IDFactura: IDFactura{
|
||||||
|
|
@ -211,6 +240,7 @@ func BuildAltaRequest(data AltaData) *AltaRequest {
|
||||||
NombreRazonEmisor: data.EmisorNombre,
|
NombreRazonEmisor: data.EmisorNombre,
|
||||||
TipoFactura: data.TipoFactura,
|
TipoFactura: data.TipoFactura,
|
||||||
DescripcionOperacion: data.Descripcion,
|
DescripcionOperacion: data.Descripcion,
|
||||||
|
Destinatarios: destinatarios,
|
||||||
Desglose: Desglose{
|
Desglose: Desglose{
|
||||||
DetalleDesglose: ivaList,
|
DetalleDesglose: ivaList,
|
||||||
},
|
},
|
||||||
|
|
@ -221,7 +251,7 @@ func BuildAltaRequest(data AltaData) *AltaRequest {
|
||||||
NombreRazon: data.Sistema.Nombre,
|
NombreRazon: data.Sistema.Nombre,
|
||||||
NIF: data.Sistema.NIFProveedor,
|
NIF: data.Sistema.NIFProveedor,
|
||||||
NombreSistemaInformatico: data.Sistema.NombreSistema,
|
NombreSistemaInformatico: data.Sistema.NombreSistema,
|
||||||
IdSistemaInformatico: "1",
|
IdSistemaInformatico: "01",
|
||||||
Version: data.Sistema.Version,
|
Version: data.Sistema.Version,
|
||||||
NumeroInstalacion: data.Sistema.NumeroInstalacion,
|
NumeroInstalacion: data.Sistema.NumeroInstalacion,
|
||||||
TipoUsoPosibleSoloVerifactu: data.Sistema.TipoUsoVerifactu,
|
TipoUsoPosibleSoloVerifactu: data.Sistema.TipoUsoVerifactu,
|
||||||
|
|
@ -229,9 +259,10 @@ func BuildAltaRequest(data AltaData) *AltaRequest {
|
||||||
IndicadorMultiplesOT: "N",
|
IndicadorMultiplesOT: "N",
|
||||||
},
|
},
|
||||||
FechaHoraHusoGenRegistro: fechaGen,
|
FechaHoraHusoGenRegistro: fechaGen,
|
||||||
TipoHuella: "SHA-256",
|
TipoHuella: "01",
|
||||||
Huella: data.Huella,
|
Huella: data.Huella,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return req
|
return req
|
||||||
|
|
@ -239,18 +270,17 @@ func BuildAltaRequest(data AltaData) *AltaRequest {
|
||||||
|
|
||||||
func BuildAnulacionRequest(data AltaData) *AnulacionRequest {
|
func BuildAnulacionRequest(data AltaData) *AnulacionRequest {
|
||||||
fechaExp := data.FechaExpedicion.Format("02-01-2006")
|
fechaExp := data.FechaExpedicion.Format("02-01-2006")
|
||||||
fechaGen := data.FechaGen.Format("02-01-2006T15:04:05")
|
fechaGen := data.FechaGen.Format("2006-01-02T15:04:05-07:00")
|
||||||
|
|
||||||
var enc Encadenamiento
|
var enc Encadenamiento
|
||||||
if data.PrevHash == "" {
|
if data.PrevHash == "" {
|
||||||
enc = Encadenamiento{PrimerRegistro: "S"}
|
enc = Encadenamiento{PrimerRegistro: "S"}
|
||||||
} else {
|
} else {
|
||||||
enc = Encadenamiento{
|
enc = Encadenamiento{
|
||||||
PrimerRegistro: "N",
|
|
||||||
RegistroAnterior: &RegistroAnterior{
|
RegistroAnterior: &RegistroAnterior{
|
||||||
IDEmisorFactura: data.EmisorNIF,
|
IDEmisorFactura: data.EmisorNIF,
|
||||||
NumSerieFactura: data.NumSerie,
|
NumSerieFactura: data.PrevNumSerie,
|
||||||
FechaExpedicionFactura: fechaExp,
|
FechaExpedicionFactura: data.PrevFecha.Format("02-01-2006"),
|
||||||
Huella: data.PrevHash,
|
Huella: data.PrevHash,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -263,7 +293,7 @@ func BuildAnulacionRequest(data AltaData) *AnulacionRequest {
|
||||||
NIF: data.EmisorNIF,
|
NIF: data.EmisorNIF,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
RegistroAnulacion: RegistroAnulacion{
|
RegistroFactura: AnulacionRegistroFactura{RegistroAnulacion: RegistroAnulacion{
|
||||||
IDVersion: "1.0",
|
IDVersion: "1.0",
|
||||||
IDFacturaAnulada: IDFacturaAnulada{
|
IDFacturaAnulada: IDFacturaAnulada{
|
||||||
IDEmisorFacturaAnulada: data.EmisorNIF,
|
IDEmisorFacturaAnulada: data.EmisorNIF,
|
||||||
|
|
@ -275,7 +305,7 @@ func BuildAnulacionRequest(data AltaData) *AnulacionRequest {
|
||||||
NombreRazon: data.Sistema.Nombre,
|
NombreRazon: data.Sistema.Nombre,
|
||||||
NIF: data.Sistema.NIFProveedor,
|
NIF: data.Sistema.NIFProveedor,
|
||||||
NombreSistemaInformatico: data.Sistema.NombreSistema,
|
NombreSistemaInformatico: data.Sistema.NombreSistema,
|
||||||
IdSistemaInformatico: "1",
|
IdSistemaInformatico: "01",
|
||||||
Version: data.Sistema.Version,
|
Version: data.Sistema.Version,
|
||||||
NumeroInstalacion: data.Sistema.NumeroInstalacion,
|
NumeroInstalacion: data.Sistema.NumeroInstalacion,
|
||||||
TipoUsoPosibleSoloVerifactu: data.Sistema.TipoUsoVerifactu,
|
TipoUsoPosibleSoloVerifactu: data.Sistema.TipoUsoVerifactu,
|
||||||
|
|
@ -283,9 +313,9 @@ func BuildAnulacionRequest(data AltaData) *AnulacionRequest {
|
||||||
IndicadorMultiplesOT: "N",
|
IndicadorMultiplesOT: "N",
|
||||||
},
|
},
|
||||||
FechaHoraHusoGenRegistro: fechaGen,
|
FechaHoraHusoGenRegistro: fechaGen,
|
||||||
TipoHuella: "SHA-256",
|
TipoHuella: "01",
|
||||||
Huella: data.Huella,
|
Huella: data.Huella,
|
||||||
},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
return req
|
return req
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue