package server import ( "context" "encoding/hex" "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/labstack/echo/v4" stripe "github.com/stripe/stripe-go/v79" "github.com/stripe/stripe-go/v79/webhook" ) type stubStripeEventService struct { intentCalled bool chargeCalled bool err error } func (s *stubStripeEventService) HandlePaymentIntentSucceeded(ctx context.Context, pi *stripe.PaymentIntent) error { s.intentCalled = true return s.err } func (s *stubStripeEventService) HandleChargeRefunded(ctx context.Context, ch *stripe.Charge) error { s.chargeCalled = true return s.err } func TestHandleStripeWebhookPaymentIntent(t *testing.T) { secret := "whsec_test" payload := map[string]any{ "id": "evt_test", "type": "payment_intent.succeeded", "api_version": stripe.APIVersion, "data": map[string]any{ "object": map[string]any{ "id": "pi_123", "amount": 10000, "amount_received": 10000, "currency": "eur", "status": "succeeded", "metadata": map[string]string{"booking_id": "42"}, "payment_method_types": []string{"card"}, }, }, } payloadBytes, err := json.Marshal(payload) if err != nil { t.Fatalf("failed to marshal payload: %v", err) } ts := time.Now() sig := webhook.ComputeSignature(ts, payloadBytes, secret) sigHeader := fmt.Sprintf("t=%d,v1=%s", ts.Unix(), hex.EncodeToString(sig)) if _, err := webhook.ConstructEvent(payloadBytes, sigHeader, secret); err != nil { t.Fatalf("signature validation failed in setup: %v", err) } e := echo.New() req := httptest.NewRequest(http.MethodPost, "/webhooks/stripe", strings.NewReader(string(payloadBytes))) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) req.Header.Set("Stripe-Signature", sigHeader) rec := httptest.NewRecorder() c := e.NewContext(req, rec) service := &stubStripeEventService{} handler := handleStripeWebhook(service, secret) if err := handler(c); err != nil { t.Fatalf("handler returned error: %v", err) } if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } if !service.intentCalled { t.Fatalf("expected payment intent handler to be called") } } func TestHandleStripeWebhookChargeRefunded(t *testing.T) { secret := "whsec_test" payload := map[string]any{ "id": "evt_charge", "type": "charge.refunded", "api_version": stripe.APIVersion, "data": map[string]any{ "object": map[string]any{ "id": "ch_123", "amount": 5000, "amount_refunded": 5000, "payment_intent": map[string]any{ "id": "pi_123", }, }, }, } payloadBytes, _ := json.Marshal(payload) ts := time.Now() sig := webhook.ComputeSignature(ts, payloadBytes, secret) sigHeader := fmt.Sprintf("t=%d,v1=%s", ts.Unix(), hex.EncodeToString(sig)) if _, err := webhook.ConstructEvent(payloadBytes, sigHeader, secret); err != nil { t.Fatalf("signature validation failed in setup: %v", err) } e := echo.New() req := httptest.NewRequest(http.MethodPost, "/webhooks/stripe", strings.NewReader(string(payloadBytes))) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) req.Header.Set("Stripe-Signature", sigHeader) rec := httptest.NewRecorder() c := e.NewContext(req, rec) service := &stubStripeEventService{} handler := handleStripeWebhook(service, secret) if err := handler(c); err != nil { t.Fatalf("handler returned error: %v", err) } if !service.chargeCalled { t.Fatalf("expected charge handler to be called") } } func TestHandleStripeWebhookInvalidSignature(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodPost, "/webhooks/stripe", strings.NewReader("{}")) req.Header.Set("Stripe-Signature", "invalid") rec := httptest.NewRecorder() c := e.NewContext(req, rec) handler := handleStripeWebhook(&stubStripeEventService{}, "secret") err := handler(c) if err == nil { t.Fatal("expected error for invalid signature") } if httpErr, ok := err.(*echo.HTTPError); !ok || httpErr.Code != http.StatusBadRequest { t.Fatalf("expected 400 HTTP error, got %v", err) } }