routes_users.go (already on main) calls settingsHandler.GetPreferences / UpdatePreferences and gdprExportHandler.ExportJSON, but the methods only existed in the working tree — main wouldn't compile, so deploy.yml's build-backend job was stuck on the same compile error every run. Bundles the WIP swagger annotation sweep across chat / marketplace / role / settings / gdpr / etc. handlers with the regenerated swagger.json, swagger.yaml, docs.go and openapi.yaml. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
186 lines
6.1 KiB
Go
186 lines
6.1 KiB
Go
package handlers
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
|
|
"veza-backend-api/internal/core/marketplace"
|
|
apperrors "veza-backend-api/internal/errors"
|
|
"veza-backend-api/internal/services"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// SellHandler handles Stripe Connect seller payout endpoints
|
|
type SellHandler struct {
|
|
db *gorm.DB
|
|
stripeConnect *services.StripeConnectService
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewSellHandler creates a new SellHandler
|
|
func NewSellHandler(db *gorm.DB, stripeConnect *services.StripeConnectService, logger *zap.Logger) *SellHandler {
|
|
return &SellHandler{
|
|
db: db,
|
|
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
|
|
// @Summary Start Stripe Connect onboarding
|
|
// @Description Initiate the Stripe Connect onboarding process and get a redirection link.
|
|
// @Tags Sell
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param data body ConnectOnboardRequest true "Return and Refresh URLs"
|
|
// @Success 200 {object} handlers.APIResponse{data=object{onboarding_url=string}}
|
|
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
|
// @Router /api/v1/sell/connect/onboard [post]
|
|
func (h *SellHandler) ConnectOnboard(c *gin.Context) {
|
|
if h.stripeConnect == nil {
|
|
RespondWithAppError(c, apperrors.NewServiceUnavailableError("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) {
|
|
RespondWithAppError(c, apperrors.NewServiceUnavailableError("Stripe Connect is not enabled"))
|
|
return
|
|
}
|
|
h.logger.Error("CreateOnboardingLink failed", zap.Error(err), zap.String("user_id", userID.String()))
|
|
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to create onboarding link", err))
|
|
return
|
|
}
|
|
|
|
RespondSuccess(c, http.StatusOK, gin.H{"onboarding_url": url})
|
|
}
|
|
|
|
// ConnectCallback syncs account status after Stripe redirect (called by frontend after return)
|
|
// @Summary Sync Stripe account
|
|
// @Description Callback endpoint to sync Stripe account status after redirection.
|
|
// @Tags Sell
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {object} handlers.APIResponse "Account synced"
|
|
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
|
// @Router /api/v1/sell/connect/callback [get]
|
|
func (h *SellHandler) ConnectCallback(c *gin.Context) {
|
|
if h.stripeConnect == nil {
|
|
RespondWithAppError(c, apperrors.NewServiceUnavailableError("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) {
|
|
RespondWithAppError(c, apperrors.NewNotFoundError("Stripe account"))
|
|
return
|
|
}
|
|
if errors.Is(err, services.ErrStripeConnectDisabled) {
|
|
RespondWithAppError(c, apperrors.NewServiceUnavailableError("Stripe Connect is not enabled"))
|
|
return
|
|
}
|
|
h.logger.Error("HandleOnboardingCallback failed", zap.Error(err), zap.String("user_id", userID.String()))
|
|
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to sync account status", err))
|
|
return
|
|
}
|
|
|
|
RespondSuccess(c, http.StatusOK, gin.H{"message": "Account synced"})
|
|
}
|
|
|
|
// GetBalance returns the seller's Stripe Connect balance
|
|
// @Summary Get seller balance
|
|
// @Description Get the current Stripe Connect balance (connected, available, pending) for the seller.
|
|
// @Tags Sell
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {object} handlers.APIResponse{data=object}
|
|
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
|
// @Router /api/v1/sell/balance [get]
|
|
func (h *SellHandler) GetBalance(c *gin.Context) {
|
|
if h.stripeConnect == nil {
|
|
RespondWithAppError(c, apperrors.NewServiceUnavailableError("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) {
|
|
RespondWithAppError(c, apperrors.NewServiceUnavailableError("Stripe Connect is not enabled"))
|
|
return
|
|
}
|
|
h.logger.Error("GetBalance failed", zap.Error(err), zap.String("user_id", userID.String()))
|
|
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get balance", err))
|
|
return
|
|
}
|
|
|
|
RespondSuccess(c, http.StatusOK, gin.H{
|
|
"connected": bal.Connected,
|
|
"available": bal.Available,
|
|
"pending": bal.Pending,
|
|
})
|
|
}
|
|
|
|
// GetSellerTransfers returns the transfer history for the authenticated seller (v0.603)
|
|
// @Summary Get transfer history
|
|
// @Description Get a list of all Stripe transfers for the authenticated seller.
|
|
// @Tags Sell
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {array} marketplace.SellerTransfer
|
|
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
|
// @Router /api/v1/sell/transfers [get]
|
|
func (h *SellHandler) GetSellerTransfers(c *gin.Context) {
|
|
userID, ok := GetUserIDUUID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var transfers []marketplace.SellerTransfer
|
|
if err := h.db.Where("seller_id = ?", userID).Order("created_at DESC").Find(&transfers).Error; err != nil {
|
|
h.logger.Error("GetSellerTransfers failed", zap.Error(err), zap.String("user_id", userID.String()))
|
|
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to fetch transfers", err))
|
|
return
|
|
}
|
|
|
|
RespondSuccess(c, http.StatusOK, transfers)
|
|
}
|