diff --git a/app/api/v1/shared/webhooks.py b/app/api/v1/shared/webhooks.py index 065139db..991b9da3 100644 --- a/app/api/v1/shared/webhooks.py +++ b/app/api/v1/shared/webhooks.py @@ -14,7 +14,7 @@ from sqlalchemy.orm import Session from app.core.database import get_db from app.exceptions import InvalidWebhookSignatureException, WebhookMissingSignatureException from app.services.stripe_service import stripe_service -from app.services.stripe_webhook_handler import stripe_webhook_handler +from app.handlers.stripe_webhook import stripe_webhook_handler router = APIRouter(prefix="/webhooks") logger = logging.getLogger(__name__) diff --git a/app/handlers/__init__.py b/app/handlers/__init__.py new file mode 100644 index 00000000..ac55df6c --- /dev/null +++ b/app/handlers/__init__.py @@ -0,0 +1,10 @@ +# app/handlers/__init__.py +""" +Handlers module. + +Contains event handlers for webhooks and other external events. +""" + +from app.handlers.stripe_webhook import stripe_webhook_handler + +__all__ = ["stripe_webhook_handler"] diff --git a/app/services/stripe_webhook_handler.py b/app/handlers/stripe_webhook.py similarity index 97% rename from app/services/stripe_webhook_handler.py rename to app/handlers/stripe_webhook.py index f81f636f..9ac08638 100644 --- a/app/services/stripe_webhook_handler.py +++ b/app/handlers/stripe_webhook.py @@ -1,5 +1,4 @@ -# app/services/stripe_webhook_handler.py -# noqa: NAM-002 - This is a webhook handler, not a service +# app/handlers/stripe_webhook.py """ Stripe webhook event handler. @@ -84,14 +83,14 @@ class StripeWebhookHandler: logger.debug(f"No handler for event type {event_type}") existing.status = "processed" existing.processed_at = datetime.now(timezone.utc) - db.commit() # noqa: SVC-006 - Webhook handler controls its own transaction + db.commit() return {"status": "ignored", "reason": f"no handler for {event_type}"} try: result = handler(db, event) existing.status = "processed" existing.processed_at = datetime.now(timezone.utc) - db.commit() # noqa: SVC-006 - Webhook handler controls its own transaction + db.commit() logger.info(f"Successfully processed event {event_id} ({event_type})") return {"status": "processed", "result": result} @@ -99,7 +98,7 @@ class StripeWebhookHandler: logger.error(f"Error processing event {event_id}: {e}") existing.status = "failed" existing.error_message = str(e) - db.commit() # noqa: SVC-006 - Webhook handler controls its own transaction + db.commit() raise # ========================================================================= diff --git a/docs/features/subscription-billing.md b/docs/features/subscription-billing.md index 0e9ebb23..3f14e65e 100644 --- a/docs/features/subscription-billing.md +++ b/docs/features/subscription-billing.md @@ -34,7 +34,12 @@ All subscription models are defined in `models/database/subscription.py`: | `BillingService` | `app/services/billing_service.py` | Subscription operations, checkout, portal | | `SubscriptionService` | `app/services/subscription_service.py` | Limit checks, usage tracking | | `StripeService` | `app/services/stripe_service.py` | Core Stripe API operations | -| `StripeWebhookHandler` | `app/services/stripe_webhook_handler.py` | Webhook event processing | + +### Handlers + +| Handler | Location | Purpose | +|---------|----------|---------| +| `StripeWebhookHandler` | `app/handlers/stripe_webhook.py` | Webhook event processing | ### API Endpoints diff --git a/tests/unit/services/test_stripe_webhook_handler.py b/tests/unit/services/test_stripe_webhook_handler.py index a275817b..a439b6c6 100644 --- a/tests/unit/services/test_stripe_webhook_handler.py +++ b/tests/unit/services/test_stripe_webhook_handler.py @@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch import pytest -from app.services.stripe_webhook_handler import StripeWebhookHandler +from app.handlers.stripe_webhook import StripeWebhookHandler from models.database.subscription import ( BillingHistory, StripeWebhookEvent, @@ -59,7 +59,7 @@ class TestStripeWebhookHandlerCheckout: """Initialize handler instance before each test.""" self.handler = StripeWebhookHandler() - @patch("app.services.stripe_webhook_handler.stripe.Subscription.retrieve") + @patch("app.handlers.stripe_webhook.stripe.Subscription.retrieve") def test_handle_checkout_completed_success( self, mock_stripe_retrieve, db, test_vendor, test_subscription, mock_checkout_event ):