veza/veza-backend-api/internal/handlers/sell_handler.go
senke 83ed4f315b
Some checks failed
Backend API CI / test-unit (push) Failing after 0s
Backend API CI / test-integration (push) Failing after 0s
Frontend CI / test (push) Failing after 0s
Storybook Audit / Build & audit Storybook (push) Failing after 0s
chore(release): v0.602 — Payout, Dette Technique & Tests E2E
- Stripe Connect: onboarding, balance, SellerDashboardView
- Interceptors: auth.ts, error.ts extracted, facade
- Grafana: dashboards enriched (p50, top endpoints, 4xx, WS, commerce)
- E2E commerce: product->order->review->invoice
- SMOKE_TEST_V0602, RETROSPECTIVE_V0602, PAYOUT_MANUAL
- Archive V0_602 scope, V0_603 placeholder, SCOPE_CONTROL v0.603
- Fix sanitizer regex (Go no backreferences)
- Marketplace test schema: product_licenses, product_images, orders, licenses
2026-02-23 22:32:01 +01:00

127 lines
3.8 KiB
Go

package handlers
import (
"errors"
"net/http"
"veza-backend-api/internal/services"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// SellHandler handles Stripe Connect seller payout endpoints
type SellHandler struct {
stripeConnect *services.StripeConnectService
logger *zap.Logger
}
// NewSellHandler creates a new SellHandler
func NewSellHandler(stripeConnect *services.StripeConnectService, logger *zap.Logger) *SellHandler {
return &SellHandler{
stripeConnect: stripeConnect,
logger: logger,
}
}
// ConnectOnboardRequest is the request body for onboarding
type ConnectOnboardRequest struct {
ReturnURL string `json:"return_url"`
RefreshURL string `json:"refresh_url"`
}
// ConnectOnboard starts Stripe Connect onboarding and returns the onboarding URL
func (h *SellHandler) ConnectOnboard(c *gin.Context) {
if h.stripeConnect == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Stripe Connect is not enabled"})
return
}
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
var req ConnectOnboardRequest
_ = c.ShouldBindJSON(&req)
returnURL := req.ReturnURL
if returnURL == "" {
returnURL = c.Request.URL.Scheme + "://" + c.Request.Host + "/sell/dashboard?onboarded=success"
}
refreshURL := req.RefreshURL
if refreshURL == "" {
refreshURL = c.Request.URL.Scheme + "://" + c.Request.Host + "/sell/dashboard?onboarded=refresh"
}
url, err := h.stripeConnect.CreateOnboardingLink(c.Request.Context(), userID, returnURL, refreshURL)
if err != nil {
if errors.Is(err, services.ErrStripeConnectDisabled) {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Stripe Connect is not enabled"})
return
}
h.logger.Error("CreateOnboardingLink failed", zap.Error(err), zap.String("user_id", userID.String()))
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create onboarding link"})
return
}
RespondSuccess(c, http.StatusOK, gin.H{"onboarding_url": url})
}
// ConnectCallback syncs account status after Stripe redirect (called by frontend after return)
func (h *SellHandler) ConnectCallback(c *gin.Context) {
if h.stripeConnect == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Stripe Connect is not enabled"})
return
}
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
if err := h.stripeConnect.HandleOnboardingCallback(c.Request.Context(), userID); err != nil {
if errors.Is(err, services.ErrNoStripeAccount) {
c.JSON(http.StatusNotFound, gin.H{"error": "No Stripe account found"})
return
}
if errors.Is(err, services.ErrStripeConnectDisabled) {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Stripe Connect is not enabled"})
return
}
h.logger.Error("HandleOnboardingCallback failed", zap.Error(err), zap.String("user_id", userID.String()))
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync account status"})
return
}
RespondSuccess(c, http.StatusOK, gin.H{"message": "Account synced"})
}
// GetBalance returns the seller's Stripe Connect balance
func (h *SellHandler) GetBalance(c *gin.Context) {
if h.stripeConnect == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Stripe Connect is not enabled"})
return
}
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
bal, err := h.stripeConnect.GetBalance(c.Request.Context(), userID)
if err != nil {
if errors.Is(err, services.ErrStripeConnectDisabled) {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Stripe Connect is not enabled"})
return
}
h.logger.Error("GetBalance failed", zap.Error(err), zap.String("user_id", userID.String()))
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get balance"})
return
}
RespondSuccess(c, http.StatusOK, gin.H{
"connected": bal.Connected,
"available": bal.Available,
"pending": bal.Pending,
})
}