package chapa import ( "bytes" "context" "encoding/json" "fmt" "net/http" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" ) type Client struct { baseURL string secretKey string httpClient *http.Client } func NewClient(baseURL, secretKey string) *Client { return &Client{ baseURL: baseURL, secretKey: secretKey, httpClient: &http.Client{ Timeout: 30 * time.Second, }, } } func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaDepositRequest) (domain.ChapaDepositResponse, error) { payload := map[string]interface{}{ "amount": req.Amount, "currency": req.Currency, "email": req.Email, "first_name": req.FirstName, "last_name": req.LastName, "tx_ref": req.TxRef, "callback_url": req.CallbackURL, "return_url": req.ReturnURL, } payloadBytes, err := json.Marshal(payload) if err != nil { return domain.ChapaDepositResponse{}, fmt.Errorf("failed to marshal payload: %w", err) } httpReq, err := http.NewRequestWithContext(ctx, "POST", c.baseURL+"/transaction/initialize", bytes.NewBuffer(payloadBytes)) if err != nil { return domain.ChapaDepositResponse{}, fmt.Errorf("failed to create request: %w", err) } httpReq.Header.Set("Authorization", "Bearer "+c.secretKey) httpReq.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(httpReq) if err != nil { return domain.ChapaDepositResponse{}, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } var response struct { Message string `json:"message"` Status string `json:"status"` Data struct { CheckoutURL string `json:"checkout_url"` } `json:"data"` } if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { return domain.ChapaDepositResponse{}, fmt.Errorf("failed to decode response: %w", err) } return domain.ChapaDepositResponse{ CheckoutURL: response.Data.CheckoutURL, // Reference: req.TxRef, }, nil } func (c *Client) VerifyPayment(ctx context.Context, reference string) (domain.ChapaDepositVerification, error) { httpReq, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/transaction/verify/"+reference, nil) if err != nil { return domain.ChapaDepositVerification{}, fmt.Errorf("failed to create request: %w", err) } httpReq.Header.Set("Authorization", "Bearer "+c.secretKey) resp, err := c.httpClient.Do(httpReq) if err != nil { return domain.ChapaDepositVerification{}, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return domain.ChapaDepositVerification{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } var verification domain.ChapaDepositVerification if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil { return domain.ChapaDepositVerification{}, fmt.Errorf("failed to decode response: %w", err) } var status domain.PaymentStatus switch verification.Status { case "success": status = domain.PaymentStatusCompleted default: status = domain.PaymentStatusFailed } return domain.ChapaDepositVerification{ Status: status, Amount: verification.Amount, Currency: verification.Currency, }, nil } func (c *Client) ManualVerifyPayment(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) { url := fmt.Sprintf("%s/transaction/verify/%s", c.baseURL, txRef) req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Authorization", "Bearer "+c.secretKey) resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } var response struct { Status string `json:"status"` Amount float64 `json:"amount"` Currency string `json:"currency"` } if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } var status domain.PaymentStatus switch response.Status { case "success": status = domain.PaymentStatusCompleted default: status = domain.PaymentStatusFailed } return &domain.ChapaVerificationResponse{ Status: string(status), Amount: response.Amount, Currency: response.Currency, }, nil } func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error) { req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/banks", nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Authorization", "Bearer "+c.secretKey) resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } var bankResponse domain.BankResponse if err := json.NewDecoder(resp.Body).Decode(&bankResponse); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } var banks []domain.Bank for _, bankData := range bankResponse.Data { bank := domain.Bank{ ID: bankData.ID, Slug: bankData.Slug, Swift: bankData.Swift, Name: bankData.Name, AcctLength: bankData.AcctLength, CountryID: bankData.CountryID, IsMobileMoney: bankData.IsMobileMoney, IsActive: bankData.IsActive, IsRTGS: bankData.IsRTGS, Active: bankData.Active, Is24Hrs: bankData.Is24Hrs, CreatedAt: bankData.CreatedAt, UpdatedAt: bankData.UpdatedAt, Currency: bankData.Currency, } banks = append(banks, bank) } return banks, nil } // Helper method to generate account regex based on bank type // func GetAccountRegex(bank domain.Bank) string { // if bank.IsMobileMoney != nil && bank.IsMobileMoney == 1 { // return `^09[0-9]{8}$` // Ethiopian mobile money pattern // } // return fmt.Sprintf(`^[0-9]{%d}$`, bank.AcctLength) // } // // Helper method to generate example account number // func GetExampleAccount(bank domain.Bank) string { // if bank.IsMobileMoney != nil && *bank.IsMobileMoney { // return "0912345678" // Ethiopian mobile number example // } // // Generate example based on length // example := "1" // for i := 1; i < bank.AcctLength; i++ { // example += fmt.Sprintf("%d", i%10) // } // return example // }