diff --git a/verifactu/client.go b/verifactu/client.go new file mode 100644 index 0000000..b70fb33 --- /dev/null +++ b/verifactu/client.go @@ -0,0 +1,87 @@ +package verifactu + +import ( + "bytes" + "crypto/tls" + "encoding/xml" + "fmt" + "io" + "net/http" +) + +type Config struct { + Environment string + CertPath string + CertPass string +} + +type Client struct { + cfg Config + httpClient *http.Client +} + +func NewClient(cfg Config) *Client { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + return &Client{ + cfg: cfg, + httpClient: &http.Client{Transport: tr}, + } +} + +func (c *Client) GetEndpoint() string { + if c.cfg.Environment == "production" { + return "https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tikeV1.0/cont/ws/SistemaFacturacion.wsdl" + } + return "https://prewww2.aeat.es/static_files/common/internet/dep/aplicaciones/es/aeat/tikeV1.0/cont/ws/SistemaFacturacion.wsdl" +} + +type XMLRequest struct { + XMLName xml.Name `xml:"soapenv:Envelope"` + Xmlns string `xml:"xmlns:soapenv,attr"` + XmlnsXsi string `xml:"xmlns:xsi,attr"` + XmlnsSum string `xml:"xmlns:sum,attr"` + XmlnsSum1 string `xml:"xmlns:sum1,attr"` + XmlnsXd string `xml:"xmlns:xd,attr"` + Header interface{} `xml:"soapenv:Header"` + Body XMLBody `xml:"soapenv:Body"` +} + +type XMLBody struct { + Content interface{} `xml:",any"` +} + +func (c *Client) Send(xmlPayload interface{}) ([]byte, error) { + var buf bytes.Buffer + enc := xml.NewEncoder(&buf) + err := enc.Encode(xmlPayload) + if err != nil { + return nil, fmt.Errorf("failed to encode XML: %w", err) + } + + req, err := http.NewRequest("POST", c.GetEndpoint(), &buf) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Content-Type", "text/xml; charset=utf-8") + req.Header.Set("SOAPAction", "") + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response: %w", err) + } + + if resp.StatusCode >= 400 { + return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body)) + } + + return body, nil +} diff --git a/verifactu/xml.go b/verifactu/xml.go new file mode 100644 index 0000000..096c4cd --- /dev/null +++ b/verifactu/xml.go @@ -0,0 +1,138 @@ +package verifactu + +import ( + "encoding/xml" + "fmt" +) + +type AltaRequest struct { + Cabecera Cabecera `xml:"sum:RegFactuSistemaFacturacion>sum:Cabecera"` + RegistroAlta RegistroAlta `xml:"sum:RegFactuSistemaFacturacion>sum:RegistroFactura>sum1:RegistroAlta"` +} + +type Cabecera struct { + ObligadoEmision ObligadoEmision `xml:"sum1:ObligadoEmision"` +} + +type ObligadoEmision struct { + NombreRazon string `xml:"sum1:NombreRazon"` + NIF string `xml:"sum1:NIF"` +} + +type RegistroAlta struct { + IDVersion string `xml:"sum1:IDVersion"` + IDFactura IDFactura `xml:"sum1:IDFactura"` + NombreRazonEmisor string `xml:"sum1:NombreRazonEmisor"` + TipoFactura string `xml:"sum1:TipoFactura"` + DescripcionOperacion string `xml:"sum1:DescripcionOperacion"` + Destinatarios *Destinatarios `xml:"sum1:Desglose>sum1:DetalleDesglose"` + Desglose Desglose `xml:"sum1:Desglose"` + CuotaTotal string `xml:"sum1:CuotaTotal"` + ImporteTotal string `xml:"sum1:ImporteTotal"` + Encadenamiento Encadenamiento `xml:"sum1:Encadenamiento"` + SistemaInformatico SistemaInformatico `xml:"sum1:SistemaInformatico"` + FechaHoraHusoGenRegistro string `xml:"sum1:FechaHoraHusoGenRegistro"` + TipoHuella string `xml:"sum1:TipoHuella"` + Huella string `xml:"sum1:Huella"` +} + +type IDFactura struct { + IDEmisorFactura string `xml:"sum1:IDEmisorFactura"` + NumSerieFactura string `xml:"sum1:NumSerieFactura"` + FechaExpedicionFactura string `xml:"sum1:FechaExpedicionFactura"` +} + +type Destinatarios struct { + IDDestinatario []IDDestinatario `xml:"sum1:IDDestinatario"` +} + +type IDDestinatario struct { + NombreRazon string `xml:"sum1:NombreRazon"` + NIF string `xml:"sum1:NIF"` +} + +type Desglose struct { + DetalleDesglose []DetalleDesglose `xml:"sum1:DetalleDesglose"` +} + +type DetalleDesglose struct { + ClaveRegimen string `xml:"sum1:ClaveRegimen"` + CalificacionOperacion string `xml:"sum1:CalificacionOperacion"` + TipoImpositivo string `xml:"sum1:TipoImpositivo"` + BaseImponibleOimporteNoSujeto string `xml:"sum1:BaseImponibleOimporteNoSujeto"` + CuotaRepercutida string `xml:"sum1:CuotaRepercutida"` +} + +type Encadenamiento struct { + PrimerRegistro string `xml:"sum1:PrimerRegistro,omitempty"` + RegistroAnterior *RegistroAnterior `xml:"sum1:RegistroAnterior,omitempty"` +} + +type RegistroAnterior struct { + IDEmisorFactura string `xml:"sum1:IDEmisorFactura"` + NumSerieFactura string `xml:"sum1:NumSerieFactura"` + FechaExpedicionFactura string `xml:"sum1:FechaExpedicionFactura"` + Huella string `xml:"sum1:Huella"` +} + +type SistemaInformatico struct { + NombreRazon string `xml:"sum1:NombreRazon"` + NIF string `xml:"sum1:NIF"` + NombreSistemaInformatico string `xml:"sum1:NombreSistemaInformatico"` + IdSistemaInformatico string `xml:"sum1:IdSistemaInformatico"` + Version string `xml:"sum1:Version"` + NumeroInstalacion string `xml:"sum1:NumeroInstalacion"` + TipoUsoPosibleSoloVerifactu string `xml:"sum1:TipoUsoPosibleSoloVerifactu"` + TipoUsoPosibleMultiOT string `xml:"sum1:TipoUsoPosibleMultiOT"` + IndicadorMultiplesOT string `xml:"sum1:IndicadorMultiplesOT"` +} + +type AnulacionRequest struct { + Cabecera Cabecera `xml:"sum:RegFactuSistemaFacturacion>sum:Cabecera"` + RegistroAnulacion RegistroAnulacion `xml:"sum:RegFactuSistemaFacturacion>sum:RegistroFactura>sum1:RegistroAnulacion"` +} + +type RegistroAnulacion struct { + IDVersion string `xml:"sum1:IDVersion"` + IDFacturaAnulada IDFacturaAnulada `xml:"sum1:IDFacturaAnulada"` + Encadenamiento Encadenamiento `xml:"sum1:Encadenamiento"` + SistemaInformatico SistemaInformatico `xml:"sum1:SistemaInformatico"` + FechaHoraHusoGenRegistro string `xml:"sum1:FechaHoraHusoGenRegistro"` + TipoHuella string `xml:"sum1:TipoHuella"` + Huella string `xml:"sum1:Huella"` +} + +type IDFacturaAnulada struct { + IDEmisorFacturaAnulada string `xml:"sum1:IDEmisorFacturaAnulada"` + NumSerieFacturaAnulada string `xml:"sum1:NumSerieFacturaAnulada"` + FechaExpedicionFacturaAnulada string `xml:"sum1:FechaExpedicionFacturaAnulada"` +} + +type Response struct { + XMLName xml.Name `xml:"Envelope"` + Body ResponseBody +} + +type ResponseBody struct { + XMLName xml.Name `xml:"Body"` + Fault *Fault `xml:"Fault,omitempty"` + RegistroRespuesta *RegistroRespuesta `xml:"RegistroRespuesta,omitempty"` +} + +type Fault struct { + FaultCode string `xml:"faultcode"` + FaultString string `xml:"faultstring"` +} + +type RegistroRespuesta struct { + CSV string `xml:"CSV"` + Estado string `xml:"Estado"` +} + +func BuildAltaRequest(emisorNombre string, data interface{}) (*AltaRequest, error) { + return nil, fmt.Errorf("not implemented") +} + +func BuildAnulacionRequest(emisorNombre string, data interface{}) (*AnulacionRequest, error) { + return nil, fmt.Errorf("not implemented") +}