package handlers import ( "encoding/json" "errors" "Yimaru-Backend/internal/domain" "Yimaru-Backend/internal/services/chapa" "github.com/gofiber/fiber/v2" ) // HandleChapaWebhook godoc // @Summary Handle Chapa webhook // @Description Processes payment notifications from Chapa (charge.success, etc.) // @Tags payments // @Accept json // @Produce json // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Router /api/v1/payments/webhook [post] func (h *Handler) HandleChapaWebhook(c *fiber.Ctx) error { body := c.Body() signature := c.Get("x-chapa-signature") if signature == "" { signature = c.Get("chapa-signature") } if err := h.chapaSvc.VerifyWebhookSignature(body, signature); err != nil { h.logger.Error("Invalid Chapa webhook signature", "error", err) return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{ Message: "Invalid webhook signature", }) } var payload domain.ChapaWebhookPayload if err := json.Unmarshal(body, &payload); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid webhook payload", Error: err.Error(), }) } if err := h.chapaSvc.ProcessPaymentWebhook(c.Context(), payload); err != nil { if errors.Is(err, chapa.ErrPaymentAlreadyPaid) { return c.JSON(domain.Response{Message: "Webhook already processed"}) } h.logger.Error("Failed to process Chapa webhook", "error", err, "tx_ref", payload.TxRef) return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to process webhook", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Webhook processed successfully", }) } // HandleChapaCallback godoc // @Summary Chapa payment callback // @Description Verifies payment after Chapa redirects to callback_url // @Tags payments // @Produce json // @Param trx_ref query string false "Transaction reference" // @Param ref_id query string false "Chapa reference ID" // @Param status query string false "Payment status" // @Success 200 {object} domain.Response // @Router /api/v1/payments/chapa/callback [get] func (h *Handler) HandleChapaCallback(c *fiber.Ctx) error { query := domain.ChapaCallbackQuery{ TrxRef: c.Query("trx_ref"), RefID: c.Query("ref_id"), Status: c.Query("status"), } if query.TrxRef == "" { query.TrxRef = c.Query("tx_ref") } if query.TrxRef == "" { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "trx_ref is required", }) } if err := h.chapaSvc.ProcessCallback(c.Context(), query); err != nil { if errors.Is(err, chapa.ErrPaymentAlreadyPaid) { return c.JSON(domain.Response{Message: "Payment already processed"}) } h.logger.Error("Failed to process Chapa callback", "error", err, "trx_ref", query.TrxRef) return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to process callback", Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Callback processed successfully", }) } // HandleChapaSuccessPage godoc // @Summary Chapa payment success page // @Description Displays the Yimaru Academy success page after Chapa redirects the learner to return_url // @Tags payments // @Produce html // @Param trx_ref query string false "Chapa transaction reference (tx_ref)" // @Param tx_ref query string false "Chapa transaction reference" // @Param ref_id query string false "Chapa reference ID" // @Param status query string false "Payment status from Chapa redirect" // @Success 200 {string} string "HTML success page" // @Router /api/v1/payments/chapa/success [get] // @Router /payment/success [get] func (h *Handler) HandleChapaSuccessPage(c *fiber.Ctx) error { txRef := firstNonEmpty( c.Query("trx_ref"), c.Query("tx_ref"), ) page := defaultPaymentSuccessPage() if txRef != "" { payment, err := h.chapaSvc.VerifyPayment(c.Context(), txRef) if err != nil { h.logger.Warn("Failed to verify Chapa success redirect", "error", err, "tx_ref", txRef) page.Body = "Thank you for your payment. We are confirming it with Chapa and will activate your subscription shortly." page.Helper = "You can safely return to Yimaru Academy. If activation takes longer than expected, refresh the app in a moment." page.Reference = txRef } else { page.Reference = txRef page.PlanName = derefString(payment.PlanName) if payment.Status == string(domain.PaymentStatusSuccess) { page.StatusLabel = "Subscription active" page.Body = "Your Yimaru Academy subscription is active. You now have access to your learning content." } else { page.Body = "Thank you for your payment. We received your success redirect and are finalizing subscription activation." page.StatusLabel = "Processing confirmation" } } } else { page.Helper = "Return to Yimaru Academy and refresh your subscription status if you do not see access immediately." } html, err := renderPaymentSuccessPage(page) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to render success page") } c.Type("html", "utf-8") return c.SendString(html) } // GetChapaPaymentMethods godoc // @Summary Get Chapa payment methods // @Description Returns payment methods available on Chapa checkout // @Tags payments // @Produce json // @Success 200 {object} domain.Response // @Router /api/v1/payments/methods [get] func (h *Handler) GetChapaPaymentMethods(c *fiber.Ctx) error { return c.JSON(domain.Response{ Message: "Payment methods retrieved successfully", Data: h.chapaSvc.GetPaymentMethods(), }) }