veza/veza-backend-api/internal/handlers/support_handler.go
senke 2281c91e8b feat(v0.13.5): polish marketplace & compliance — KYC, support, payout E2E
- Seller KYC via Stripe Identity (start verification, status check, webhook)
- Support ticket system (backend handler + frontend form page)
- E2E payout flow integration test (sale → payment → balance → payout)
- Migrations: seller_kyc columns, support_tickets table
- Frontend: SupportPage with SUMI design, lazy loading, routing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 14:57:19 +01:00

100 lines
3 KiB
Go

package handlers
import (
"net/http"
"strings"
"time"
apperrors "veza-backend-api/internal/errors"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"gorm.io/gorm"
)
// SupportTicket represents a support contact request (v0.13.5 TASK-MKT-004)
type SupportTicket struct {
ID string `json:"id" gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
UserID string `json:"user_id,omitempty" gorm:"type:uuid"`
Email string `json:"email" gorm:"type:varchar(255);not null"`
Subject string `json:"subject" gorm:"type:varchar(500);not null"`
Message string `json:"message" gorm:"type:text;not null"`
Category string `json:"category" gorm:"type:varchar(100)"`
Status string `json:"status" gorm:"type:varchar(50);default:'open'"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
}
func (SupportTicket) TableName() string {
return "support_tickets"
}
// SupportHandler handles support/contact form submissions
type SupportHandler struct {
db *gorm.DB
logger *zap.Logger
}
// NewSupportHandler creates a new support handler
func NewSupportHandler(db *gorm.DB, logger *zap.Logger) *SupportHandler {
return &SupportHandler{
db: db,
logger: logger,
}
}
// SubmitTicketRequest is the request body for submitting a support ticket
type SubmitTicketRequest struct {
Email string `json:"email" binding:"required,email"`
Subject string `json:"subject" binding:"required,min=3,max=500"`
Message string `json:"message" binding:"required,min=10,max=5000"`
Category string `json:"category"`
}
// SubmitTicket creates a new support ticket from the contact form
func (h *SupportHandler) SubmitTicket(c *gin.Context) {
var req SubmitTicketRequest
if err := c.ShouldBindJSON(&req); err != nil {
RespondWithAppError(c, apperrors.NewValidationError("Invalid request: email, subject (3-500 chars), and message (10-5000 chars) are required"))
return
}
// Sanitize
req.Email = strings.TrimSpace(req.Email)
req.Subject = strings.TrimSpace(req.Subject)
req.Message = strings.TrimSpace(req.Message)
if req.Category == "" {
req.Category = "general"
}
// Get user ID if authenticated (optional)
var userID string
if uid, exists := c.Get("user_id"); exists {
userID = uid.(interface{ String() string }).String()
}
ticket := SupportTicket{
UserID: userID,
Email: req.Email,
Subject: req.Subject,
Message: req.Message,
Category: req.Category,
Status: "open",
}
if err := h.db.WithContext(c.Request.Context()).Create(&ticket).Error; err != nil {
h.logger.Error("Failed to create support ticket", zap.Error(err))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to submit support request", err))
return
}
h.logger.Info("Support ticket created",
zap.String("ticket_id", ticket.ID),
zap.String("email", req.Email),
zap.String("category", req.Category),
)
RespondSuccess(c, http.StatusCreated, gin.H{
"ticket_id": ticket.ID,
"message": "Your support request has been submitted. We'll respond to your email within 48 hours.",
})
}