235 lines
6.5 KiB
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
|
|
// }
|