102 lines
3.6 KiB
Go
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()
|
|
}
|
|
}
|