package services import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "time" "api-service/internal/config" "github.com/mashingan/smapping" ) // VClaimService interface for VClaim operations type VClaimService interface { Get(ctx context.Context, endpoint string, result interface{}) error Post(ctx context.Context, endpoint string, payload interface{}, result interface{}) error Put(ctx context.Context, endpoint string, payload interface{}, result interface{}) error Delete(ctx context.Context, endpoint string, result interface{}) error GetRawResponse(ctx context.Context, endpoint string) (*ResponDTO, error) PostRawResponse(ctx context.Context, endpoint string, payload interface{}) (*ResponDTO, error) } // Service struct for VClaim service type Service struct { config config.BpjsConfig httpClient *http.Client } // Response structures type ResponMentahDTO struct { MetaData struct { Code string `json:"code"` Message string `json:"message"` } `json:"metaData"` Response string `json:"response"` } type ResponDTO struct { MetaData struct { Code string `json:"code"` Message string `json:"message"` } `json:"metaData"` Response interface{} `json:"response"` } // NewService creates a new VClaim service instance func NewService(cfg config.BpjsConfig) VClaimService { service := &Service{ config: cfg, httpClient: &http.Client{ Timeout: cfg.Timeout, }, } return service } // NewServiceFromConfig creates service from main config func NewServiceFromConfig(cfg *config.Config) VClaimService { return NewService(cfg.Bpjs) } // NewServiceFromInterface creates service from interface (for backward compatibility) func NewServiceFromInterface(cfg interface{}) (VClaimService, error) { var bpjsConfig config.BpjsConfig // Try to map from interface err := smapping.FillStruct(&bpjsConfig, smapping.MapFields(&cfg)) if err != nil { return nil, fmt.Errorf("failed to map config: %w", err) } if bpjsConfig.Timeout == 0 { bpjsConfig.Timeout = 30 * time.Second } return NewService(bpjsConfig), nil } // SetHTTPClient allows custom http client configuration func (s *Service) SetHTTPClient(client *http.Client) { s.httpClient = client } // prepareRequest prepares HTTP request with required headers func (s *Service) prepareRequest(ctx context.Context, method, endpoint string, body io.Reader) (*http.Request, error) { fullURL := s.config.BaseURL + endpoint req, err := http.NewRequestWithContext(ctx, method, fullURL, body) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } // Set headers using the SetHeader method consID, _, userKey, tstamp, xSignature := s.config.SetHeader() req.Header.Set("Content-Type", "application/json") req.Header.Set("X-cons-id", consID) req.Header.Set("X-timestamp", tstamp) req.Header.Set("X-signature", xSignature) req.Header.Set("user_key", userKey) return req, nil } // processResponse processes response from VClaim API func (s *Service) processResponse(res *http.Response) (*ResponDTO, error) { defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } // Check HTTP status if res.StatusCode >= 400 { return nil, fmt.Errorf("HTTP error: %d - %s", res.StatusCode, string(body)) } // Parse raw response var respMentah ResponMentahDTO if err := json.Unmarshal(body, &respMentah); err != nil { return nil, fmt.Errorf("failed to unmarshal raw response: %w", err) } // Create final response finalResp := &ResponDTO{ MetaData: respMentah.MetaData, } // If response is empty, return as is if respMentah.Response == "" { return finalResp, nil } // Decrypt response consID, secretKey, _, tstamp, _ := s.config.SetHeader() respDecrypt, err := ResponseVclaim(respMentah.Response, consID+secretKey+tstamp) if err != nil { return nil, fmt.Errorf("failed to decrypt response: %w", err) } // Unmarshal decrypted response if respDecrypt != "" { if err := json.Unmarshal([]byte(respDecrypt), &finalResp.Response); err != nil { // If JSON unmarshal fails, store as string finalResp.Response = respDecrypt } } return finalResp, nil } // Get performs HTTP GET request func (s *Service) Get(ctx context.Context, endpoint string, result interface{}) error { resp, err := s.GetRawResponse(ctx, endpoint) if err != nil { return err } return mapToResult(resp, result) } // Post performs HTTP POST request func (s *Service) Post(ctx context.Context, endpoint string, payload interface{}, result interface{}) error { resp, err := s.PostRawResponse(ctx, endpoint, payload) if err != nil { return err } return mapToResult(resp, result) } // Put performs HTTP PUT request func (s *Service) Put(ctx context.Context, endpoint string, payload interface{}, result interface{}) error { var buf bytes.Buffer if payload != nil { if err := json.NewEncoder(&buf).Encode(payload); err != nil { return fmt.Errorf("failed to encode payload: %w", err) } } req, err := s.prepareRequest(ctx, http.MethodPut, endpoint, &buf) if err != nil { return err } res, err := s.httpClient.Do(req) if err != nil { return fmt.Errorf("failed to execute PUT request: %w", err) } resp, err := s.processResponse(res) if err != nil { return err } return mapToResult(resp, result) } // Delete performs HTTP DELETE request func (s *Service) Delete(ctx context.Context, endpoint string, result interface{}) error { req, err := s.prepareRequest(ctx, http.MethodDelete, endpoint, nil) if err != nil { return err } res, err := s.httpClient.Do(req) if err != nil { return fmt.Errorf("failed to execute DELETE request: %w", err) } resp, err := s.processResponse(res) if err != nil { return err } return mapToResult(resp, result) } // GetRawResponse returns raw response without mapping func (s *Service) GetRawResponse(ctx context.Context, endpoint string) (*ResponDTO, error) { req, err := s.prepareRequest(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, err } res, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to execute GET request: %w", err) } return s.processResponse(res) } // PostRawResponse returns raw response without mapping func (s *Service) PostRawResponse(ctx context.Context, endpoint string, payload interface{}) (*ResponDTO, error) { var buf bytes.Buffer if payload != nil { if err := json.NewEncoder(&buf).Encode(payload); err != nil { return nil, fmt.Errorf("failed to encode payload: %w", err) } } req, err := s.prepareRequest(ctx, http.MethodPost, endpoint, &buf) if err != nil { return nil, err } res, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to execute POST request: %w", err) } return s.processResponse(res) } // mapToResult maps the final response to the result interface func mapToResult(resp *ResponDTO, result interface{}) error { respBytes, err := json.Marshal(resp) if err != nil { return fmt.Errorf("failed to marshal final response: %w", err) } if err := json.Unmarshal(respBytes, result); err != nil { return fmt.Errorf("failed to unmarshal to result: %w", err) } return nil } // Backward compatibility functions func GetRequest(endpoint string, cfg interface{}) interface{} { service, err := NewServiceFromInterface(cfg) if err != nil { fmt.Printf("Failed to create service: %v\n", err) return nil } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() resp, err := service.GetRawResponse(ctx, endpoint) if err != nil { fmt.Printf("Failed to get response: %v\n", err) return nil } return resp } func PostRequest(endpoint string, cfg interface{}, data interface{}) interface{} { service, err := NewServiceFromInterface(cfg) if err != nil { fmt.Printf("Failed to create service: %v\n", err) return nil } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() resp, err := service.PostRawResponse(ctx, endpoint, data) if err != nil { fmt.Printf("Failed to post response: %v\n", err) return nil } return resp }