package api import ( "encoding/base64" "encoding/json" "fmt" "io" "log" "net/http" "VerifactuMidAPI/internal" "VerifactuMidAPI/internal/cert" "VerifactuMidAPI/internal/config" "VerifactuMidAPI/internal/crypto" "VerifactuMidAPI/internal/formats" ) type RegisterInput struct { CertName string `json:"cert_name"` CertNamePascal string `json:"CertName"` CertFile string `json:"cert_file"` CertFilePascal string `json:"CertFile"` PasswordEncrypted string `json:"password_encrypted"` PasswordPascal string `json:"PasswordEncrypted"` } func (r RegisterInput) CertNameResolved() string { if r.CertName != "" { return r.CertName } return r.CertNamePascal } func (r RegisterInput) CertFileResolved() string { if r.CertFile != "" { return r.CertFile } return r.CertFilePascal } func (r RegisterInput) PasswordResolved() string { if r.PasswordEncrypted != "" { return r.PasswordEncrypted } return r.PasswordPascal } type Handler struct { cfg *config.Config cert *cert.Storage crypto *crypto.KeyPair facturaSvc *internal.FacturaService } func New(cfg *config.Config, certStorage *cert.Storage, keyPair *crypto.KeyPair, facturaSvc *internal.FacturaService) *Handler { return &Handler{ cfg: cfg, cert: certStorage, crypto: keyPair, facturaSvc: facturaSvc, } } func (h *Handler) GetPublicKey(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } pubPEM, err := h.crypto.PublicKeyPEM() if err != nil { http.Error(w, "failed to get public key", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write([]byte(fmt.Sprintf(`{"public_key":"%s"}`, base64.StdEncoding.EncodeToString(pubPEM)))) } func (h *Handler) RegisterCert(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "failed to read body", http.StatusBadRequest) return } var input RegisterInput if err := json.Unmarshal(body, &input); err != nil { log.Printf("[RegisterCert] ERROR: JSON invalido: %v", err) w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"success":false,"error":"invalid_json"}`)) return } certName := input.CertNameResolved() certFileB64 := input.CertFileResolved() passwordEnc := input.PasswordResolved() log.Printf("[RegisterCert] Inicio: cert_name=%q, cert_file_len=%d, password_len=%d", certName, len(certFileB64), len(passwordEnc)) if certName == "" || certFileB64 == "" || passwordEnc == "" { log.Printf("[RegisterCert] ERROR: campos vacios") w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"success":false,"error":"missing_fields"}`)) return } certBytes, err := base64.StdEncoding.DecodeString(certFileB64) if err != nil { log.Printf("[RegisterCert] ERROR: base64 del cert invalido: %v", err) w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"success":false,"error":"invalid_cert_file"}`)) return } log.Printf("[RegisterCert] Certificado decodificado: %d bytes", len(certBytes)) decodedPass, err := base64.StdEncoding.DecodeString(passwordEnc) if err != nil { log.Printf("[RegisterCert] ERROR: base64 contrasena invalido: %v", err) w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"success":false,"error":"invalid_password_encrypted"}`)) return } plainPassBytes, err := h.crypto.Decrypt(decodedPass) if err != nil { log.Printf("[RegisterCert] ERROR: descifrado RSA fallido: %v", err) w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"success":false,"error":"decrypt_failed"}`)) return } log.Printf("[RegisterCert] Contrasena descifrada correctamente") plainPass := string(plainPassBytes) log.Printf("[RegisterCert] Validando certificado P12...") validation := cert.ValidateP12Bytes(certBytes, plainPass) if !validation.Valid { log.Printf("[RegisterCert] ERROR validacion: %s", validation.Error) resp, _ := json.Marshal(map[string]interface{}{ "success": false, "error": validation.Error, "cert": validation.CertInfo, }) w.Header().Set("Content-Type", "application/json") w.Write(resp) return } log.Printf("[RegisterCert] Certificado valido. Dias hasta expiracion: %d, warnings: %v", validation.CertInfo.DaysUntilExpiry, validation.Warnings) tempPath, err := h.cert.StoreFromBase64(certName, certFileB64) if err != nil { log.Printf("[RegisterCert] ERROR StoreFromBase64: %v", err) h.cert.DeleteTemp(tempPath) w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"success":false,"error":"temp_storage_failed"}`)) return } log.Printf("[RegisterCert] Certificado guardado en temporal: %s", tempPath) storedPath, err := h.cert.MoveToPerm(certName, tempPath) if err != nil { log.Printf("[RegisterCert] ERROR MoveToPerm: %v", err) h.cert.DeleteTemp(tempPath) w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"success":false,"error":"storage_failed"}`)) return } log.Printf("[RegisterCert] Certificado almacenado definitivamente: %s", storedPath) tokenData, err := h.cert.GenerateToken(certName, storedPath, plainPass) if err != nil { log.Printf("[RegisterCert] ERROR GenerateToken: %v", err) w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"success":false,"error":"token_generation_failed"}`)) return } log.Printf("[RegisterCert] Token generado: %s", tokenData.Token) if err := h.facturaSvc.ReloadCertificate(storedPath, plainPass); err != nil { log.Printf("[RegisterCert] WARNING: no se pudo recargar el certificado en el cliente VeriFactu: %v", err) } else { log.Printf("[RegisterCert] Certificado recargado en el cliente VeriFactu") } if len(validation.Warnings) > 0 { log.Printf("[RegisterCert] OK con warnings: %v", validation.Warnings) resp, _ := json.Marshal(map[string]interface{}{ "success": true, "cert": validation.CertInfo, "token": tokenData.Token, "warnings": validation.Warnings, }) w.Header().Set("Content-Type", "application/json") w.Write(resp) return } log.Printf("[RegisterCert] OK: certificado %q registrado", certName) resp, _ := json.Marshal(map[string]interface{}{ "success": true, "cert": validation.CertInfo, "token": tokenData.Token, }) w.Header().Set("Content-Type", "application/json") w.Write(resp) } func (h *Handler) HandleFacturas(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "failed to read body", http.StatusBadRequest) return } log.Printf("facturas request: %s", string(body)) output, _ := h.facturaSvc.ProcessAlta(body) log.Printf("facturas output:%+v", output) resp, _ := json.Marshal(output) w.Header().Set("Content-Type", "application/json") w.Write(resp) } func (h *Handler) HandleFacturasAnular(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "failed to read body", http.StatusBadRequest) return } var input internal.AnulacionInput if err := json.Unmarshal(body, &input); err != nil { w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"success":false,"error":"invalid_json"}`)) return } output, _ := h.facturaSvc.ProcessAnulacion(input) resp, _ := json.Marshal(output) w.Header().Set("Content-Type", "application/json") w.Write(resp) } func (h *Handler) ListFormats(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } available := formats.Available() resp, _ := json.Marshal(map[string]interface{}{ "formats": available, }) w.Header().Set("Content-Type", "application/json") w.Write(resp) }