veza/veza-backend-api/internal/handlers/kyc_handler.go
senke 1ccaa03737 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

98 lines
2.8 KiB
Go

package handlers
import (
"errors"
"net/http"
apperrors "veza-backend-api/internal/errors"
"veza-backend-api/internal/services"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// KYCHandler handles seller identity verification endpoints (v0.13.5 TASK-MKT-001)
type KYCHandler struct {
kycService *services.KYCService
logger *zap.Logger
}
// NewKYCHandler creates a new KYC handler
func NewKYCHandler(kycService *services.KYCService, logger *zap.Logger) *KYCHandler {
return &KYCHandler{
kycService: kycService,
logger: logger,
}
}
// CreateVerificationRequest is the request body for starting KYC
type CreateVerificationRequest struct {
ReturnURL string `json:"return_url"`
}
// StartVerification creates a Stripe Identity verification session
func (h *KYCHandler) StartVerification(c *gin.Context) {
if h.kycService == nil {
RespondWithAppError(c, apperrors.NewServiceUnavailableError("KYC verification is not available"))
return
}
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
var req CreateVerificationRequest
_ = c.ShouldBindJSON(&req)
returnURL := req.ReturnURL
if returnURL == "" {
returnURL = c.Request.URL.Scheme + "://" + c.Request.Host + "/sell?kyc=complete"
}
session, err := h.kycService.CreateVerificationSession(c.Request.Context(), userID, returnURL)
if err != nil {
if errors.Is(err, services.ErrKYCAlreadyDone) {
RespondSuccess(c, http.StatusOK, gin.H{"status": "verified", "message": "Already verified"})
return
}
if errors.Is(err, services.ErrKYCNotAvailable) {
RespondWithAppError(c, apperrors.NewServiceUnavailableError("KYC verification is not available"))
return
}
if errors.Is(err, services.ErrNoStripeAccount) {
RespondWithAppError(c, apperrors.NewValidationError("Complete Stripe Connect onboarding first"))
return
}
h.logger.Error("StartVerification failed", zap.Error(err), zap.String("user_id", userID.String()))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to start verification", err))
return
}
RespondSuccess(c, http.StatusCreated, session)
}
// GetVerificationStatus returns the current KYC status for a seller
func (h *KYCHandler) GetVerificationStatus(c *gin.Context) {
if h.kycService == nil {
RespondSuccess(c, http.StatusOK, gin.H{"status": "not_available"})
return
}
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
status, err := h.kycService.GetVerificationStatus(c.Request.Context(), userID)
if err != nil {
if errors.Is(err, services.ErrKYCNotAvailable) {
RespondSuccess(c, http.StatusOK, gin.H{"status": "not_available"})
return
}
h.logger.Error("GetVerificationStatus failed", zap.Error(err), zap.String("user_id", userID.String()))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get verification status", err))
return
}
RespondSuccess(c, http.StatusOK, gin.H{"status": status})
}