package internal import ( "log" "time" "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 } type AltaInput struct { InvoiceInput EmisorNombre string } 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(input AltaInput) (*AltaOutput, error) { defer func() { if r := recover(); r != nil { log.Printf("PANIC: %v", r) } }() errs := ValidateInvoiceInput(&input.InvoiceInput) if len(errs) > 0 { errMsg := "" for _, e := range errs { errMsg += e.Error() + "; " } log.Printf("validation errors: %s", errMsg) return &AltaOutput{ Success: false, Error: "validation_failed: " + errMsg, }, nil } data, err := TransformToInvoiceData(&input.InvoiceInput) if err != nil { log.Printf("transform error: %v", err) return &AltaOutput{ Success: false, Error: err.Error(), }, nil } prevHash := "" if s.hashStorage != nil { record, err := s.hashStorage.GetLastRecord(input.Factura.EmisorNIF) if err != nil { return &AltaOutput{ Success: false, Error: "hash_storage_error", }, nil } if record != nil { prevHash = record.Huella } } 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 := ToAltaData(&input.InvoiceInput, data, currentHash, prevHash) if s.verifactu != nil { log.Printf("Sending to AEAT...") resp, err := s.verifactu.SendAlta(altaData) if err != nil { log.Printf("AEAT error: %v", err) log.Printf("AEAT error, falling back to local: %v", err) goto saveLocal } if resp.Body.Fault != nil { log.Printf("AEAT fault, falling back to local: %v", resp.Body.Fault.FaultString) goto saveLocal } if resp.Body.RegistroRespuesta != nil { if resp.Body.RegistroRespuesta.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: resp.Body.RegistroRespuesta.CSV, Estado: resp.Body.RegistroRespuesta.Estado, }, nil } return &AltaOutput{ Success: false, Error: "aeat_error:" + resp.Body.RegistroRespuesta.Estado, }, nil } } saveLocal: 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: currentHash, Estado: "Correcto (local)", }, nil } 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 == "" { 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 } func ToAltaData(in *InvoiceInput, data *InvoiceData, huella, prevHash string) verifactu.AltaData { ivaData := make([]verifactu.IVARegularizacionData, len(data.IVA)) for i, iva := range data.IVA { ivaData[i] = verifactu.IVARegularizacionData{ Base: iva.Base, Cuota: iva.Cuota, Tipo: iva.Tipo, ClaveRegimen: iva.ClaveRegimen, Calificacion: iva.Calificacion, } } destNombre := "" destNIF := "" if data.Destinatario != nil { destNombre = data.Destinatario.Nombre destNIF = data.Destinatario.NIF } return verifactu.AltaData{ EmisorNombre: in.Factura.EmisorNIF, EmisorNIF: data.EmisorNIF, NumSerie: data.NumSerie, FechaExpedicion: data.Fecha, TipoFactura: data.TipoFactura, Descripcion: data.Descripcion, DestinatarioNombre: destNombre, DestinatarioNIF: destNIF, IVA: ivaData, 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: huella, PrevHash: prevHash, FechaGen: data.FechaGen, } }