300 lines
8.6 KiB
Go
300 lines
8.6 KiB
Go
package chapa
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"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": fmt.Sprintf("%.2f", float64(req.Amount)/100),
|
|
"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,
|
|
}
|
|
|
|
fmt.Printf("\n\nChapa Payload: %+v\n\n", payload)
|
|
|
|
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)
|
|
}
|
|
|
|
fmt.Printf("\n\nBase URL is: %+v\n\n", c.baseURL)
|
|
|
|
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 {
|
|
body, _ := io.ReadAll(resp.Body) // <-- Add this
|
|
return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body)) // <-- Log it
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (c *Client) InitiateTransfer(ctx context.Context, req domain.ChapaWithdrawalRequest) (bool, error) {
|
|
// base, err := url.Parse(c.baseURL)
|
|
// if err != nil {
|
|
// return false, fmt.Errorf("invalid base URL: %w", err)
|
|
// }
|
|
endpoint := c.baseURL + "/transfers"
|
|
fmt.Printf("\n\nChapa withdrawal URL is %v\n\n", endpoint)
|
|
|
|
reqBody, err := json.Marshal(req)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
httpReq, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewBuffer(reqBody))
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
c.setHeaders(httpReq)
|
|
|
|
resp, err := c.httpClient.Do(httpReq)
|
|
if err != nil {
|
|
return false, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return false, fmt.Errorf("chapa api returned status: %d", resp.StatusCode)
|
|
}
|
|
|
|
var response domain.ChapaWithdrawalResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
|
return false, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return response.Status == string(domain.WithdrawalStatusProcessing), nil
|
|
}
|
|
|
|
func (c *Client) VerifyTransfer(ctx context.Context, reference string) (*domain.ChapaVerificationResponse, error) {
|
|
base, err := url.Parse(c.baseURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid base URL: %w", err)
|
|
}
|
|
endpoint := base.ResolveReference(&url.URL{Path: fmt.Sprintf("/v1/transfers/%s/verify", reference)})
|
|
|
|
httpReq, err := http.NewRequestWithContext(ctx, "GET", endpoint.String(), nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
c.setHeaders(httpReq)
|
|
|
|
resp, err := c.httpClient.Do(httpReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("chapa api returned status: %d", resp.StatusCode)
|
|
}
|
|
|
|
var verification domain.ChapaVerificationResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &verification, nil
|
|
}
|
|
|
|
func (c *Client) setHeaders(req *http.Request) {
|
|
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Accept", "application/json")
|
|
}
|