Chapa Fixes

This commit is contained in:
Yared Yemane 2025-10-20 15:16:39 +03:00
parent e0e4ff4b64
commit 4104d7d371
10 changed files with 790 additions and 10 deletions

View File

@ -2085,6 +2085,49 @@ const docTemplate = `{
}
}
},
"/api/v1/chapa/balances": {
"get": {
"description": "Retrieve Chapa account balance, optionally filtered by currency code (e.g., ETB, USD)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Chapa"
],
"summary": "Get Chapa account balance",
"parameters": [
{
"type": "string",
"description": "Currency code (optional)",
"name": "currency_code",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/chapa/banks": {
"get": {
"description": "Get list of banks supported by Chapa",
@ -2209,6 +2252,50 @@ const docTemplate = `{
}
}
},
"/api/v1/chapa/payments/receipt/{chapa_ref}": {
"get": {
"description": "Retrieve the Chapa payment receipt URL using the reference ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Chapa"
],
"summary": "Get Chapa Payment Receipt URL",
"parameters": [
{
"type": "string",
"description": "Chapa Reference ID",
"name": "chapa_ref",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/chapa/payments/webhook/verify": {
"post": {
"description": "Handles payment notifications from Chapa",
@ -2319,6 +2406,87 @@ const docTemplate = `{
}
}
},
"/api/v1/chapa/swap": {
"post": {
"description": "Perform a USD to ETB currency swap using Chapa's API",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Chapa"
],
"summary": "Initiate a currency swap",
"parameters": [
{
"description": "Swap Request Payload",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/domain.SwapRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/chapa/transfers": {
"get": {
"description": "Retrieve all transfer records from Chapa",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Chapa"
],
"summary": "Get all Chapa transfers",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/company": {
"get": {
"description": "Gets all companies",
@ -9837,7 +10005,7 @@ const docTemplate = `{
"type": "string"
},
"round_id": {
"type": "integer"
"type": "string"
},
"session_id": {
"type": "string"
@ -12934,6 +13102,20 @@ const docTemplate = `{
}
}
},
"domain.SwapRequest": {
"type": "object",
"properties": {
"amount": {
"type": "number"
},
"from": {
"type": "string"
},
"to": {
"type": "string"
}
}
},
"domain.TelebirrPaymentCallbackPayload": {
"type": "object",
"properties": {

View File

@ -2077,6 +2077,49 @@
}
}
},
"/api/v1/chapa/balances": {
"get": {
"description": "Retrieve Chapa account balance, optionally filtered by currency code (e.g., ETB, USD)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Chapa"
],
"summary": "Get Chapa account balance",
"parameters": [
{
"type": "string",
"description": "Currency code (optional)",
"name": "currency_code",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/chapa/banks": {
"get": {
"description": "Get list of banks supported by Chapa",
@ -2201,6 +2244,50 @@
}
}
},
"/api/v1/chapa/payments/receipt/{chapa_ref}": {
"get": {
"description": "Retrieve the Chapa payment receipt URL using the reference ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Chapa"
],
"summary": "Get Chapa Payment Receipt URL",
"parameters": [
{
"type": "string",
"description": "Chapa Reference ID",
"name": "chapa_ref",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/chapa/payments/webhook/verify": {
"post": {
"description": "Handles payment notifications from Chapa",
@ -2311,6 +2398,87 @@
}
}
},
"/api/v1/chapa/swap": {
"post": {
"description": "Perform a USD to ETB currency swap using Chapa's API",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Chapa"
],
"summary": "Initiate a currency swap",
"parameters": [
{
"description": "Swap Request Payload",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/domain.SwapRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/chapa/transfers": {
"get": {
"description": "Retrieve all transfer records from Chapa",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Chapa"
],
"summary": "Get all Chapa transfers",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/company": {
"get": {
"description": "Gets all companies",
@ -9829,7 +9997,7 @@
"type": "string"
},
"round_id": {
"type": "integer"
"type": "string"
},
"session_id": {
"type": "string"
@ -12926,6 +13094,20 @@
}
}
},
"domain.SwapRequest": {
"type": "object",
"properties": {
"amount": {
"type": "number"
},
"from": {
"type": "string"
},
"to": {
"type": "string"
}
}
},
"domain.TelebirrPaymentCallbackPayload": {
"type": "object",
"properties": {

View File

@ -75,7 +75,7 @@ definitions:
player_id:
type: string
round_id:
type: integer
type: string
session_id:
type: string
timestamp:
@ -2191,6 +2191,15 @@ definitions:
example: SportsBook
type: string
type: object
domain.SwapRequest:
properties:
amount:
type: number
from:
type: string
to:
type: string
type: object
domain.TelebirrPaymentCallbackPayload:
properties:
appid:
@ -5509,6 +5518,35 @@ paths:
summary: Update cashier
tags:
- cashier
/api/v1/chapa/balances:
get:
consumes:
- application/json
description: Retrieve Chapa account balance, optionally filtered by currency
code (e.g., ETB, USD)
parameters:
- description: Currency code (optional)
in: query
name: currency_code
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Get Chapa account balance
tags:
- Chapa
/api/v1/chapa/banks:
get:
consumes:
@ -5589,6 +5627,35 @@ paths:
summary: Verify a payment manually
tags:
- Chapa
/api/v1/chapa/payments/receipt/{chapa_ref}:
get:
consumes:
- application/json
description: Retrieve the Chapa payment receipt URL using the reference ID
parameters:
- description: Chapa Reference ID
in: path
name: chapa_ref
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Get Chapa Payment Receipt URL
tags:
- Chapa
/api/v1/chapa/payments/webhook/verify:
post:
consumes:
@ -5661,6 +5728,59 @@ paths:
summary: Initiate a withdrawal
tags:
- Chapa
/api/v1/chapa/swap:
post:
consumes:
- application/json
description: Perform a USD to ETB currency swap using Chapa's API
parameters:
- description: Swap Request Payload
in: body
name: payload
required: true
schema:
$ref: '#/definitions/domain.SwapRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Initiate a currency swap
tags:
- Chapa
/api/v1/chapa/transfers:
get:
consumes:
- application/json
description: Retrieve all transfer records from Chapa
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Get all Chapa transfers
tags:
- Chapa
/api/v1/company:
get:
consumes:

View File

@ -46,7 +46,7 @@ type AtlasBetResponse struct {
type AtlasBetWinRequest struct {
Game string `json:"game"`
CasinoID string `json:"casino_id"`
RoundID int64 `json:"round_id"`
RoundID string `json:"round_id"`
PlayerID string `json:"player_id"`
SessionID string `json:"session_id"`
BetAmount float64 `json:"betAmount"`

View File

@ -196,3 +196,28 @@ type ChapaWebHookPayment struct {
} `json:"customization"`
Meta string `json:"meta"`
}
type Balance struct {
Currency string `json:"currency"`
AvailableBalance float64 `json:"available_balance"`
LedgerBalance float64 `json:"ledger_balance"`
}
type SwapRequest struct {
From string `json:"from"`
To string `json:"to"`
Amount float64 `json:"amount"`
}
type SwapResponse struct {
Status string `json:"status"`
RefID string `json:"ref_id"`
FromCurrency string `json:"from_currency"`
ToCurrency string `json:"to_currency"`
Amount float64 `json:"amount"`
ExchangedAmount float64 `json:"exchanged_amount"`
Charge float64 `json:"charge"`
Rate float64 `json:"rate"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}

View File

@ -16,10 +16,13 @@ import (
type ChapaStore interface {
InitializePayment(request domain.ChapaDepositRequest) (domain.ChapaDepositResponse, error)
// VerifyPayment(reference string) (domain.ChapaDepositVerification, error)
ManualVerifTransaction(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error)
FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
CreateWithdrawal(userID string, amount float64, accountNumber, bankCode string) (*domain.ChapaWithdrawal, error)
HandleVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaWebHookTransfer) error
HandleVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebHookPayment) error
GetPaymentReceiptURL(ctx context.Context, chapaRef string) (string, error)
GetAllTransfers(ctx context.Context) ([]domain.Transfer, error)
GetAccountBalance(ctx context.Context, currencyCode string) ([]domain.Balance, error)
InitiateSwap(ctx context.Context, amount float64, from, to string) (*domain.SwapResponse, error)
}

View File

@ -1,9 +1,13 @@
package chapa
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
@ -63,7 +67,7 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
return "", fmt.Errorf("failed to get sender wallets: %w", err)
}
for _, wallet := range senderWallets {
if wallet.IsWithdraw {
if wallet.IsTransferable {
senderWallet = wallet
break
}
@ -364,3 +368,138 @@ func (s *Service) HandleVerifyWithdrawWebhook(ctx context.Context, payment domai
return nil
}
func (s *Service) GetPaymentReceiptURL(ctx context.Context, chapaRef string) (string, error) {
if chapaRef == "" {
return "", fmt.Errorf("chapa reference ID is required")
}
receiptURL := fmt.Sprintf("https://chapa.link/payment-receipt/%s", chapaRef)
return receiptURL, nil
}
func (s *Service) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.chapa.co/v1/transfers", nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.cfg.CHAPA_SECRET_KEY))
resp, err := s.chapaClient.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to fetch transfers: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(bodyBytes))
}
var result struct {
Status string `json:"status"`
Message string `json:"message"`
Data []domain.Transfer `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
return result.Data, nil
}
func (s *Service) GetAccountBalance(ctx context.Context, currencyCode string) ([]domain.Balance, error) {
baseURL := "https://api.chapa.co/v1/balances"
if currencyCode != "" {
baseURL = fmt.Sprintf("%s/%s", baseURL, strings.ToLower(currencyCode))
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to create balance request: %w", err)
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.cfg.CHAPA_SECRET_KEY))
resp, err := s.chapaClient.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to execute balance request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(bodyBytes))
}
var result struct {
Status string `json:"status"`
Message string `json:"message"`
Data []domain.Balance `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode balance response: %w", err)
}
return result.Data, nil
}
func (s *Service) InitiateSwap(ctx context.Context, amount float64, from, to string) (*domain.SwapResponse, error) {
if amount < 1 {
return nil, fmt.Errorf("amount must be at least 1 USD")
}
if strings.ToUpper(from) != "USD" || strings.ToUpper(to) != "ETB" {
return nil, fmt.Errorf("only USD to ETB swap is supported")
}
payload := domain.SwapRequest{
Amount: amount,
From: strings.ToUpper(from),
To: strings.ToUpper(to),
}
// payload := map[string]any{
// "amount": amount,
// "from": strings.ToUpper(from),
// "to": strings.ToUpper(to),
// }
body, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("failed to encode swap payload: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.chapa.co/v1/swap", bytes.NewBuffer(body))
if err != nil {
return nil, fmt.Errorf("failed to create swap request: %w", err)
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.cfg.CHAPA_SECRET_KEY))
req.Header.Set("Content-Type", "application/json")
resp, err := s.chapaClient.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to execute swap request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
bodyBytes, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(bodyBytes))
}
var result struct {
Message string `json:"message"`
Status string `json:"status"`
Data domain.SwapResponse `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode swap response: %w", err)
}
return &result.Data, nil
}

View File

@ -657,10 +657,6 @@ func (s *service) verifySignature(callback *domain.PopOKCallback) bool {
return expected == callback.Signature
}
// func (s *service) GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) {
// return s.repo.GetGameCounts(ctx, filter)
// }
func (s *service) ListGames(ctx context.Context, currency string) ([]domain.PopOKGame, error) {
now := time.Now().Format("02-01-2006 15:04:05") // dd-mm-yyyy hh:mm:ss

View File

@ -263,3 +263,132 @@ func (h *Handler) InitiateWithdrawal(c *fiber.Ctx) error {
Data: withdrawal,
})
}
// GetPaymentReceipt godoc
// @Summary Get Chapa Payment Receipt URL
// @Description Retrieve the Chapa payment receipt URL using the reference ID
// @Tags Chapa
// @Accept json
// @Produce json
// @Param chapa_ref path string true "Chapa Reference ID"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/chapa/payments/receipt/{chapa_ref} [get]
func (h *Handler) GetPaymentReceipt(c *fiber.Ctx) error {
chapaRef := c.Params("chapa_ref")
if chapaRef == "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to get Chapa payment receipt",
Error: "Chapa reference ID is required",
})
}
receiptURL, err := h.chapaSvc.GetPaymentReceiptURL(c.Context(), chapaRef)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to get Chapa payment receipt",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Payment receipt URL generated successfully",
Data: receiptURL,
StatusCode: 200,
Success: true,
})
}
// GetAllTransfers godoc
// @Summary Get all Chapa transfers
// @Description Retrieve all transfer records from Chapa
// @Tags Chapa
// @Accept json
// @Produce json
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/chapa/transfers [get]
func (h *Handler) GetAllTransfers(c *fiber.Ctx) error {
transfers, err := h.chapaSvc.GetAllTransfers(c.Context())
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to fetch Chapa transfers",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Chapa transfers retrieved successfully",
Data: transfers,
StatusCode: fiber.StatusOK,
Success: true,
})
}
// GetAccountBalance godoc
// @Summary Get Chapa account balance
// @Description Retrieve Chapa account balance, optionally filtered by currency code (e.g., ETB, USD)
// @Tags Chapa
// @Accept json
// @Produce json
// @Param currency_code query string false "Currency code (optional)"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/chapa/balances [get]
func (h *Handler) GetAccountBalance(c *fiber.Ctx) error {
currencyCode := c.Query("currency_code", "")
balances, err := h.chapaSvc.GetAccountBalance(c.Context(), currencyCode)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to fetch Chapa account balance",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Chapa account balance retrieved successfully",
Data: balances,
StatusCode: fiber.StatusOK,
Success: true,
})
}
// InitiateSwap godoc
// @Summary Initiate a currency swap
// @Description Perform a USD to ETB currency swap using Chapa's API
// @Tags Chapa
// @Accept json
// @Produce json
// @Param payload body domain.SwapRequest true "Swap Request Payload"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/chapa/swap [post]
func (h *Handler) InitiateSwap(c *fiber.Ctx) error {
var req domain.SwapRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid request payload",
Error: err.Error(),
})
}
swapResult, err := h.chapaSvc.InitiateSwap(c.Context(), req.Amount, req.From, req.To)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to initiate currency swap",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Currency swap initiated successfully",
Data: swapResult,
StatusCode: fiber.StatusOK,
Success: true,
})
}

View File

@ -383,6 +383,10 @@ func (a *App) initAppRoutes() {
groupV1.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit)
groupV1.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal)
groupV1.Get("/chapa/banks", h.GetSupportedBanks)
groupV1.Get("/chapa/payments/receipt/:chapa_ref", h.GetPaymentReceipt)
groupV1.Get("/chapa/transfers", h.GetAllTransfers)
groupV1.Get("/chapa/balance", h.GetAccountBalance)
groupV1.Post("/chapa/init-swap", h.InitiateSwap)
// Currencies
groupV1.Get("/currencies", h.GetSupportedCurrencies)