Yimaru-BackEnd/internal/web_server/handlers/arifpay.go
2025-11-06 16:37:41 +03:00

342 lines
12 KiB
Go

package handlers
import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/gofiber/fiber/v2"
)
// CreateCheckoutSessionHandler initializes a checkout session with Arifpay.
//
// @Summary Create Arifpay Checkout Session
// @Description Creates a payment session using Arifpay and returns a redirect URL.
// @Tags Arifpay
// @Accept json
// @Produce json
// @Param request body domain.CheckoutSessionClientRequest true "Checkout session request payload"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/arifpay/checkout [post]
func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error {
userId, ok := c.Locals("user_id").(int64)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
Error: "missing user id",
Message: "Unauthorized",
})
}
var req domain.CheckoutSessionClientRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to process your request",
})
}
data, err := h.arifpaySvc.CreateCheckoutSession(req, true, userId)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to process your request",
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Checkout session created successfully",
Data: data,
Success: true,
StatusCode: fiber.StatusOK,
})
}
// CancelCheckoutSessionHandler cancels an existing Arifpay checkout session.
//
// @Summary Cancel Arifpay Checkout Session
// @Description Cancels a payment session using Arifpay before completion.
// @Tags Arifpay
// @Accept json
// @Produce json
// @Param sessionId path string true "Checkout session ID"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/arifpay/checkout/cancel/{sessionId} [post]
func (h *Handler) CancelCheckoutSessionHandler(c *fiber.Ctx) error {
sessionID := c.Params("sessionId")
if sessionID == "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: "missing session ID",
Message: "Session ID is required",
})
}
data, err := h.arifpaySvc.CancelCheckoutSession(c.Context(), sessionID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to cancel checkout session",
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Checkout session canceled successfully",
Data: data,
Success: true,
StatusCode: fiber.StatusOK,
})
}
// HandleWebhook processes Arifpay webhook notifications.
//
// @Summary Handle Arifpay C2B Webhook
// @Description Handles webhook notifications from Arifpay for C2B transfers and updates transfer + wallet status.
// @Tags Arifpay
// @Accept json
// @Produce json
// @Param request body domain.WebhookRequest true "Arifpay webhook payload"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/arifpay/c2b-webhook [post]
func (h *Handler) HandleArifpayC2BWebhook(c *fiber.Ctx) error {
var req domain.WebhookRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Invalid webhook payload",
})
}
// 🚨 Decide how to get userId:
// If you get it from auth context/middleware, extract it here.
// For now, let's assume userId comes from your auth claims:
// userId, ok := c.Locals("user_id").(int64)
// if !ok {
// return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
// Error: "missing user id",
// Message: "Unauthorized",
// })
// }
err := h.arifpaySvc.ProcessWebhook(c.Context(), req, true)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to process webhook",
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Webhook processed successfully",
Success: true,
StatusCode: fiber.StatusOK,
})
}
// HandleWebhook processes Arifpay webhook notifications.
//
// @Summary Handle Arifpay B2C Webhook
// @Description Handles webhook notifications from Arifpay for B2C transfers and updates transfer + wallet status.
// @Tags Arifpay
// @Accept json
// @Produce json
// @Param request body domain.WebhookRequest true "Arifpay webhook payload"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/arifpay/b2c-webhook [post]
func (h *Handler) HandleArifpayB2CWebhook(c *fiber.Ctx) error {
var req domain.WebhookRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Invalid webhook payload",
})
}
// 🚨 Decide how to get userId:
// If you get it from auth context/middleware, extract it here.
// For now, let's assume userId comes from your auth claims:
// userId, ok := c.Locals("user_id").(int64)
// if !ok {
// return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
// Error: "missing user id",
// Message: "Unauthorized",
// })
// }
err := h.arifpaySvc.ProcessWebhook(c.Context(), req, false)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to process webhook",
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Webhook processed successfully",
Success: true,
StatusCode: fiber.StatusOK,
})
}
// ArifpayVerifyByTransactionIDHandler godoc
// @Summary Verify Arifpay Transaction
// @Description Verifies a transaction using transaction ID and payment type
// @Tags Arifpay
// @Accept json
// @Produce json
// @Param request body domain.ArifpayVerifyByTransactionIDRequest true "Transaction verification payload"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 502 {object} domain.ErrorResponse
// @Router /api/v1/arifpay/transaction-id/verify-transaction [post]
func (h *Handler) ArifpayVerifyByTransactionIDHandler(c *fiber.Ctx) error {
var req domain.ArifpayVerifyByTransactionIDRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to parse request body",
})
}
resp, err := h.arifpaySvc.VerifyTransactionByTransactionID(c.Context(), req)
if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to verify transaction",
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Transaction verified successfully",
Data: resp,
Success: true,
StatusCode: fiber.StatusOK,
})
}
// ArifpayVerifyBySessionIDHandler godoc
// @Summary Verify Arifpay Transaction by Session ID
// @Description Verifies an Arifpay transaction using a session ID
// @Tags Arifpay
// @Accept json
// @Produce json
// @Param session_id query string true "Arifpay Session ID"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 502 {object} domain.ErrorResponse
// @Router /api/v1/arifpay/session-id/verify-transaction/{session_id} [get]
func (h *Handler) ArifpayVerifyBySessionIDHandler(c *fiber.Ctx) error {
sessionID := c.Query("session_id")
if sessionID == "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: "missing session_id",
Message: "session_id query parameter is required",
})
}
resp, err := h.arifpaySvc.VerifyTransactionBySessionID(c.Context(), sessionID)
if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to verify transaction",
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Transaction verified successfully",
Data: resp,
Success: true,
StatusCode: fiber.StatusOK,
})
}
// ExecuteTransfer handles B2C transfers via Telebirr, CBE, or MPESA.
//
// @Summary Execute B2C Transfer
// @Description Initiates a B2C transfer using Telebirr, CBE, or MPESA depending on the "type" query parameter
// @Tags Arifpay
// @Accept json
// @Produce json
// @Param type query string true "Transfer type (telebirr, cbe, mpesa)"
// @Param request body domain.CheckoutSessionClientRequest true "Transfer request payload"
// @Success 200 {object} map[string]string "message: transfer executed successfully"
// @Failure 400 {object} map[string]string "error: invalid request or unsupported transfer type"
// @Failure 500 {object} map[string]string "error: internal server error"
// @Router /api/v1/arifpay/b2c/transfer [post]
func (h *Handler) ExecuteArifpayB2CTransfer(c *fiber.Ctx) error {
transferType := c.Query("type")
if transferType == "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to process your withdrawal request",
Error: "missing query parameter: type (telebirr, cbe, mpesa)",
})
}
userId, ok := c.Locals("user_id").(int64)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
Error: "missing user id",
Message: "Unauthorized",
})
}
var req domain.CheckoutSessionClientRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to process your withdrawal request",
Error: "invalid request body",
})
}
var err error
switch transferType {
case "telebirr":
err = h.arifpaySvc.ExecuteTelebirrB2CTransfer(c.Context(), req, userId)
case "cbe":
err = h.arifpaySvc.ExecuteCBEB2CTransfer(c.Context(), req, userId)
case "mpesa":
err = h.arifpaySvc.ExecuteMPesaB2CTransfer(c.Context(), req, userId)
default:
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to process your withdrawal request",
Error: "unsupported transfer type, must be one of: telebirr, cbe, mpesa",
})
}
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to process your withdrawal request",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Withdrawal process initiated successfully",
Success: true,
StatusCode: fiber.StatusOK,
})
}
// GetPaymentMethodsHandler returns the list of all Arifpay payment methods
//
// @Summary List Arifpay Payment Methods
// @Description Returns all payment method IDs and names for Arifpay
// @Tags Arifpay
// @Produce json
// @Success 200 {object} []domain.ARIFPAYPaymentMethod
// @Router /api/v1/arifpay/payment-methods [get]
func (h *Handler) GetArifpayPaymentMethodsHandler(c *fiber.Ctx) error {
methods := h.arifpaySvc.GetPaymentMethodsMapping()
return c.Status(fiber.StatusOK).JSON(domain.Response{
Success: true,
Message: "Arifpay payment methods fetched successfully",
Data: methods,
StatusCode: fiber.StatusOK,
})
}