From 12e25032442d76f6557eda59e973c26affa17590 Mon Sep 17 00:00:00 2001 From: Tiago Yamamoto Date: Fri, 26 Dec 2025 23:23:18 -0300 Subject: [PATCH] feat: implement invisible 12% buyer fee MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- backend/internal/config/config.go | 2 ++ backend/internal/http/handler/handler.go | 7 ++++--- backend/internal/http/handler/handler_test.go | 6 +++--- backend/internal/http/handler/product_handler.go | 11 +++++++++++ backend/internal/server/server.go | 2 +- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index 905632a..6ca51df 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -17,6 +17,7 @@ type Config struct { DatabaseURL string MercadoPagoBaseURL string MarketplaceCommission float64 + BuyerFeeRate float64 // Fee rate applied to buyer prices (e.g., 0.12 = 12%) JWTSecret string JWTExpiresIn time.Duration PasswordPepper string @@ -40,6 +41,7 @@ func Load() Config { DatabaseURL: getEnv("DATABASE_URL", "postgres://postgres:postgres@localhost:5432/saveinmed?sslmode=disable"), MercadoPagoBaseURL: getEnv("MERCADOPAGO_BASE_URL", "https://api.mercadopago.com"), MarketplaceCommission: getEnvFloat("MARKETPLACE_COMMISSION", 2.5), + BuyerFeeRate: getEnvFloat("BUYER_FEE_RATE", 0.12), // 12% invisible fee JWTSecret: getEnv("JWT_SECRET", "dev-secret"), JWTExpiresIn: getEnvDuration("JWT_EXPIRES_IN", 24*time.Hour), PasswordPepper: getEnv("PASSWORD_PEPPER", ""), diff --git a/backend/internal/http/handler/handler.go b/backend/internal/http/handler/handler.go index 0152ec6..1addd8f 100644 --- a/backend/internal/http/handler/handler.go +++ b/backend/internal/http/handler/handler.go @@ -16,11 +16,12 @@ import ( var json = jsoniter.ConfigCompatibleWithStandardLibrary 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 { - return &Handler{svc: svc} +func New(svc *usecase.Service, buyerFeeRate float64) *Handler { + return &Handler{svc: svc, buyerFeeRate: buyerFeeRate} } // Register godoc diff --git a/backend/internal/http/handler/handler_test.go b/backend/internal/http/handler/handler_test.go index 7001193..0899830 100644 --- a/backend/internal/http/handler/handler_test.go +++ b/backend/internal/http/handler/handler_test.go @@ -312,7 +312,7 @@ func newTestHandler() *Handler { repo := NewMockRepository() gateway := &MockPaymentGateway{} 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) { @@ -398,7 +398,7 @@ func TestAdminLogin_Success(t *testing.T) { repo := NewMockRepository() gateway := &MockPaymentGateway{} 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) companyID, _ := uuid.NewV7() @@ -442,7 +442,7 @@ func TestAdminLogin_WrongPassword(t *testing.T) { repo := NewMockRepository() gateway := &MockPaymentGateway{} svc := usecase.NewService(repo, gateway, 0.05, "test-secret", time.Hour, "test-pepper") - h := New(svc) + h := New(svc, 0.12) // Create admin user companyID, _ := uuid.NewV7() diff --git a/backend/internal/http/handler/product_handler.go b/backend/internal/http/handler/product_handler.go index def0ab9..5d76afa 100644 --- a/backend/internal/http/handler/product_handler.go +++ b/backend/internal/http/handler/product_handler.go @@ -142,6 +142,17 @@ func (h *Handler) SearchProducts(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusInternalServerError, err) 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) } diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index 67a2e8f..ec95418 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -37,7 +37,7 @@ func New(cfg config.Config) (*Server, error) { repo := postgres.New(db) gateway := payments.NewMercadoPagoGateway(cfg.MercadoPagoBaseURL, cfg.MarketplaceCommission) 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()