this repo has no description

:sparkles: configuring zap logger and request default values

+9 -4
cmd/app/main.go
··· 8 8 "github.com/Tulkdan/payment-gateway/internal/providers" 9 9 "github.com/Tulkdan/payment-gateway/internal/service" 10 10 "github.com/Tulkdan/payment-gateway/internal/web" 11 + "go.uber.org/zap" 11 12 ) 12 13 13 14 func getEnv(key, defaultValue string) string { ··· 21 22 ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) 22 23 defer stop() 23 24 25 + logger, _ := zap.NewDevelopment() 26 + defer logger.Sync() 27 + sugar := logger.Sugar() 28 + 24 29 providers := providers.NewUseProviders([]providers.Provider{ 25 - providers.NewBraintreeProvider(getEnv("BRAINTREE_URL", "localhost:8001")), 26 - providers.NewStripeProvider(getEnv("STRIPE_URL", "localhost:8002")), 27 - }) 30 + providers.NewBraintreeProvider(getEnv("BRAINTREE_URL", "http://localhost:8001")), 31 + providers.NewStripeProvider(getEnv("STRIPE_URL", "http://localhost:8002")), 32 + }, sugar) 28 33 paymentsService := service.NewPaymentService(providers) 29 34 30 35 port := getEnv("PORT", "8000") 31 - server := web.NewServer(paymentsService, port) 36 + server := web.NewServer(paymentsService, port, sugar) 32 37 server.ConfigureRouter() 33 38 34 39 srvErr := make(chan error, 1)
+4
docker-compose.yml
··· 15 15 stripe: 16 16 build: 17 17 dockerfile: $PWD/Dockerfile-stubby 18 + ports: 19 + - "8002:8882" 18 20 environment: 19 21 PORT: 8882 20 22 volumes: ··· 23 25 braintree: 24 26 build: 25 27 dockerfile: $PWD/Dockerfile-stubby 28 + ports: 29 + - "8001:8882" 26 30 environment: 27 31 PORT: 8882 28 32 volumes:
+5
go.mod
··· 3 3 go 1.24.4 4 4 5 5 require github.com/google/uuid v1.6.0 6 + 7 + require ( 8 + go.uber.org/multierr v1.11.0 // indirect 9 + go.uber.org/zap v1.27.0 // indirect 10 + )
+4
go.sum
··· 1 1 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 2 2 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 3 + go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 4 + go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 5 + go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 6 + go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+7 -3
internal/providers/braintree.go
··· 38 38 PaymentMethod BraintreeChargePaymentMethod `json:"paymentMethod"` 39 39 } 40 40 41 - func (b BraintreeProvider) Charge(ctx context.Context, request *domain.Payment) (*domain.Provider, error) { 41 + func (b *BraintreeProvider) Charge(ctx context.Context, request *domain.Payment) (*domain.Provider, error) { 42 42 body := b.createChargeBody(request) 43 43 req, err := http.NewRequestWithContext(ctx, http.MethodPost, b.Url+"/charges", bytes.NewBuffer(body)) 44 44 if err != nil { ··· 55 55 return b.responseCharge(response) 56 56 } 57 57 58 - func (b BraintreeProvider) createChargeBody(request *domain.Payment) []byte { 58 + func (b *BraintreeProvider) createChargeBody(request *domain.Payment) []byte { 59 59 toSend := &BraintreeCharge{ 60 60 Amount: request.Amount, 61 61 Currency: request.Currency, ··· 87 87 CardId uuid.UUID `json:"cardId"` 88 88 } 89 89 90 - func (b BraintreeProvider) responseCharge(response *http.Response) (*domain.Provider, error) { 90 + func (b *BraintreeProvider) responseCharge(response *http.Response) (*domain.Provider, error) { 91 91 var data BraintreeChargeResponse 92 92 if err := json.NewDecoder(response.Body).Decode(&data); err != nil { 93 93 return nil, err ··· 116 116 } 117 117 return providerResponse, nil 118 118 } 119 + 120 + func (b *BraintreeProvider) GetName() string { 121 + return "Braintree Provider" 122 + }
+18 -5
internal/providers/provider.go
··· 6 6 "time" 7 7 8 8 "github.com/Tulkdan/payment-gateway/internal/domain" 9 + "go.uber.org/zap" 9 10 ) 10 11 11 12 var thirtySecondTimout = 30 * time.Second 12 13 13 14 type Provider interface { 15 + GetName() string 14 16 Charge(ctx context.Context, request *domain.Payment) (*domain.Provider, error) 15 17 } 16 18 17 19 type UseProviders struct { 18 20 providers []Provider 19 21 timeout time.Duration 22 + logger *zap.SugaredLogger 20 23 } 21 24 22 - func NewUseProviders(providers []Provider) *UseProviders { 23 - return ConfigurableUseProvider(providers, thirtySecondTimout) 25 + func NewUseProviders(providers []Provider, logger *zap.SugaredLogger) *UseProviders { 26 + return ConfigurableUseProvider(providers, logger, thirtySecondTimout) 24 27 } 25 28 26 - func ConfigurableUseProvider(providers []Provider, timeout time.Duration) *UseProviders { 29 + func ConfigurableUseProvider(providers []Provider, logger *zap.SugaredLogger, timeout time.Duration) *UseProviders { 27 30 return &UseProviders{ 28 31 providers: providers, 32 + logger: logger, 29 33 timeout: timeout, 30 34 } 31 35 } 32 36 33 37 func (p *UseProviders) Payment(ctx context.Context, payment *domain.Payment) (*domain.Provider, error) { 34 38 var err error = nil 39 + attempts := 0 35 40 36 41 for _, provider := range p.providers { 37 42 select { 38 - case data := <-charge(ctx, payment, provider): 43 + case data := <-p.charge(ctx, payment, provider): 44 + p.logger.Debugw("[Payment] Sending request successfully", 45 + "provider", provider.GetName(), 46 + "attempt", attempts) 47 + 39 48 return data, nil 40 49 case <-time.After(p.timeout): 50 + p.logger.Errorw("[Payment] Timeout for provider to respond", 51 + "provider", provider.GetName(), 52 + "attempt", attempts) 53 + 41 54 err = errors.New("Timeout") 42 55 continue 43 56 } ··· 46 59 return nil, err 47 60 } 48 61 49 - func charge(ctx context.Context, charge *domain.Payment, provider Provider) chan *domain.Provider { 62 + func (p *UseProviders) charge(ctx context.Context, charge *domain.Payment, provider Provider) chan *domain.Provider { 50 63 ch := make(chan *domain.Provider) 51 64 52 65 go func() {
+20 -3
internal/providers/provider_test.go
··· 7 7 8 8 "github.com/Tulkdan/payment-gateway/internal/domain" 9 9 "github.com/Tulkdan/payment-gateway/internal/providers" 10 + "go.uber.org/zap" 10 11 ) 11 12 12 13 type SpyProvider struct { ··· 22 23 return s.Response, nil 23 24 } 24 25 26 + func (s *SpyProvider) GetName() string { 27 + return "Mock" 28 + } 29 + 25 30 func TestProvider(t *testing.T) { 26 31 t.Run("should make request for first provider", func(t *testing.T) { 32 + logger, _ := zap.NewDevelopment() 33 + defer logger.Sync() 34 + sugar := logger.Sugar() 35 + 27 36 spyFirst := &SpyProvider{Timeout: 10 * time.Millisecond, Response: &domain.Provider{Description: "First"}} 28 37 spySecond := &SpyProvider{Timeout: 10 * time.Millisecond, Response: &domain.Provider{Description: "Second"}} 29 38 30 39 payment, _ := domain.NewPayment(1000, "R$", "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1}) 31 40 32 - useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, 15*time.Millisecond) 41 + useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, sugar, 15*time.Millisecond) 33 42 data, err := useProvider.Payment(context.Background(), payment) 34 43 35 44 if err != nil { ··· 45 54 }) 46 55 47 56 t.Run("should make request for second provider when first provider timeouts", func(t *testing.T) { 57 + logger, _ := zap.NewProduction() 58 + defer logger.Sync() 59 + sugar := logger.Sugar() 60 + 48 61 spyFirst := &SpyProvider{Timeout: 20 * time.Millisecond, Response: &domain.Provider{Description: "First"}} 49 62 spySecond := &SpyProvider{Timeout: 10 * time.Millisecond, Response: &domain.Provider{Description: "Second"}} 50 63 51 64 payment, _ := domain.NewPayment(1000, "R$", "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1}) 52 65 53 - useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, 15*time.Millisecond) 66 + useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, sugar, 15*time.Millisecond) 54 67 data, err := useProvider.Payment(context.Background(), payment) 55 68 56 69 if err != nil { ··· 66 79 }) 67 80 68 81 t.Run("should return error when all providers timeout", func(t *testing.T) { 82 + logger, _ := zap.NewProduction() 83 + defer logger.Sync() 84 + sugar := logger.Sugar() 85 + 69 86 spyFirst := &SpyProvider{Timeout: 20 * time.Millisecond, Response: &domain.Provider{Description: "First"}} 70 87 spySecond := &SpyProvider{Timeout: 20 * time.Millisecond, Response: &domain.Provider{Description: "Second"}} 71 88 72 89 payment, _ := domain.NewPayment(1000, "R$", "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1}) 73 90 74 - useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, 5*time.Millisecond) 91 + useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, sugar, 5*time.Millisecond) 75 92 data, err := useProvider.Payment(context.Background(), payment) 76 93 77 94 if data != nil {
+7 -3
internal/providers/stripe.go
··· 34 34 Card StripeChargeCard `json:"card"` 35 35 } 36 36 37 - func (b StripeProvider) Charge(ctx context.Context, request *domain.Payment) (*domain.Provider, error) { 37 + func (b *StripeProvider) Charge(ctx context.Context, request *domain.Payment) (*domain.Provider, error) { 38 38 body := b.createChargeBody(request) 39 39 req, err := http.NewRequestWithContext(ctx, http.MethodPost, b.Url+"/transactions", bytes.NewBuffer(body)) 40 40 if err != nil { ··· 52 52 return b.responseCharge(response) 53 53 } 54 54 55 - func (b StripeProvider) createChargeBody(request *domain.Payment) []byte { 55 + func (b *StripeProvider) createChargeBody(request *domain.Payment) []byte { 56 56 toSend := &StripeCharge{ 57 57 Amount: request.Amount, 58 58 Currency: request.Currency, ··· 82 82 CardId uuid.UUID `json:"cardId"` 83 83 } 84 84 85 - func (b StripeProvider) responseCharge(response *http.Response) (*domain.Provider, error) { 85 + func (b *StripeProvider) responseCharge(response *http.Response) (*domain.Provider, error) { 86 86 var data StripeChargeResponse 87 87 if err := json.NewDecoder(response.Body).Decode(&data); err != nil { 88 88 return nil, err ··· 111 111 } 112 112 return providerResponse, nil 113 113 } 114 + 115 + func (s *StripeProvider) GetName() string { 116 + return "Stripe provider" 117 + }
+4 -1
internal/service/payment_service.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "fmt" 5 6 6 7 "github.com/Tulkdan/payment-gateway/internal/domain" 7 8 "github.com/Tulkdan/payment-gateway/internal/dto" ··· 22 23 return nil, err 23 24 } 24 25 25 - _, err = p.providers.Payment(ctx, payment) 26 + data, err := p.providers.Payment(ctx, payment) 26 27 if err != nil { 27 28 return nil, err 28 29 } 30 + 31 + fmt.Println(data) 29 32 30 33 return &dto.PaymentOutput{Message: "Processed successfully"}, nil 31 34 }
+13 -1
internal/web/handler/payments_handler.go
··· 6 6 7 7 "github.com/Tulkdan/payment-gateway/internal/dto" 8 8 "github.com/Tulkdan/payment-gateway/internal/service" 9 + "go.uber.org/zap" 9 10 ) 10 11 11 12 type PaymentsHandler struct { 12 13 paymentService *service.PaymentService 14 + 15 + logger *zap.SugaredLogger 13 16 } 14 17 15 - func NewPaymentsHandler(paymentsService *service.PaymentService) *PaymentsHandler { 18 + func NewPaymentsHandler(paymentsService *service.PaymentService, logger *zap.SugaredLogger) *PaymentsHandler { 16 19 return &PaymentsHandler{ 17 20 paymentService: paymentsService, 21 + logger: logger.Named("PaymentHandler"), 18 22 } 19 23 } 20 24 21 25 func (p *PaymentsHandler) Create(w http.ResponseWriter, r *http.Request) { 22 26 var body dto.PaymentInput 23 27 if err := json.NewDecoder(r.Body).Decode(&body); err != nil { 28 + p.logger.Errorw("Body incomplete", 29 + "error", err.Error(), 30 + "requestId", r.Context().Value("request-id").(string)) 31 + 24 32 http.Error(w, err.Error(), http.StatusBadRequest) 25 33 return 26 34 } 27 35 28 36 response, err := p.paymentService.CreatePayment(r.Context(), body) 29 37 if err != nil { 38 + p.logger.Errorw("Failed to create payment", 39 + "error", err.Error(), 40 + "requestId", r.Context().Value("request-id").(string)) 41 + 30 42 http.Error(w, err.Error(), http.StatusBadRequest) 31 43 return 32 44 }
+5 -2
internal/web/server.go
··· 9 9 "github.com/Tulkdan/payment-gateway/internal/service" 10 10 "github.com/Tulkdan/payment-gateway/internal/web/handler" 11 11 "github.com/Tulkdan/payment-gateway/internal/web/middleware" 12 + "go.uber.org/zap" 12 13 ) 13 14 14 15 type Server struct { 15 16 port string 16 17 router *http.ServeMux 17 18 server *http.Server 19 + logger *zap.SugaredLogger 18 20 19 21 paymentsService *service.PaymentService 20 22 } 21 23 22 - func NewServer(paymentsService *service.PaymentService, port string) *Server { 24 + func NewServer(paymentsService *service.PaymentService, port string, logger *zap.SugaredLogger) *Server { 23 25 return &Server{ 24 26 port: port, 25 27 paymentsService: paymentsService, 28 + logger: logger, 26 29 } 27 30 } 28 31 29 32 func (s *Server) ConfigureRouter() { 30 33 mux := http.NewServeMux() 31 34 32 - paymentsHandler := handler.NewPaymentsHandler(s.paymentsService) 35 + paymentsHandler := handler.NewPaymentsHandler(s.paymentsService, s.logger) 33 36 34 37 mux.HandleFunc("POST /payments", middleware.WithRequestId(paymentsHandler.Create)) 35 38 // r.HandleFunc("POST /refunds", func(http.ResponseWriter, *http.Request) {})
+1 -1
mocks/braintree.yml
··· 1 1 - request: 2 - url: ^/charge$ 2 + url: ^/charges$ 3 3 method: POST 4 4 headers: 5 5 content-type: application/json