package soap import ( "bytes" "context" "encoding/xml" "io" "net/http" "time" "github.com/google/uuid" "github.com/pkg/errors" ) // Client es la interfaz para un cliente SOAP type Client interface { Call(ctx context.Context, operation string, request interface{}, response interface{}) error } // ClientOptions contiene opciones para el cliente SOAP type ClientOptions struct { Endpoint string Username string Password string Timeout time.Duration Headers map[string]string } // RequestIDKey es la clave para el ID de solicitud en el contexto type RequestIDKey struct{} // client implementa la interfaz Client //type client struct { // endpoint string // httpClient *http.Client // credentials *credentials // headers map[string]string //} // credentials contiene las credenciales para autenticación type credentials struct { username string password string } // Añadir este método al final del archivo pkg/soap/soap.go // En la definición del cliente: type client struct { endpoint string httpClient *http.Client credentials *credentials headers map[string]string } // Endpoint retorna el endpoint actual del cliente func (c *client) Endpoint() string { return c.endpoint } // NewClient crea un nuevo cliente SOAP func NewClient(options ClientOptions) Client { httpClient := &http.Client{ Timeout: options.Timeout, } var creds *credentials if options.Username != "" && options.Password != "" { creds = &credentials{ username: options.Username, password: options.Password, } } return &client{ endpoint: options.Endpoint, httpClient: httpClient, credentials: creds, headers: options.Headers, } } // Envelope es la estructura para el envelope SOAP type Envelope struct { XMLName xml.Name `xml:"Envelope"` XMLNS string `xml:"xmlns,attr"` Header interface{} `xml:"Header,omitempty"` Body Body `xml:"Body"` } // Body es la estructura para el cuerpo SOAP type Body struct { XMLName xml.Name `xml:"Body"` Content interface{} `xml:",omitempty"` Fault *Fault `xml:"Fault,omitempty"` } // Fault es la estructura para errores SOAP type Fault struct { XMLName xml.Name `xml:"Fault"` FaultCode string `xml:"faultcode,omitempty"` FaultString string `xml:"faultstring,omitempty"` Detail string `xml:"detail,omitempty"` } // Call realiza una llamada SOAP func (c *client) Call(ctx context.Context, operation string, request interface{}, response interface{}) error { // Obtener ID de solicitud del contexto o generar uno nuevo var requestID string if id, ok := ctx.Value(RequestIDKey{}).(string); ok { requestID = id } else { requestID = uuid.New().String() } // Crear envelope SOAP envelope := Envelope{ XMLNS: "http://schemas.xmlsoap.org/soap/envelope/", Body: Body{ Content: request, }, } // Serializar envelope a XML requestBuffer := &bytes.Buffer{} enc := xml.NewEncoder(requestBuffer) enc.Indent("", " ") if err := enc.Encode(envelope); err != nil { return errors.Wrap(err, "error encoding SOAP request") } // Crear solicitud HTTP httpReq, err := http.NewRequestWithContext( ctx, http.MethodPost, c.endpoint, requestBuffer, ) if err != nil { return errors.Wrap(err, "error creating HTTP request") } // Establecer cabeceras HTTP httpReq.Header.Set("Content-Type", "text/xml; charset=utf-8") httpReq.Header.Set("SOAPAction", operation) httpReq.Header.Set("X-Request-ID", requestID) // Agregar cabeceras adicionales for key, value := range c.headers { httpReq.Header.Set(key, value) } // Agregar autenticación si es necesario if c.credentials != nil { httpReq.SetBasicAuth(c.credentials.username, c.credentials.password) } // Realizar solicitud HTTP httpResp, err := c.httpClient.Do(httpReq) if err != nil { return errors.Wrap(err, "error sending HTTP request") } defer httpResp.Body.Close() // Leer respuesta respBody, err := io.ReadAll(httpResp.Body) if err != nil { return errors.Wrap(err, "error reading HTTP response") } // Verificar código de estado HTTP if httpResp.StatusCode != http.StatusOK { return errors.Errorf("HTTP error: %d - %s, body: %s", httpResp.StatusCode, httpResp.Status, string(respBody), ) } // Deserializar respuesta respEnvelope := &Envelope{ Body: Body{ Content: response, }, } if err := xml.Unmarshal(respBody, respEnvelope); err != nil { return errors.Wrap(err, "error unmarshalling SOAP response") } // Verificar si hay un SOAP Fault if respEnvelope.Body.Fault != nil { return errors.Errorf("SOAP Fault: %s - %s, detail: %s", respEnvelope.Body.Fault.FaultCode, respEnvelope.Body.Fault.FaultString, respEnvelope.Body.Fault.Detail, ) } return nil }