From a2485f1279f542ca02e798f1fa406782f6c171db Mon Sep 17 00:00:00 2001 From: Pedro Correa Date: Thu, 10 Jul 2025 16:21:48 -0300 Subject: [PATCH] :sparkles: configuring zap logger and request default values --- cmd/app/main.go | 13 +++++++++---- docker-compose.yml | 4 ++++ go.mod | 5 +++++ go.sum | 4 ++++ internal/providers/braintree.go | 10 +++++++--- internal/providers/provider.go | 23 ++++++++++++++++++----- internal/providers/provider_test.go | 23 ++++++++++++++++++++--- internal/providers/stripe.go | 10 +++++++--- internal/service/payment_service.go | 5 ++++- internal/web/handler/payments_handler.go | 14 +++++++++++++- internal/web/server.go | 7 +++++-- mocks/braintree.yml | 2 +- 12 files changed, 97 insertions(+), 23 deletions(-) diff --git a/cmd/app/main.go b/cmd/app/main.go index 020f36d..78cffa9 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -8,6 +8,7 @@ import ( "github.com/Tulkdan/payment-gateway/internal/providers" "github.com/Tulkdan/payment-gateway/internal/service" "github.com/Tulkdan/payment-gateway/internal/web" + "go.uber.org/zap" ) func getEnv(key, defaultValue string) string { @@ -21,14 +22,18 @@ func main() { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() + logger, _ := zap.NewDevelopment() + defer logger.Sync() + sugar := logger.Sugar() + providers := providers.NewUseProviders([]providers.Provider{ - providers.NewBraintreeProvider(getEnv("BRAINTREE_URL", "localhost:8001")), - providers.NewStripeProvider(getEnv("STRIPE_URL", "localhost:8002")), - }) + providers.NewBraintreeProvider(getEnv("BRAINTREE_URL", "http://localhost:8001")), + providers.NewStripeProvider(getEnv("STRIPE_URL", "http://localhost:8002")), + }, sugar) paymentsService := service.NewPaymentService(providers) port := getEnv("PORT", "8000") - server := web.NewServer(paymentsService, port) + server := web.NewServer(paymentsService, port, sugar) server.ConfigureRouter() srvErr := make(chan error, 1) diff --git a/docker-compose.yml b/docker-compose.yml index e2fdd4b..f908705 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,8 @@ services: stripe: build: dockerfile: $PWD/Dockerfile-stubby + ports: + - "8002:8882" environment: PORT: 8882 volumes: @@ -23,6 +25,8 @@ services: braintree: build: dockerfile: $PWD/Dockerfile-stubby + ports: + - "8001:8882" environment: PORT: 8882 volumes: diff --git a/go.mod b/go.mod index 1e4988f..c8f0524 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,8 @@ module github.com/Tulkdan/payment-gateway go 1.24.4 require github.com/google/uuid v1.6.0 + +require ( + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect +) diff --git a/go.sum b/go.sum index 7790d7c..4d0a179 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= diff --git a/internal/providers/braintree.go b/internal/providers/braintree.go index 7ad1ac6..8136dbd 100644 --- a/internal/providers/braintree.go +++ b/internal/providers/braintree.go @@ -38,7 +38,7 @@ type BraintreeCharge struct { PaymentMethod BraintreeChargePaymentMethod `json:"paymentMethod"` } -func (b BraintreeProvider) Charge(ctx context.Context, request *domain.Payment) (*domain.Provider, error) { +func (b *BraintreeProvider) Charge(ctx context.Context, request *domain.Payment) (*domain.Provider, error) { body := b.createChargeBody(request) req, err := http.NewRequestWithContext(ctx, http.MethodPost, b.Url+"/charges", bytes.NewBuffer(body)) if err != nil { @@ -55,7 +55,7 @@ func (b BraintreeProvider) Charge(ctx context.Context, request *domain.Payment) return b.responseCharge(response) } -func (b BraintreeProvider) createChargeBody(request *domain.Payment) []byte { +func (b *BraintreeProvider) createChargeBody(request *domain.Payment) []byte { toSend := &BraintreeCharge{ Amount: request.Amount, Currency: request.Currency, @@ -87,7 +87,7 @@ type BraintreeChargeResponse struct { CardId uuid.UUID `json:"cardId"` } -func (b BraintreeProvider) responseCharge(response *http.Response) (*domain.Provider, error) { +func (b *BraintreeProvider) responseCharge(response *http.Response) (*domain.Provider, error) { var data BraintreeChargeResponse if err := json.NewDecoder(response.Body).Decode(&data); err != nil { return nil, err @@ -116,3 +116,7 @@ func (b BraintreeProvider) responseCharge(response *http.Response) (*domain.Prov } return providerResponse, nil } + +func (b *BraintreeProvider) GetName() string { + return "Braintree Provider" +} diff --git a/internal/providers/provider.go b/internal/providers/provider.go index b859c22..073e5aa 100644 --- a/internal/providers/provider.go +++ b/internal/providers/provider.go @@ -6,38 +6,51 @@ import ( "time" "github.com/Tulkdan/payment-gateway/internal/domain" + "go.uber.org/zap" ) var thirtySecondTimout = 30 * time.Second type Provider interface { + GetName() string Charge(ctx context.Context, request *domain.Payment) (*domain.Provider, error) } type UseProviders struct { providers []Provider timeout time.Duration + logger *zap.SugaredLogger } -func NewUseProviders(providers []Provider) *UseProviders { - return ConfigurableUseProvider(providers, thirtySecondTimout) +func NewUseProviders(providers []Provider, logger *zap.SugaredLogger) *UseProviders { + return ConfigurableUseProvider(providers, logger, thirtySecondTimout) } -func ConfigurableUseProvider(providers []Provider, timeout time.Duration) *UseProviders { +func ConfigurableUseProvider(providers []Provider, logger *zap.SugaredLogger, timeout time.Duration) *UseProviders { return &UseProviders{ providers: providers, + logger: logger, timeout: timeout, } } func (p *UseProviders) Payment(ctx context.Context, payment *domain.Payment) (*domain.Provider, error) { var err error = nil + attempts := 0 for _, provider := range p.providers { select { - case data := <-charge(ctx, payment, provider): + case data := <-p.charge(ctx, payment, provider): + p.logger.Debugw("[Payment] Sending request successfully", + "provider", provider.GetName(), + "attempt", attempts) + return data, nil case <-time.After(p.timeout): + p.logger.Errorw("[Payment] Timeout for provider to respond", + "provider", provider.GetName(), + "attempt", attempts) + err = errors.New("Timeout") continue } @@ -46,7 +59,7 @@ func (p *UseProviders) Payment(ctx context.Context, payment *domain.Payment) (*d return nil, err } -func charge(ctx context.Context, charge *domain.Payment, provider Provider) chan *domain.Provider { +func (p *UseProviders) charge(ctx context.Context, charge *domain.Payment, provider Provider) chan *domain.Provider { ch := make(chan *domain.Provider) go func() { diff --git a/internal/providers/provider_test.go b/internal/providers/provider_test.go index 6e7ba93..fa2cb57 100644 --- a/internal/providers/provider_test.go +++ b/internal/providers/provider_test.go @@ -7,6 +7,7 @@ import ( "github.com/Tulkdan/payment-gateway/internal/domain" "github.com/Tulkdan/payment-gateway/internal/providers" + "go.uber.org/zap" ) type SpyProvider struct { @@ -22,14 +23,22 @@ func (s *SpyProvider) Charge(ctx context.Context, request *domain.Payment) (*dom return s.Response, nil } +func (s *SpyProvider) GetName() string { + return "Mock" +} + func TestProvider(t *testing.T) { t.Run("should make request for first provider", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + defer logger.Sync() + sugar := logger.Sugar() + spyFirst := &SpyProvider{Timeout: 10 * time.Millisecond, Response: &domain.Provider{Description: "First"}} spySecond := &SpyProvider{Timeout: 10 * time.Millisecond, Response: &domain.Provider{Description: "Second"}} payment, _ := domain.NewPayment(1000, "R$", "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1}) - useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, 15*time.Millisecond) + useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, sugar, 15*time.Millisecond) data, err := useProvider.Payment(context.Background(), payment) if err != nil { @@ -45,12 +54,16 @@ func TestProvider(t *testing.T) { }) t.Run("should make request for second provider when first provider timeouts", func(t *testing.T) { + logger, _ := zap.NewProduction() + defer logger.Sync() + sugar := logger.Sugar() + spyFirst := &SpyProvider{Timeout: 20 * time.Millisecond, Response: &domain.Provider{Description: "First"}} spySecond := &SpyProvider{Timeout: 10 * time.Millisecond, Response: &domain.Provider{Description: "Second"}} payment, _ := domain.NewPayment(1000, "R$", "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1}) - useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, 15*time.Millisecond) + useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, sugar, 15*time.Millisecond) data, err := useProvider.Payment(context.Background(), payment) if err != nil { @@ -66,12 +79,16 @@ func TestProvider(t *testing.T) { }) t.Run("should return error when all providers timeout", func(t *testing.T) { + logger, _ := zap.NewProduction() + defer logger.Sync() + sugar := logger.Sugar() + spyFirst := &SpyProvider{Timeout: 20 * time.Millisecond, Response: &domain.Provider{Description: "First"}} spySecond := &SpyProvider{Timeout: 20 * time.Millisecond, Response: &domain.Provider{Description: "Second"}} payment, _ := domain.NewPayment(1000, "R$", "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1}) - useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, 5*time.Millisecond) + useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, sugar, 5*time.Millisecond) data, err := useProvider.Payment(context.Background(), payment) if data != nil { diff --git a/internal/providers/stripe.go b/internal/providers/stripe.go index 6eda59d..4fbfc68 100644 --- a/internal/providers/stripe.go +++ b/internal/providers/stripe.go @@ -34,7 +34,7 @@ type StripeCharge struct { Card StripeChargeCard `json:"card"` } -func (b StripeProvider) Charge(ctx context.Context, request *domain.Payment) (*domain.Provider, error) { +func (b *StripeProvider) Charge(ctx context.Context, request *domain.Payment) (*domain.Provider, error) { body := b.createChargeBody(request) req, err := http.NewRequestWithContext(ctx, http.MethodPost, b.Url+"/transactions", bytes.NewBuffer(body)) if err != nil { @@ -52,7 +52,7 @@ func (b StripeProvider) Charge(ctx context.Context, request *domain.Payment) (*d return b.responseCharge(response) } -func (b StripeProvider) createChargeBody(request *domain.Payment) []byte { +func (b *StripeProvider) createChargeBody(request *domain.Payment) []byte { toSend := &StripeCharge{ Amount: request.Amount, Currency: request.Currency, @@ -82,7 +82,7 @@ type StripeChargeResponse struct { CardId uuid.UUID `json:"cardId"` } -func (b StripeProvider) responseCharge(response *http.Response) (*domain.Provider, error) { +func (b *StripeProvider) responseCharge(response *http.Response) (*domain.Provider, error) { var data StripeChargeResponse if err := json.NewDecoder(response.Body).Decode(&data); err != nil { return nil, err @@ -111,3 +111,7 @@ func (b StripeProvider) responseCharge(response *http.Response) (*domain.Provide } return providerResponse, nil } + +func (s *StripeProvider) GetName() string { + return "Stripe provider" +} diff --git a/internal/service/payment_service.go b/internal/service/payment_service.go index 3f88edf..9cc50b6 100644 --- a/internal/service/payment_service.go +++ b/internal/service/payment_service.go @@ -2,6 +2,7 @@ package service import ( "context" + "fmt" "github.com/Tulkdan/payment-gateway/internal/domain" "github.com/Tulkdan/payment-gateway/internal/dto" @@ -22,10 +23,12 @@ func (p *PaymentService) CreatePayment(ctx context.Context, input dto.PaymentInp return nil, err } - _, err = p.providers.Payment(ctx, payment) + data, err := p.providers.Payment(ctx, payment) if err != nil { return nil, err } + fmt.Println(data) + return &dto.PaymentOutput{Message: "Processed successfully"}, nil } diff --git a/internal/web/handler/payments_handler.go b/internal/web/handler/payments_handler.go index e88831f..3813af7 100644 --- a/internal/web/handler/payments_handler.go +++ b/internal/web/handler/payments_handler.go @@ -6,27 +6,39 @@ import ( "github.com/Tulkdan/payment-gateway/internal/dto" "github.com/Tulkdan/payment-gateway/internal/service" + "go.uber.org/zap" ) type PaymentsHandler struct { paymentService *service.PaymentService + + logger *zap.SugaredLogger } -func NewPaymentsHandler(paymentsService *service.PaymentService) *PaymentsHandler { +func NewPaymentsHandler(paymentsService *service.PaymentService, logger *zap.SugaredLogger) *PaymentsHandler { return &PaymentsHandler{ paymentService: paymentsService, + logger: logger.Named("PaymentHandler"), } } func (p *PaymentsHandler) Create(w http.ResponseWriter, r *http.Request) { var body dto.PaymentInput if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + p.logger.Errorw("Body incomplete", + "error", err.Error(), + "requestId", r.Context().Value("request-id").(string)) + http.Error(w, err.Error(), http.StatusBadRequest) return } response, err := p.paymentService.CreatePayment(r.Context(), body) if err != nil { + p.logger.Errorw("Failed to create payment", + "error", err.Error(), + "requestId", r.Context().Value("request-id").(string)) + http.Error(w, err.Error(), http.StatusBadRequest) return } diff --git a/internal/web/server.go b/internal/web/server.go index a5abd80..c3eb749 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -9,27 +9,30 @@ import ( "github.com/Tulkdan/payment-gateway/internal/service" "github.com/Tulkdan/payment-gateway/internal/web/handler" "github.com/Tulkdan/payment-gateway/internal/web/middleware" + "go.uber.org/zap" ) type Server struct { port string router *http.ServeMux server *http.Server + logger *zap.SugaredLogger paymentsService *service.PaymentService } -func NewServer(paymentsService *service.PaymentService, port string) *Server { +func NewServer(paymentsService *service.PaymentService, port string, logger *zap.SugaredLogger) *Server { return &Server{ port: port, paymentsService: paymentsService, + logger: logger, } } func (s *Server) ConfigureRouter() { mux := http.NewServeMux() - paymentsHandler := handler.NewPaymentsHandler(s.paymentsService) + paymentsHandler := handler.NewPaymentsHandler(s.paymentsService, s.logger) mux.HandleFunc("POST /payments", middleware.WithRequestId(paymentsHandler.Create)) // r.HandleFunc("POST /refunds", func(http.ResponseWriter, *http.Request) {}) diff --git a/mocks/braintree.yml b/mocks/braintree.yml index bb29a20..d0b35da 100644 --- a/mocks/braintree.yml +++ b/mocks/braintree.yml @@ -1,5 +1,5 @@ - request: - url: ^/charge$ + url: ^/charges$ method: POST headers: content-type: application/json -- 2.51.2