package handlers import ( "encoding/json" "fmt" "io" "net/http" "os" "strings" "time" ) // VismaHandler hanterar riktig Visma-integration type VismaHandler struct { clientID string clientSecret string redirectURI string accessToken string refreshToken string tokenExpiry time.Time client *http.Client } // NewVismaHandler skapar en ny handler func NewVismaHandler() *VismaHandler { return &VismaHandler{ clientID: os.Getenv("VISMA_CLIENT_ID"), clientSecret: os.Getenv("VISMA_CLIENT_SECRET"), redirectURI: os.Getenv("VISMA_REDIRECT_URI"), client: &http.Client{Timeout: 30 * time.Second}, } } // IsConfigured returnerar true om Visma är konfigurerat func (h *VismaHandler) IsConfigured() bool { return h.clientID != "" && h.clientSecret != "" } // VismaCompany representerar ett Visma-företag type VismaCompany struct { ID string `json:"id"` Name string `json:"name"` OrgNumber string `json:"organisationNumber"` } // VismaVoucher representerar ett Visma-verifikat type VismaVoucher struct { ID string `json:"id"` VoucherDate string `json:"voucherDate"` Text string `json:"text"` Rows []VismaRow `json:"rows"` Modified time.Time `json:"modifiedUtc"` } // VismaRow representerar en verifikatrad type VismaRow struct { AccountID string `json:"accountId"` AccountName string `json:"accountName"` DebitAmount float64 `json:"debitAmount"` CreditAmount float64 `json:"creditAmount"` Description string `json:"description"` } // GetAuthURL returnerar Visma OAuth URL func (h *VismaHandler) GetAuthURL(w http.ResponseWriter, r *http.Request) { if !h.IsConfigured() { writeJSON(w, http.StatusServiceUnavailable, map[string]interface{}{ "ok": false, "error": "Visma not configured", }) return } authURL := fmt.Sprintf( "https://eaccountingapi.vismaonline.com/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&scope=ea:api", h.clientID, h.redirectURI, ) writeJSON(w, http.StatusOK, map[string]interface{}{ "ok": true, "auth_url": authURL, }) } // HandleCallback hanterar Visma OAuth callback func (h *VismaHandler) HandleCallback(w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") if code == "" { writeJSON(w, http.StatusBadRequest, map[string]interface{}{ "ok": false, "error": "missing code", }) return } // Byt kod mot token tokenURL := "https://eaccountingapi.vismaonline.com/oauth/token" reqBody := fmt.Sprintf("grant_type=authorization_code&code=%s&redirect_uri=%s&client_id=%s&client_secret=%s", code, h.redirectURI, h.clientID, h.clientSecret) req, err := http.NewRequest("POST", tokenURL, strings.NewReader(reqBody)) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "ok": false, "error": err.Error(), }) return } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := h.client.Do(req) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "ok": false, "error": err.Error(), }) return } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "ok": false, "error": err.Error(), }) return } var tokenResp struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` ExpiresIn int `json:"expires_in"` } if err := json.Unmarshal(body, &tokenResp); err != nil { writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "ok": false, "error": err.Error(), }) return } h.accessToken = tokenResp.AccessToken h.refreshToken = tokenResp.RefreshToken h.tokenExpiry = time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second) writeJSON(w, http.StatusOK, map[string]interface{}{ "ok": true, "authenticated": true, "expires": h.tokenExpiry, }) } // GetCompanies hämtar företag från Visma func (h *VismaHandler) GetCompanies(w http.ResponseWriter, r *http.Request) { if h.accessToken == "" { writeJSON(w, http.StatusUnauthorized, map[string]interface{}{ "ok": false, "error": "not authenticated", }) return } req, err := http.NewRequest("GET", "https://eaccountingapi.vismaonline.com/v2/companysettings", nil) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "ok": false, "error": err.Error(), }) return } req.Header.Set("Authorization", "Bearer "+h.accessToken) req.Header.Set("Accept", "application/json") resp, err := h.client.Do(req) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "ok": false, "error": err.Error(), }) return } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "ok": false, "error": err.Error(), }) return } if resp.StatusCode != http.StatusOK { writeJSON(w, resp.StatusCode, map[string]interface{}{ "ok": false, "error": fmt.Sprintf("Visma API error: %s", string(body)), "status": resp.StatusCode, }) return } var companies []VismaCompany if err := json.Unmarshal(body, &companies); err != nil { writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "ok": false, "error": err.Error(), }) return } writeJSON(w, http.StatusOK, map[string]interface{}{ "ok": true, "companies": companies, }) } // GetVouchers hämtar verifikat från Visma func (h *VismaHandler) GetVouchers(w http.ResponseWriter, r *http.Request) { if h.accessToken == "" { writeJSON(w, http.StatusUnauthorized, map[string]interface{}{ "ok": false, "error": "not authenticated", }) return } // Hämta vouchers från Visma API req, err := http.NewRequest("GET", "https://eaccountingapi.vismaonline.com/v2/vouchers", nil) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "ok": false, "error": err.Error(), }) return } req.Header.Set("Authorization", "Bearer "+h.accessToken) req.Header.Set("Accept", "application/json") resp, err := h.client.Do(req) if err != nil { writeJSON(w, http.StatusServiceUnavailable, map[string]interface{}{ "ok": false, "error": fmt.Sprintf("Visma API error: %v", err), }) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) writeJSON(w, resp.StatusCode, map[string]interface{}{ "ok": false, "error": fmt.Sprintf("Visma API returned %d: %s", resp.StatusCode, string(body)), }) return } var vouchers []VismaVoucher if err := json.NewDecoder(resp.Body).Decode(&vouchers); err != nil { writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "ok": false, "error": "Failed to decode Visma response", }) return } writeJSON(w, http.StatusOK, map[string]interface{}{ "ok": true, "vouchers": vouchers, "source": "visma", }) } // GetStatus returnerar Visma-kopplingsstatus func (h *VismaHandler) GetStatus(w http.ResponseWriter, r *http.Request) { status := map[string]interface{}{ "configured": h.IsConfigured(), "authenticated": h.accessToken != "", "client_id": h.clientID, "token_expiry": h.tokenExpiry, } if !h.IsConfigured() { status["setup_url"] = "/api/v1/visma/auth" } writeJSON(w, http.StatusOK, map[string]interface{}{ "ok": true, "visma": status, }) }