Files
template-vitrine/backend/internal/modules/orders/gate.go
T
Xor290 919c807004
ci-api / test (push) Failing after 8m7s
ci-web / test (push) Failing after 5m5s
chore: build
2026-09-20 12:18:33 +02:00

102 lines
3.6 KiB
Go

package orders
import (
"context"
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"backend/internal/platform/middleware"
)
// SettingsProvider and VerificationChecker are defined on the consumer
// side (this package), matching ProductFinder/UnitFinder/PriceResolver
// above: orders never imports the site or customerverification packages
// directly, main.go just needs to supply something satisfying these shapes.
type SettingsProvider interface {
CustomerCheckoutSettings(ctx context.Context) (accountsEnabled, verificationRequired bool, err error)
}
type VerificationChecker interface {
IsApproved(ctx context.Context, userID uuid.UUID) (bool, error)
}
// OrdersGate reports the site-wide "vitrine vs boutique" switch
// (site.Settings.OrdersEnabled). It runs before every other order gate: a
// pure showcase site rejects order creation outright, regardless of the
// customer-account settings below.
type OrdersGate interface {
OrdersEnabled(ctx context.Context) (bool, error)
}
// RequireOrdersEnabled blocks order creation entirely while the admin has
// switched the site to showcase-only mode.
func RequireOrdersEnabled(gate OrdersGate) gin.HandlerFunc {
return func(c *gin.Context) {
enabled, err := gate.OrdersEnabled(c.Request.Context())
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "failed to check orders setting"})
return
}
if !enabled {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "ordering is currently disabled for this shop"})
return
}
c.Next()
}
}
// RequireAccountsEnabled blocks order creation entirely while the admin
// hasn't turned on customer accounts. This is intentionally stricter than
// "guest checkout allowed by default": some shops (e.g. selling
// age/ID-restricted goods) need every order tied to a verified account, so
// the admin opts into that by flipping this one setting, and until they
// do, nobody -- guest or not -- can check out.
func RequireAccountsEnabled(settings SettingsProvider) gin.HandlerFunc {
return func(c *gin.Context) {
enabled, _, err := settings.CustomerCheckoutSettings(c.Request.Context())
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "failed to check checkout settings"})
return
}
if !enabled {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "ordering requires a customer account, which is currently disabled"})
return
}
c.Next()
}
}
// RequireApprovedVerificationIfNeeded must run after RequireAccountsEnabled
// and middleware.RequireCustomer (so a userID is already in context). It is
// a no-op unless the admin also turned on identity verification.
func RequireApprovedVerificationIfNeeded(settings SettingsProvider, verification VerificationChecker) gin.HandlerFunc {
return func(c *gin.Context) {
_, verificationRequired, err := settings.CustomerCheckoutSettings(c.Request.Context())
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "failed to check checkout settings"})
return
}
if !verificationRequired {
c.Next()
return
}
userID, ok := middleware.GetUserID(c)
if !ok {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
approved, err := verification.IsApproved(c.Request.Context(), userID)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "failed to check verification status"})
return
}
if !approved {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "identity verification must be approved before ordering"})
return
}
c.Next()
}
}