feat: implement invisible 12% buyer fee
Business model: - Seller registers R$10,00 → Seller sees R$10,00 → Seller receives R$10,00 - Buyer searches → sees R$11,20 (+12%) → pays R$11,20 - Marketplace keeps R$1,20 (12%) Changes: - config.go: Add BuyerFeeRate (default 0.12) - handler.go: Add buyerFeeRate field to Handler struct - product_handler.go: SearchProducts inflates prices for buyers - server.go: Pass cfg.BuyerFeeRate to handler.New() - handler_test.go: Fix 3 New() calls with fee rate Env var: BUYER_FEE_RATE (default: 0.12)
This commit is contained in:
parent
285f586371
commit
12e2503244
5 changed files with 21 additions and 7 deletions
|
|
@ -17,6 +17,7 @@ type Config struct {
|
||||||
DatabaseURL string
|
DatabaseURL string
|
||||||
MercadoPagoBaseURL string
|
MercadoPagoBaseURL string
|
||||||
MarketplaceCommission float64
|
MarketplaceCommission float64
|
||||||
|
BuyerFeeRate float64 // Fee rate applied to buyer prices (e.g., 0.12 = 12%)
|
||||||
JWTSecret string
|
JWTSecret string
|
||||||
JWTExpiresIn time.Duration
|
JWTExpiresIn time.Duration
|
||||||
PasswordPepper string
|
PasswordPepper string
|
||||||
|
|
@ -40,6 +41,7 @@ func Load() Config {
|
||||||
DatabaseURL: getEnv("DATABASE_URL", "postgres://postgres:postgres@localhost:5432/saveinmed?sslmode=disable"),
|
DatabaseURL: getEnv("DATABASE_URL", "postgres://postgres:postgres@localhost:5432/saveinmed?sslmode=disable"),
|
||||||
MercadoPagoBaseURL: getEnv("MERCADOPAGO_BASE_URL", "https://api.mercadopago.com"),
|
MercadoPagoBaseURL: getEnv("MERCADOPAGO_BASE_URL", "https://api.mercadopago.com"),
|
||||||
MarketplaceCommission: getEnvFloat("MARKETPLACE_COMMISSION", 2.5),
|
MarketplaceCommission: getEnvFloat("MARKETPLACE_COMMISSION", 2.5),
|
||||||
|
BuyerFeeRate: getEnvFloat("BUYER_FEE_RATE", 0.12), // 12% invisible fee
|
||||||
JWTSecret: getEnv("JWT_SECRET", "dev-secret"),
|
JWTSecret: getEnv("JWT_SECRET", "dev-secret"),
|
||||||
JWTExpiresIn: getEnvDuration("JWT_EXPIRES_IN", 24*time.Hour),
|
JWTExpiresIn: getEnvDuration("JWT_EXPIRES_IN", 24*time.Hour),
|
||||||
PasswordPepper: getEnv("PASSWORD_PEPPER", ""),
|
PasswordPepper: getEnv("PASSWORD_PEPPER", ""),
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,12 @@ import (
|
||||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
svc *usecase.Service
|
svc *usecase.Service
|
||||||
|
buyerFeeRate float64 // Rate to inflate prices for buyers (e.g., 0.12 = 12%)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(svc *usecase.Service) *Handler {
|
func New(svc *usecase.Service, buyerFeeRate float64) *Handler {
|
||||||
return &Handler{svc: svc}
|
return &Handler{svc: svc, buyerFeeRate: buyerFeeRate}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register godoc
|
// Register godoc
|
||||||
|
|
|
||||||
|
|
@ -312,7 +312,7 @@ func newTestHandler() *Handler {
|
||||||
repo := NewMockRepository()
|
repo := NewMockRepository()
|
||||||
gateway := &MockPaymentGateway{}
|
gateway := &MockPaymentGateway{}
|
||||||
svc := usecase.NewService(repo, gateway, 0.05, "test-secret", time.Hour, "test-pepper")
|
svc := usecase.NewService(repo, gateway, 0.05, "test-secret", time.Hour, "test-pepper")
|
||||||
return New(svc)
|
return New(svc, 0.12) // 12% buyer fee rate for testing
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListProducts(t *testing.T) {
|
func TestListProducts(t *testing.T) {
|
||||||
|
|
@ -398,7 +398,7 @@ func TestAdminLogin_Success(t *testing.T) {
|
||||||
repo := NewMockRepository()
|
repo := NewMockRepository()
|
||||||
gateway := &MockPaymentGateway{}
|
gateway := &MockPaymentGateway{}
|
||||||
svc := usecase.NewService(repo, gateway, 0.05, "test-secret", time.Hour, "test-pepper")
|
svc := usecase.NewService(repo, gateway, 0.05, "test-secret", time.Hour, "test-pepper")
|
||||||
h := New(svc)
|
h := New(svc, 0.12)
|
||||||
|
|
||||||
// Create admin user through service (which hashes password)
|
// Create admin user through service (which hashes password)
|
||||||
companyID, _ := uuid.NewV7()
|
companyID, _ := uuid.NewV7()
|
||||||
|
|
@ -442,7 +442,7 @@ func TestAdminLogin_WrongPassword(t *testing.T) {
|
||||||
repo := NewMockRepository()
|
repo := NewMockRepository()
|
||||||
gateway := &MockPaymentGateway{}
|
gateway := &MockPaymentGateway{}
|
||||||
svc := usecase.NewService(repo, gateway, 0.05, "test-secret", time.Hour, "test-pepper")
|
svc := usecase.NewService(repo, gateway, 0.05, "test-secret", time.Hour, "test-pepper")
|
||||||
h := New(svc)
|
h := New(svc, 0.12)
|
||||||
|
|
||||||
// Create admin user
|
// Create admin user
|
||||||
companyID, _ := uuid.NewV7()
|
companyID, _ := uuid.NewV7()
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,17 @@ func (h *Handler) SearchProducts(w http.ResponseWriter, r *http.Request) {
|
||||||
writeError(w, http.StatusInternalServerError, err)
|
writeError(w, http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply invisible buyer fee: inflate prices by buyerFeeRate (e.g., 12%)
|
||||||
|
// The buyer sees inflated prices, but the DB stores the original seller price
|
||||||
|
if h.buyerFeeRate > 0 {
|
||||||
|
for i := range result.Products {
|
||||||
|
originalPrice := result.Products[i].PriceCents
|
||||||
|
inflatedPrice := int64(float64(originalPrice) * (1 + h.buyerFeeRate))
|
||||||
|
result.Products[i].PriceCents = inflatedPrice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, result)
|
writeJSON(w, http.StatusOK, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ func New(cfg config.Config) (*Server, error) {
|
||||||
repo := postgres.New(db)
|
repo := postgres.New(db)
|
||||||
gateway := payments.NewMercadoPagoGateway(cfg.MercadoPagoBaseURL, cfg.MarketplaceCommission)
|
gateway := payments.NewMercadoPagoGateway(cfg.MercadoPagoBaseURL, cfg.MarketplaceCommission)
|
||||||
svc := usecase.NewService(repo, gateway, cfg.MarketplaceCommission, cfg.JWTSecret, cfg.JWTExpiresIn, cfg.PasswordPepper)
|
svc := usecase.NewService(repo, gateway, cfg.MarketplaceCommission, cfg.JWTSecret, cfg.JWTExpiresIn, cfg.PasswordPepper)
|
||||||
h := handler.New(svc)
|
h := handler.New(svc, cfg.BuyerFeeRate)
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue