Yimaru-BackEnd/internal/services/chapa/client.go

235 lines
6.5 KiB
Go

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
// }