package services import ( "bytes" "encoding/json" "fmt" "io" "io/ioutil" "net/http" "time" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" "gorm.io/gorm" ) // Modelo que refleja la tabla en la base de datos type DlocalApi struct { ID uint `gorm:"primaryKey"` Modo string AccessKeyID string AccessKeySecret string AccessKeyIDdev string AccessKeySecretdev string UrlProd string UrlDev string } // Estructura de configuración para usar en la llamada type dlocalConfig struct { Modo string AccessKeyID string AccessKeySecret string AccessKeyIDdev string AccessKeySecretdev string UrlProd string UrlDev string } // Obtiene la configuración desde la base de datos func GetDlocalConfigFromDB(db *gorm.DB) (dlocalConfig, error) { var api DlocalApi if err := db.First(&api).Error; err != nil { return dlocalConfig{}, err } return dlocalConfig{ Modo: api.Modo, AccessKeyID: api.AccessKeyID, AccessKeySecret: api.AccessKeySecret, AccessKeyIDdev: api.AccessKeyIDdev, AccessKeySecretdev: api.AccessKeySecretdev, UrlProd: api.UrlProd, UrlDev: api.UrlDev, }, nil } type PlanRequest struct { Name string `json:"name"` Description string `json:"description"` Country string `json:"country,omitempty"` Currency string `json:"currency"` Amount float64 `json:"amount"` FrequencyType string `json:"frequency_type"` FrequencyValue int `json:"frequency_value,omitempty"` DayOfMonth int `json:"day_of_month,omitempty"` NotificationURL string `json:"notification_url,omitempty"` SuccessURL string `json:"success_url"` BackURL string `json:"back_url"` ErrorURL string `json:"error_url"` } type PlanUpdated struct { Name string `json:"name"` // Plan name Description string `json:"description"` // Plan description Amount float64 `json:"amount"` // Plan amount NotificationURL string `json:"notification_url"` // Webhook notification URL BackURL string `json:"back_url"` // URL to return client to merchant site SuccessURL string `json:"success_url"` // Redirect URL on payment success ErrorURL string `json:"error_url"` // Redirect URL on payment error } type PagoRequest struct { Currency string `json:"currency"` Amount float64 `json:"amount"` OrderID int `json:"order_id"` Description string `json:"description"` SuccessURL string `json:"success_url"` BackURL string `json:"back_url"` NotificationURL string `json:"notification_url"` } // Llama al endpoint /v1/me de DLocal usando la configuración func GetDLocalMe(cfg dlocalConfig) ([]byte, error) { // Determinar URL base según el modo baseURL := cfg.UrlDev AccessKeyID := cfg.AccessKeyIDdev AccessKeySecret := cfg.AccessKeySecretdev if cfg.Modo == "prod" { baseURL = cfg.UrlProd AccessKeyID = cfg.AccessKeyID AccessKeySecret = cfg.AccessKeySecret } // Construir token de autorización authToken := fmt.Sprintf("Bearer %s:%s", AccessKeyID, AccessKeySecret) // Crear la solicitud HTTP req, err := http.NewRequest("GET", baseURL+"/v1/me", bytes.NewBuffer([]byte("{}"))) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", authToken) // Ejecutar la solicitud client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() // Leer la respuesta body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } return body, nil } // Función para crear el plan en DLocal func CreatePlan(cfg models.DlocalApi, plan models.PlanRequest) ([]byte, error) { // Determinar la URL base según el modo baseURL := cfg.UrlDev AccessKeyID := cfg.AccessKeyIDdev AccessKeySecret := cfg.AccessKeySecretdev if cfg.Modo == "prod" { baseURL = cfg.UrlProd AccessKeyID = cfg.AccessKeyID AccessKeySecret = cfg.AccessKeySecret } // URL completa para el endpoint url := baseURL + "/v1/subscription/plan" // Construir el token de autorización authToken := fmt.Sprintf("Bearer %s:%s", AccessKeyID, AccessKeySecret) // Convertir el cuerpo de la solicitud a JSON requestBody, err := json.Marshal(plan) if err != nil { return nil, fmt.Errorf("error al convertir el cuerpo de la solicitud a JSON: %v", err) } // Crear la solicitud HTTP req, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBody)) if err != nil { return nil, fmt.Errorf("error al crear la solicitud: %v", err) } // Agregar los encabezados a la solicitud req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", authToken) // Configurar el cliente HTTP client := &http.Client{ Timeout: 30 * time.Second, } // Ejecutar la solicitud resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("error al enviar la solicitud: %v", err) } defer resp.Body.Close() // Leer la respuesta responseBody, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("error al leer la respuesta: %v", err) } // Si la respuesta no es exitosa, devolver un error if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("error en la respuesta de DLocal: %s", string(responseBody)) } // Devolver la respuesta exitosa return responseBody, nil } func SeePlan(cfg models.DlocalApi, planID string) ([]byte, error) { // Determinar la URL base según el modo baseURL := cfg.UrlDev accessKeyID := cfg.AccessKeyIDdev accessKeySecret := cfg.AccessKeySecretdev if cfg.Modo == "prod" { baseURL = cfg.UrlProd accessKeyID = cfg.AccessKeyID accessKeySecret = cfg.AccessKeySecret } // Validar planID (opcional: depende de tus requisitos) if planID == "" { return nil, fmt.Errorf("planID no puede estar vacío") } // Construir la URL completa para el endpoint url := fmt.Sprintf("%s/v1/subscription/plan/%s", baseURL, planID) // Construir el token de autorización authToken := fmt.Sprintf("Bearer %s:%s", accessKeyID, accessKeySecret) // Crear la solicitud HTTP req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("error al crear la solicitud: %v", err) } // Agregar los encabezados a la solicitud req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", authToken) // Configurar el cliente HTTP client := &http.Client{ Timeout: 30 * time.Second, } // Ejecutar la solicitud resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("error al enviar la solicitud: %v", err) } defer resp.Body.Close() // Leer la respuesta responseBody, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("error al leer la respuesta: %v", err) } // Si la respuesta no es exitosa, devolver un error if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("error en la respuesta de DLocal (%d): %s", resp.StatusCode, string(responseBody)) } // Devolver la respuesta exitosa return responseBody, nil } func UpdatedPlan(cfg models.DlocalApi, plan PlanUpdated, planID string) ([]byte, error) { // Determinar la URL base según el modo baseURL := cfg.UrlDev accessKeyID := cfg.AccessKeyIDdev accessKeySecret := cfg.AccessKeySecretdev if cfg.Modo == "prod" { baseURL = cfg.UrlProd accessKeyID = cfg.AccessKeyID accessKeySecret = cfg.AccessKeySecret } // Construir la URL completa para el endpoint url := fmt.Sprintf("%s/v1/subscription/plan/%s", baseURL, planID) // Construir el token de autorización authToken := fmt.Sprintf("Bearer %s:%s", accessKeyID, accessKeySecret) // Convertir el cuerpo de la solicitud a JSON requestBody, err := json.Marshal(plan) if err != nil { return nil, fmt.Errorf("error al convertir el cuerpo de la solicitud a JSON: %v", err) } // Crear la solicitud HTTP req, err := http.NewRequest("PATCH", url, bytes.NewBuffer(requestBody)) if err != nil { return nil, fmt.Errorf("error al crear la solicitud: %v", err) } // Agregar los encabezados a la solicitud req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", authToken) // Configurar el cliente HTTP client := &http.Client{ Timeout: 30 * time.Second, } // Ejecutar la solicitud resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("error al enviar la solicitud: %v", err) } defer resp.Body.Close() // Leer la respuesta responseBody, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("error al leer la respuesta: %v", err) } // Si la respuesta no es exitosa, devolver un error if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("error en la respuesta de DLocal (%d): %s", resp.StatusCode, string(responseBody)) } // Devolver la respuesta exitosa return responseBody, nil } func DeactivatePlan(cfg models.DlocalApi, planID string, subscriptionId string) ([]byte, error) { // Determinar la URL base según el modo baseURL := cfg.UrlDev accessKeyID := cfg.AccessKeyIDdev accessKeySecret := cfg.AccessKeySecretdev if cfg.Modo == "prod" { baseURL = cfg.UrlProd accessKeyID = cfg.AccessKeyID accessKeySecret = cfg.AccessKeySecret } // Construir la URL completa para el endpoint url := fmt.Sprintf("%s/v1/subscription/plan/%s/subscription/%s/deactivate", baseURL, planID, subscriptionId) // Construir el token de autorización authToken := fmt.Sprintf("Bearer %s:%s", accessKeyID, accessKeySecret) // Crear la solicitud HTTP req, err := http.NewRequest("PATCH", url, nil) if err != nil { return nil, fmt.Errorf("error al crear la solicitud: %v", err) } // Agregar los encabezados a la solicitud req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", authToken) // Configurar el cliente HTTP client := &http.Client{ Timeout: 30 * time.Second, } // Ejecutar la solicitud resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("error al enviar la solicitud: %v", err) } defer resp.Body.Close() // Leer la respuesta responseBody, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("error al leer la respuesta: %v", err) } // Si la respuesta no es exitosa, devolver un error if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("error en la respuesta de DLocal (%d): %s", resp.StatusCode, string(responseBody)) } // Devolver la respuesta exitosa return responseBody, nil } func SeePlanes(cfg models.DlocalApi) ([]byte, error) { // Validar modo y establecer valores var baseURL, accessKeyID, accessKeySecret string switch cfg.Modo { case "prod": baseURL = cfg.UrlProd accessKeyID = cfg.AccessKeyID accessKeySecret = cfg.AccessKeySecret default: baseURL = cfg.UrlDev accessKeyID = cfg.AccessKeyIDdev accessKeySecret = cfg.AccessKeySecretdev } if baseURL == "" || accessKeyID == "" || accessKeySecret == "" { return nil, fmt.Errorf("configuración incompleta para el entorno %q", cfg.Modo) } // Construir la URL del endpoint url := fmt.Sprintf("%s/v1/subscription/plan/all", baseURL) // Crear la solicitud HTTP req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("error al crear la solicitud: %w", err) } // Establecer encabezados req.Header.Set("Content-Type", "application/json") // En DLocal se usa generalmente Basic Auth codificado en base64, pero si realmente usan Bearer personalizado: authToken := fmt.Sprintf("Bearer %s:%s", accessKeyID, accessKeySecret) req.Header.Set("Authorization", authToken) // Cliente HTTP con timeout client := &http.Client{Timeout: 30 * time.Second} // Ejecutar solicitud resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("error al enviar la solicitud: %w", err) } defer resp.Body.Close() // Leer respuesta body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("error al leer la respuesta: %w", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("respuesta no exitosa (%d): %s", resp.StatusCode, string(body)) } return body, nil } func SeeSubscription(cfg models.DlocalApi, subscriptionId string, invoiceId string) ([]byte, error) { // Determinar la URL base según el modo baseURL := cfg.UrlDev accessKeyID := cfg.AccessKeyIDdev accessKeySecret := cfg.AccessKeySecretdev if cfg.Modo == "prod" { baseURL = cfg.UrlProd accessKeyID = cfg.AccessKeyID accessKeySecret = cfg.AccessKeySecret } // Construir la URL completa para el endpoint url := fmt.Sprintf("%s/v1/subscription/%s/execution/%s", baseURL, subscriptionId, invoiceId) // Crear la solicitud HTTP req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("error al crear la solicitud: %w", err) } // Establecer encabezados req.Header.Set("Content-Type", "application/json") // En DLocal se usa generalmente Basic Auth codificado en base64, pero si realmente usan Bearer personalizado: authToken := fmt.Sprintf("Bearer %s:%s", accessKeyID, accessKeySecret) req.Header.Set("Authorization", authToken) // Cliente HTTP con timeout client := &http.Client{Timeout: 30 * time.Second} // Ejecutar solicitud resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("error al enviar la solicitud: %w", err) } defer resp.Body.Close() // Leer respuesta body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("error al leer la respuesta: %w", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("respuesta no exitosa (%d): %s", resp.StatusCode, string(body)) } return body, nil } func CreatePago(cfg models.DlocalApi, pago PagoRequest) ([]byte, error) { // Determinar la URL base y credenciales baseURL := cfg.UrlDev AccessKeyID := cfg.AccessKeyIDdev AccessKeySecret := cfg.AccessKeySecretdev if cfg.Modo == "prod" { baseURL = cfg.UrlProd AccessKeyID = cfg.AccessKeyID AccessKeySecret = cfg.AccessKeySecret } // URL del endpoint de pagos url := baseURL + "/v1/payments" // Token de autenticación authToken := fmt.Sprintf("Bearer %s:%s", AccessKeyID, AccessKeySecret) // Convertir el cuerpo de la solicitud a JSON requestBody, err := json.Marshal(pago) if err != nil { return nil, fmt.Errorf("error al convertir el cuerpo de la solicitud a JSON: %v", err) } // Crear la solicitud HTTP req, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBody)) if err != nil { return nil, fmt.Errorf("error al crear la solicitud: %v", err) } // Encabezados req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", authToken) // Cliente HTTP con timeout client := &http.Client{ Timeout: 30 * time.Second, } // Ejecutar la solicitud resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("error al enviar la solicitud: %v", err) } defer resp.Body.Close() // Leer la respuesta responseBody, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("error al leer la respuesta: %v", err) } // Verificar si la respuesta fue exitosa if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { return nil, fmt.Errorf("error en la respuesta de DLocal: %s", string(responseBody)) } return responseBody, nil }