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, }) }