rentease/internal/server/handle_stripe_webhook_test.go

150 lines
4.1 KiB
Go

package server
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/labstack/echo/v4"
"github.com/stripe/stripe-go/v83"
"github.com/stripe/stripe-go/v83/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)
}
}