package verifactu import ( "bytes" "crypto/tls" "crypto/x509" "fmt" "io" "log" "net/http" "os" "os/exec" "path/filepath" "time" ) type Client struct { BaseURL string HTTPClient *http.Client Certificate *tls.Certificate } type ClientConfig struct { BaseURL string Timeout time.Duration CertificatePath string CertificatePassword string } func NewClient(cfg ClientConfig) (*Client, error) { httpClient := &http.Client{ Timeout: cfg.Timeout, Transport: &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: false, }, }, } var cert *tls.Certificate if cfg.CertificatePath != "" { c, err := LoadCertificate(cfg.CertificatePath, cfg.CertificatePassword) if err != nil { return nil, fmt.Errorf("loading certificate: %w", err) } cert = c } return &Client{ BaseURL: cfg.BaseURL, HTTPClient: httpClient, Certificate: cert, }, nil } func LoadCertificate(certPath, password string) (*tls.Certificate, error) { dir := filepath.Dir(certPath) keyPath := filepath.Join(dir, "cert_key.pem") certPath2 := filepath.Join(dir, "cert_cert.pem") pyScript := "C:\\Users\\jmest\\GolandProjects\\VerifactuMidAPI\\convert_cert.py" cmd := exec.Command("python", pyScript, certPath, password, keyPath, certPath2) out, err := cmd.CombinedOutput() log.Printf("cert convert: out=%s err=%v", string(out), err) if err != nil { return nil, fmt.Errorf("converting: %w - %s", err, string(out)) } certData, err := os.ReadFile(certPath2) if err != nil { return nil, fmt.Errorf("reading cert: %w", err) } keyData, err := os.ReadFile(keyPath) if err != nil { return nil, fmt.Errorf("reading key: %w", err) } cert, err := tls.X509KeyPair(certData, keyData) if err != nil { return nil, fmt.Errorf("parsing: %w", err) } log.Printf("cert loaded: has private key=%v", cert.PrivateKey != nil) return &cert, nil } func (c *Client) SetCertificate(certPath, password string) error { cert, err := LoadCertificate(certPath, password) if err != nil { return err } c.Certificate = cert return nil } func (c *Client) SendAlta(data AltaData) (*Response, error) { env, err := BuildAltaSOAPRequest(data) if err != nil { return nil, fmt.Errorf("building Alta request: %w", err) } return c.SendRequest(env) } func (c *Client) SendAnulacion(data AltaData) (*Response, error) { env, err := BuildAnulacionSOAPRequest(data) if err != nil { return nil, fmt.Errorf("building Anulacion request: %w", err) } return c.SendRequest(env) } func (c *Client) SendRequest(env *SOAPEnvelope) (*Response, error) { body, err := env.ToBytes() if err != nil { return nil, fmt.Errorf("marshaling request: %w", err) } req, err := http.NewRequest("POST", c.BaseURL, bytes.NewReader(body)) if err != nil { return nil, fmt.Errorf("creating request: %w", err) } req.Header.Set("Content-Type", "text/xml; charset=utf-8") req.Header.Set("SOAPAction", "") if c.Certificate != nil { log.Printf("Using client certificate with TLS") tlsConfig := &tls.Config{ Certificates: []tls.Certificate{*c.Certificate}, InsecureSkipVerify: true, } c.HTTPClient.Transport = &http.Transport{ TLSClientConfig: tlsConfig, } } resp, err := c.HTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("sending request: %w", err) } defer resp.Body.Close() log.Printf("AEAT response status: %d", resp.StatusCode) respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("reading response: %w", err) } if resp.StatusCode != 200 { return nil, fmt.Errorf("HTTP error: %d - %s", resp.StatusCode, string(respBody)) } response, err := ParseResponse(respBody) if err != nil { return nil, fmt.Errorf("parsing response: %w", err) } return response, nil } func (c *Client) SetRootCA(caPath string) error { caData, err := os.ReadFile(caPath) if err != nil { return fmt.Errorf("reading CA file: %w", err) } pool := x509.NewCertPool() pool.AppendCertsFromPEM(caData) if transport, ok := c.HTTPClient.Transport.(*http.Transport); ok { if transport.TLSClientConfig == nil { transport.TLSClientConfig = &tls.Config{} } transport.TLSClientConfig.RootCAs = pool } return nil } func GetTestURL() string { return "https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP" } func GetProdURL() string { return "https://www1.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP" }