486 lines
15 KiB
Go
486 lines
15 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
|
"github.com/gofiber/fiber/v2"
|
|
)
|
|
|
|
// InitiateDeposit godoc
|
|
// @Summary Initiate a deposit
|
|
// @Description Starts a new deposit process using Chapa payment gateway
|
|
// @Tags Chapa
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security ApiKeyAuth
|
|
// @Param request body domain.ChapaDepositRequestPayload true "Deposit request"
|
|
// @Success 200 {object} domain.ChapaDepositResponse
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/chapa/payments/deposit [post]
|
|
func (h *Handler) InitiateDeposit(c *fiber.Ctx) error {
|
|
// Get user ID from context (set by your auth middleware)
|
|
userID, ok := c.Locals("user_id").(int64)
|
|
if !ok {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Error: "invalid user ID",
|
|
Message: "User ID is required to initiate a deposit",
|
|
})
|
|
}
|
|
|
|
var req domain.ChapaDepositRequestPayload
|
|
|
|
if err := c.BodyParser(&req); err != nil {
|
|
// fmt.Println("We first first are here init Chapa payment")
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Error: err.Error(),
|
|
Message: "Failed to parse request body",
|
|
})
|
|
}
|
|
|
|
amount := domain.Currency(req.Amount)
|
|
|
|
fmt.Println("We are here init Chapa payment")
|
|
|
|
checkoutURL, err := h.chapaSvc.InitiateDeposit(c.Context(), userID, amount)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Error: err.Error(),
|
|
Message: "Failed to initiate Chapa deposit",
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "Chapa deposit process initiated successfully",
|
|
Data: checkoutURL,
|
|
StatusCode: 200,
|
|
Success: true,
|
|
})
|
|
}
|
|
|
|
// WebhookCallback godoc
|
|
// @Summary Chapa payment webhook callback (used by Chapa)
|
|
// @Description Handles payment and transfer notifications from Chapa
|
|
// @Tags Chapa
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body domain.ChapaWebhookPayment true "Webhook payload"
|
|
// @Success 200 {object} domain.Response
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/chapa/payments/webhook/verify [post]
|
|
func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
|
body := c.Body()
|
|
|
|
// Retrieve signature headers
|
|
chapaSignature := c.Get("chapa-signature")
|
|
xChapaSignature := c.Get("x-chapa-signature")
|
|
|
|
// Verify webhook signature
|
|
valid, err := h.chapaSvc.VerifyWebhookSignature(c.Context(), body, chapaSignature, xChapaSignature)
|
|
if err != nil || !valid {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid Chapa webhook signature",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
// Try parsing as transfer webhook first
|
|
var transfer domain.ChapaWebhookTransfer
|
|
if err := json.Unmarshal(body, &transfer); err == nil &&
|
|
strings.EqualFold(transfer.Type, "payout") {
|
|
|
|
if err := h.chapaSvc.ProcessVerifyWithdrawWebhook(c.Context(), transfer); err != nil {
|
|
return domain.UnExpectedErrorResponse(c)
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
StatusCode: 200,
|
|
Message: "Chapa withdrawal webhook processed successfully",
|
|
// Data: transfer,
|
|
Success: true,
|
|
})
|
|
}
|
|
|
|
// Otherwise, try as payment webhook
|
|
var payment domain.ChapaWebhookPayment
|
|
if err := json.Unmarshal(body, &payment); err != nil {
|
|
return domain.UnProcessableEntityResponse(c)
|
|
}
|
|
|
|
if err := h.chapaSvc.ProcessVerifyDepositWebhook(c.Context(), payment); err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Failed to verify Chapa deposit",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
StatusCode: 200,
|
|
Message: "Chapa deposit webhook processed successfully",
|
|
// Data: payment,
|
|
Success: true,
|
|
})
|
|
}
|
|
|
|
// CancelDeposit godoc
|
|
// @Summary Cancel a Chapa deposit transaction
|
|
// @Description Cancels an active Chapa transaction using its transaction reference
|
|
// @Tags Chapa
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security ApiKeyAuth
|
|
// @Param tx_ref path string true "Transaction Reference"
|
|
// @Success 200 {object} domain.ChapaCancelResponse
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 404 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/chapa/transaction/cancel/{tx_ref} [put]
|
|
func (h *Handler) CancelDeposit(c *fiber.Ctx) error {
|
|
// Get user ID from context (set by your auth middleware)
|
|
userID, ok := c.Locals("user_id").(int64)
|
|
if !ok {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Error: "invalid user ID",
|
|
Message: "User ID is required to cancel a deposit",
|
|
})
|
|
}
|
|
|
|
// Extract tx_ref from URL path
|
|
txRef := c.Params("tx_ref")
|
|
if txRef == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Error: "missing transaction reference",
|
|
Message: "Transaction reference is required in the path",
|
|
})
|
|
}
|
|
|
|
fmt.Printf("\n\nReceived request to cancel Chapa transaction: %s (User ID: %d)\n\n", txRef, userID)
|
|
|
|
// Call the service layer to cancel deposit
|
|
cancelResp, err := h.chapaSvc.CancelDeposit(c.Context(), userID, txRef)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Error: err.Error(),
|
|
Message: "Failed to cancel Chapa deposit",
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "Chapa transaction cancelled successfully",
|
|
Data: cancelResp,
|
|
StatusCode: 200,
|
|
Success: true,
|
|
})
|
|
}
|
|
|
|
// FetchAllTransactions godoc
|
|
// @Summary Get all Chapa transactions
|
|
// @Description Retrieves all transactions from Chapa payment gateway
|
|
// @Tags Chapa
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security ApiKeyAuth
|
|
// @Success 200 {array} domain.ChapaTransaction
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/chapa/transactions [get]
|
|
func (h *Handler) FetchAllTransactions(c *fiber.Ctx) error {
|
|
transactions, err := h.chapaSvc.FetchAllTransactions(c.Context())
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Error: err.Error(),
|
|
Message: "Failed to fetch Chapa transactions",
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "Chapa transactions retrieved successfully",
|
|
Data: transactions,
|
|
StatusCode: 200,
|
|
Success: true,
|
|
})
|
|
}
|
|
|
|
// GetTransactionEvents godoc
|
|
// @Summary Fetch transaction events
|
|
// @Description Retrieve the timeline of events for a specific Chapa transaction
|
|
// @Tags Chapa
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param ref_id path string true "Transaction Reference"
|
|
// @Success 200 {array} domain.ChapaTransactionEvent
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/chapa/transaction/events/{ref_id} [get]
|
|
func (h *Handler) GetTransactionEvents(c *fiber.Ctx) error {
|
|
refID := c.Params("ref_id")
|
|
if refID == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Failed to fetch transaction events",
|
|
Error: "Transaction reference is required",
|
|
})
|
|
}
|
|
|
|
events, err := h.chapaSvc.FetchTransactionEvents(c.Context(), refID)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Failed to fetch transaction events",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "Transaction events fetched successfully",
|
|
Data: events,
|
|
StatusCode: 200,
|
|
Success: true,
|
|
})
|
|
}
|
|
|
|
// VerifyPayment godoc
|
|
// @Summary Verify a payment manually
|
|
// @Description Manually verify a payment using Chapa's API
|
|
// @Tags Chapa
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param tx_ref path string true "Transaction Reference"
|
|
// @Success 200 {object} domain.Response
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/chapa/transaction/manual/verify/{tx_ref} [get]
|
|
func (h *Handler) ManualVerifyTransaction(c *fiber.Ctx) error {
|
|
txRef := c.Params("tx_ref")
|
|
if txRef == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Failed to verify Chapa transaction",
|
|
Error: "Transaction reference is required",
|
|
})
|
|
}
|
|
|
|
verification, err := h.chapaSvc.ManuallyVerify(c.Context(), txRef)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Failed to verify Chapa transaction",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "Chapa transaction verified successfully",
|
|
Data: verification,
|
|
StatusCode: 200,
|
|
Success: true,
|
|
})
|
|
}
|
|
|
|
// GetSupportedBanks godoc
|
|
// @Summary Get supported banks
|
|
// @Description Get list of banks supported by Chapa
|
|
// @Tags Chapa
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} domain.Response
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/chapa/banks [get]
|
|
func (h *Handler) GetSupportedBanks(c *fiber.Ctx) error {
|
|
banks, err := h.chapaSvc.GetSupportedBanks(c.Context())
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Error: err.Error(),
|
|
Message: "Failed to fetch banks",
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "Banks fetched successfully",
|
|
StatusCode: 200,
|
|
Success: true,
|
|
Data: banks,
|
|
})
|
|
}
|
|
|
|
// InitiateWithdrawal initiates a withdrawal request via Chapa payment gateway
|
|
// @Summary Initiate a withdrawal
|
|
// @Description Initiates a withdrawal request to transfer funds to a bank account via Chapa
|
|
// @Tags Chapa
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security ApiKeyAuth
|
|
// @Param request body domain.ChapaWithdrawalRequest true "Withdrawal request details"
|
|
// @Success 200 {object} domain.Response "Chapa withdrawal process initiated successfully"
|
|
// @Failure 400 {object} domain.ErrorResponse "Invalid request body"
|
|
// @Failure 401 {object} domain.ErrorResponse "Unauthorized"
|
|
// @Failure 422 {object} domain.ErrorResponse "Unprocessable entity"
|
|
// @Failure 500 {object} domain.ErrorResponse "Internal server error"
|
|
// @Router /api/v1/chapa/payments/withdraw [post]
|
|
func (h *Handler) InitiateWithdrawal(c *fiber.Ctx) error {
|
|
userID, ok := c.Locals("user_id").(int64)
|
|
if !ok {
|
|
return domain.UnProcessableEntityResponse(c)
|
|
}
|
|
|
|
var req domain.ChapaWithdrawalRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return domain.UnProcessableEntityResponse(c)
|
|
}
|
|
|
|
withdrawal, err := h.chapaSvc.InitiateWithdrawal(c.Context(), userID, req)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Failed to initiate Chapa withdrawal",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "Chapa withdrawal process initiated successfully",
|
|
StatusCode: 200,
|
|
Success: true,
|
|
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(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 balances retrieved successfully",
|
|
Data: balances,
|
|
StatusCode: fiber.StatusOK,
|
|
Success: true,
|
|
})
|
|
}
|
|
|
|
// SwapCurrency godoc
|
|
// @Summary Swap currency using Chapa API
|
|
// @Description Convert an amount from one currency to another using Chapa's currency swap API
|
|
// @Tags Chapa
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request 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) SwapCurrency(c *fiber.Ctx) error {
|
|
var reqBody domain.SwapRequest
|
|
|
|
// Parse request body
|
|
if err := c.BodyParser(&reqBody); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid request payload",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
// Validate input
|
|
if reqBody.From == "" || reqBody.To == "" || reqBody.Amount <= 0 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Missing or invalid swap parameters",
|
|
Error: "from, to, and amount are required fields",
|
|
})
|
|
}
|
|
|
|
// Call service
|
|
resp, err := h.chapaSvc.SwapCurrency(c.Context(), reqBody)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Failed to perform currency swap",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
// Success response
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "Currency swapped successfully",
|
|
Data: resp,
|
|
StatusCode: fiber.StatusOK,
|
|
Success: true,
|
|
})
|
|
}
|