package server import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "github.com/stripe/stripe-go/v83" ) 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) } restore := stubConstructEvent(stripe.EventTypePaymentIntentSucceeded, payloadBytes) defer restore() service := &stubStripeEventService{} handler := handleStripeWebhook(service, secret) req := httptest.NewRequest(http.MethodPost, "/webhooks/stripe", strings.NewReader(string(payloadBytes))) req.Header.Set("Content-Type", "application/json") req.Header.Set("Stripe-Signature", "test") rec := httptest.NewRecorder() handler(rec, req) 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) restore := stubConstructEvent(stripe.EventTypeChargeRefunded, payloadBytes) defer restore() service := &stubStripeEventService{} handler := handleStripeWebhook(service, secret) req := httptest.NewRequest(http.MethodPost, "/webhooks/stripe", strings.NewReader(string(payloadBytes))) req.Header.Set("Content-Type", "application/json") req.Header.Set("Stripe-Signature", "test") rec := httptest.NewRecorder() handler(rec, req) if !service.chargeCalled { t.Fatalf("expected charge handler to be called") } } func TestHandleStripeWebhookInvalidSignature(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/webhooks/stripe", strings.NewReader("{}")) req.Header.Set("Stripe-Signature", "invalid") rec := httptest.NewRecorder() handler := handleStripeWebhook(&stubStripeEventService{}, "secret") handler(rec, req) if rec.Code != http.StatusBadRequest { t.Fatalf("expected 400 status, got %d", rec.Code) } } func stubConstructEvent(eventType stripe.EventType, payload []byte) func() { original := constructEvent constructEvent = func(_ []byte, _ string, _ string) (stripe.Event, error) { event := stripe.Event{Type: eventType} event.Data = &stripe.EventData{Raw: payload} return event, nil } return func() { constructEvent = original } }