- 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>
100 lines
3 KiB
Go
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.",
|
|
})
|
|
}
|