Yimaru-BackEnd/internal/web_server/handlers/chapa.go
2025-11-03 17:20:35 +03:00

475 lines
15 KiB
Go

package handlers
import (
"fmt"
"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 notifications from Chapa
// @Tags Chapa
// @Accept json
// @Produce json
// @Param request body domain.ChapaWebhookPayload true "Webhook payload"
// @Success 200 {object} map[string]interface{}
// @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 {
chapaTransactionType := new(domain.ChapaTransactionType)
if parseTypeErr := c.BodyParser(chapaTransactionType); parseTypeErr != nil {
return domain.UnProcessableEntityResponse(c)
}
switch chapaTransactionType.Type {
case h.Cfg.CHAPA_PAYMENT_TYPE:
chapaTransferVerificationRequest := new(domain.ChapaPaymentWebhookRequest)
if err := c.BodyParser(chapaTransferVerificationRequest); err != nil {
return domain.UnProcessableEntityResponse(c)
}
err := h.chapaSvc.ProcessVerifyDepositWebhook(c.Context(), *chapaTransferVerificationRequest)
if 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 transaction verified successfully",
Data: chapaTransferVerificationRequest,
Success: true,
})
case h.Cfg.CHAPA_TRANSFER_TYPE:
chapaPaymentVerificationRequest := new(domain.ChapaWebHookPayment)
if err := c.BodyParser(chapaPaymentVerificationRequest); err != nil {
return domain.UnProcessableEntityResponse(c)
}
err := h.chapaSvc.ProcessVerifyWithdrawWebhook(c.Context(), *chapaPaymentVerificationRequest)
if err != nil {
return domain.UnExpectedErrorResponse(c)
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
StatusCode: 200,
Message: "Chapa withdrawal transaction verified successfully",
Data: chapaPaymentVerificationRequest,
Success: true,
})
}
// Return a 400 Bad Request if the type does not match any known case
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid Chapa webhook type",
Error: "Unknown transaction type",
})
}
// 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.ChapaVerificationResponse
// @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 201 {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.StatusCreated).JSON(domain.Response{
Message: "Chapa withdrawal process initiated successfully",
StatusCode: 201,
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 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,
})
}