Serve /payment/success and /api/v1/payments/chapa/success to verify tx_ref on redirect and activate subscriptions, and share the payment success template with ArifPay. Co-authored-by: Cursor <cursoragent@cursor.com>
165 lines
5.5 KiB
Go
165 lines
5.5 KiB
Go
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(),
|
|
})
|
|
}
|