Files
telecom-platform/apps/api-server/internal/payment/controller_refund.go
T
nutcas3 cb1d7171e0 feat: Add PaymentController with modular handlers for payment operations, refunds, and webhook processing
- Add PaymentController struct with paymentService/db dependencies and NewPaymentController constructor
- Add SetupRoutes with /payments endpoints (POST /top-up, POST/DELETE/GET /payment-methods, GET /transactions/:id, POST /transactions/:id/refund) and /webhooks/stripe endpoint
- Add ProcessTopUp handler with amount/paymentMethodId validation, subscriberID authentication, and PaymentService.
2026-04-24 08:53:34 +03:00

88 lines
2.6 KiB
Go

package payment
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/nutcas3/telecom-platform/apps/api-server/internal/models"
)
// RefundTransaction refunds a completed transaction, creating a reverse
// Transaction record and adjusting subscriber balance.
func (pc *PaymentController) RefundTransaction(c *gin.Context) {
transactionID := c.Param("id")
if transactionID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Transaction ID required"})
return
}
var req struct {
Amount float64 `json:"amount" binding:"required,min=0.01"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
subscriberID := c.GetInt("subscriberID")
if subscriberID == 0 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
transaction, err := pc.db.GetTransaction(c.Request.Context(), transactionID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Transaction not found"})
return
}
if transaction.SubscriberID != uint(subscriberID) {
c.JSON(http.StatusForbidden, gin.H{"error": "Transaction does not belong to subscriber"})
return
}
if transaction.Status != "COMPLETED" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot refund non-completed transaction"})
return
}
refundResp, err := pc.paymentService.gateway.RefundPayment(c.Request.Context(), transaction.TransactionID, req.Amount)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
refund := &models.Transaction{
SubscriberID: transaction.SubscriberID,
TransactionID: refundResp.RefundID,
Type: "REFUND",
Amount: req.Amount,
Currency: refundResp.Currency,
Status: string(refundResp.Status),
Description: fmt.Sprintf("Refund for transaction %s", transactionID),
ParentID: &transaction.ID,
CreatedAt: refundResp.ProcessedAt,
UpdatedAt: refundResp.ProcessedAt,
}
if err := pc.db.CreateTransaction(c.Request.Context(), refund); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save refund"})
return
}
if err := pc.db.UpdateSubscriberBalance(c.Request.Context(), transaction.SubscriberID, -req.Amount); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update balance"})
return
}
c.JSON(http.StatusOK, gin.H{
"refund": refund,
"message": "Refund processed successfully",
})
}
// HandleStripeWebhook delegates to WebhookHandler.
func (pc *PaymentController) HandleStripeWebhook(c *gin.Context) {
NewWebhookHandler(pc.paymentService, pc.db).HandleStripeWebhook(c)
}