package internal import ( "encoding/json" "fmt" "log" "time" "VerifactuMidAPI/internal/formats" "VerifactuMidAPI/verifactu" ) type FacturaService struct { hashStorage LastRecordStorage verifactu *verifactu.Client } func NewFacturaService(storage LastRecordStorage) *FacturaService { return &FacturaService{ hashStorage: storage, } } func (s *FacturaService) SetVerifactuClient(client *verifactu.Client) { s.verifactu = client } func (s *FacturaService) ReloadCertificate(path, password string) error { if s.verifactu == nil { return fmt.Errorf("verifactu client not initialized") } return s.verifactu.SetCertificate(path, password) } type AltaOutput struct { Success bool `json:"success"` CSV string `json:"csv,omitempty"` Estado string `json:"estado,omitempty"` Error string `json:"error,omitempty"` } func (s *FacturaService) ProcessAlta(raw json.RawMessage) (*AltaOutput, error) { defer func() { if r := recover(); r != nil { log.Printf("PANIC: %v", r) } }() result, formatName, err := formats.TransformAuto(raw) if err != nil { return &AltaOutput{ Success: false, Error: "transform_error: " + err.Error(), }, nil } log.Printf("[Transform] formato=%s NumSerie=%q Fecha=%q Total=%.2f IVA=%d tramos", formatName, result.NumSerie, result.FechaExpedicion, result.ImporteTotal, len(result.IVA)) if s.verifactu != nil { if result.EmisorNIF == "" { nif, nombre := s.verifactu.CertSubject() result.EmisorNIF = nif if result.EmisorNombre == "" { result.EmisorNombre = nombre } } if result.Sistema.NIFProveedor == "" { result.Sistema.NIFProveedor = result.EmisorNIF } } if result.Sistema.Nombre == "" { result.Sistema.Nombre = "VerifactuMidAPI" } if result.Sistema.Version == "" { result.Sistema.Version = "1.0" } log.Printf("[Emisor] NIF=%q Nombre=%q", result.EmisorNIF, result.EmisorNombre) log.Printf("[Sistema] Nombre=%q NIFProveedor=%q Version=%q", result.Sistema.Nombre, result.Sistema.NIFProveedor, result.Sistema.Version) return s.processInvoiceData(result) } func (s *FacturaService) processInvoiceData(result *formats.TransformResult) (*AltaOutput, error) { fecha, _ := time.Parse("02-01-2006", result.FechaExpedicion) ivaList := make([]IVARegularizacion, len(result.IVA)) for i, v := range result.IVA { ivaList[i] = IVARegularizacion{ Base: v.Base, Cuota: v.Cuota, Tipo: v.Tipo, ClaveRegimen: "01", Calificacion: "S1", } } cuotaTotal := 0.0 for _, v := range result.IVA { cuotaTotal += v.Cuota } var dest *Destinatario if result.Destinatario != nil { dest = &Destinatario{ Nombre: result.Destinatario.Nombre, NIF: result.Destinatario.NIF, } } data := &InvoiceData{ Tipo: "alta", EmisorNIF: result.EmisorNIF, NumSerie: result.NumSerie, Fecha: fecha, TipoFactura: result.TipoFactura, Descripcion: result.Descripcion, Destinatario: dest, IVA: ivaList, CuotaTotal: cuotaTotal, ImporteTotal: result.ImporteTotal, Sistema: Sistema{ Nombre: result.Sistema.Nombre, NIFProveedor: result.Sistema.NIFProveedor, NombreSistema: result.Sistema.Nombre, IDSistema: "01", Version: result.Sistema.Version, NumeroInstalacion: "1", TipoUsoVerifactu: "S", TipoUsoMultiOT: "N", IndicadorMultiOT: "N", }, FechaGen: time.Now(), } var prevHash, prevNumSerie string var prevFecha time.Time if s.hashStorage != nil { record, err := s.hashStorage.GetLastRecord(result.EmisorNIF) if err != nil { return &AltaOutput{ Success: false, Error: "hash_storage_error", }, nil } if record != nil { prevHash = record.Huella prevNumSerie = record.NumSerie prevFecha = record.Fecha } } hashService := NewHashService() currentHash := hashService.CalculateHash(data, prevHash) now := time.Now() lastRecord := &LastRecord{ EmisorNIF: data.EmisorNIF, NumSerie: data.NumSerie, Fecha: data.Fecha, Huella: currentHash, FechaGen: now, } data.Huella = currentHash data.FechaGen = now altaData := verifactu.AltaData{ EmisorNombre: result.EmisorNombre, EmisorNIF: data.EmisorNIF, NumSerie: data.NumSerie, FechaExpedicion: data.Fecha, TipoFactura: data.TipoFactura, Descripcion: data.Descripcion, DestinatarioNombre: destNombre(data.Destinatario), DestinatarioNIF: destNIF(data.Destinatario), IVA: toIVAData(data.IVA), CuotaTotal: data.CuotaTotal, ImporteTotal: data.ImporteTotal, Sistema: verifactu.SistemaData{ Nombre: data.Sistema.Nombre, NIFProveedor: data.Sistema.NIFProveedor, NombreSistema: data.Sistema.NombreSistema, Version: data.Sistema.Version, NumeroInstalacion: data.Sistema.NumeroInstalacion, TipoUsoVerifactu: data.Sistema.TipoUsoVerifactu, }, Huella: currentHash, PrevHash: prevHash, PrevNumSerie: prevNumSerie, PrevFecha: prevFecha, FechaGen: data.FechaGen, } if s.verifactu == nil { return &AltaOutput{ Success: false, Error: "verifactu_not_configured: no hay certificado registrado", }, nil } log.Printf("Sending to AEAT...") resp, err := s.verifactu.SendAlta(altaData) if err != nil { log.Printf("AEAT error: %v", err) return &AltaOutput{ Success: false, Error: "aeat_connection_error: " + err.Error(), }, nil } if resp.Body.Fault != nil { log.Printf("AEAT fault: %v", resp.Body.Fault.FaultString) return &AltaOutput{ Success: false, Error: "aeat_fault: " + resp.Body.Fault.FaultString, }, nil } if resp.Body.Respuesta == nil { return &AltaOutput{ Success: false, Error: "aeat_error: respuesta vacĂ­a", }, nil } 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 err := s.hashStorage.SaveLastRecord(lastRecord); err != nil { return &AltaOutput{ Success: false, Error: "hash_save_error", }, nil } } return &AltaOutput{ Success: true, CSV: csv, Estado: "Correcto", }, 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{ Success: false, Error: "aeat_error: " + errMsg, }, nil } func destNombre(d *Destinatario) string { if d == nil { return "" } return d.Nombre } func destNIF(d *Destinatario) string { if d == nil { return "" } return d.NIF } func toIVAData(list []IVARegularizacion) []verifactu.IVARegularizacionData { out := make([]verifactu.IVARegularizacionData, len(list)) for i, v := range list { out[i] = verifactu.IVARegularizacionData{ Base: v.Base, Cuota: v.Cuota, Tipo: v.Tipo, ClaveRegimen: v.ClaveRegimen, Calificacion: v.Calificacion, } } return out } type AnulacionInput struct { InvoiceInput EmisorNombre string } type AnulacionOutput struct { Success bool `json:"success"` CSV string `json:"csv,omitempty"` Estado string `json:"estado,omitempty"` Error string `json:"error,omitempty"` } func (s *FacturaService) ProcessAnulacion(input AnulacionInput) (*AnulacionOutput, error) { if input.Factura.EmisorNIF == "" && s.verifactu != nil { nif, nombre := s.verifactu.CertSubject() input.Factura.EmisorNIF = nif if input.EmisorNombre == "" { input.EmisorNombre = nombre } } if input.Factura.EmisorNIF == "" { return &AnulacionOutput{ Success: false, Error: "emisor_nif_required", }, nil } if input.Factura.NumSerie == "" { return &AnulacionOutput{ Success: false, Error: "num_serie_required", }, nil } if input.Factura.FechaExpedicion == "" { return &AnulacionOutput{ Success: false, Error: "fecha_expedicion_required", }, nil } return &AnulacionOutput{ Success: true, Estado: "Anulada", }, nil }