refactor: move stripe webhook handler to app/handlers/
- Create app/handlers/ directory for event handlers - Move stripe_webhook_handler.py to app/handlers/stripe_webhook.py - Update imports in webhooks.py, tests, and docs - Handlers are distinct from services (event-driven vs request-driven) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@ from sqlalchemy.orm import Session
|
|||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.exceptions import InvalidWebhookSignatureException, WebhookMissingSignatureException
|
from app.exceptions import InvalidWebhookSignatureException, WebhookMissingSignatureException
|
||||||
from app.services.stripe_service import stripe_service
|
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")
|
router = APIRouter(prefix="/webhooks")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
10
app/handlers/__init__.py
Normal file
10
app/handlers/__init__.py
Normal file
@@ -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"]
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
# app/services/stripe_webhook_handler.py
|
# app/handlers/stripe_webhook.py
|
||||||
# noqa: NAM-002 - This is a webhook handler, not a service
|
|
||||||
"""
|
"""
|
||||||
Stripe webhook event handler.
|
Stripe webhook event handler.
|
||||||
|
|
||||||
@@ -84,14 +83,14 @@ class StripeWebhookHandler:
|
|||||||
logger.debug(f"No handler for event type {event_type}")
|
logger.debug(f"No handler for event type {event_type}")
|
||||||
existing.status = "processed"
|
existing.status = "processed"
|
||||||
existing.processed_at = datetime.now(timezone.utc)
|
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}"}
|
return {"status": "ignored", "reason": f"no handler for {event_type}"}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = handler(db, event)
|
result = handler(db, event)
|
||||||
existing.status = "processed"
|
existing.status = "processed"
|
||||||
existing.processed_at = datetime.now(timezone.utc)
|
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})")
|
logger.info(f"Successfully processed event {event_id} ({event_type})")
|
||||||
return {"status": "processed", "result": result}
|
return {"status": "processed", "result": result}
|
||||||
|
|
||||||
@@ -99,7 +98,7 @@ class StripeWebhookHandler:
|
|||||||
logger.error(f"Error processing event {event_id}: {e}")
|
logger.error(f"Error processing event {event_id}: {e}")
|
||||||
existing.status = "failed"
|
existing.status = "failed"
|
||||||
existing.error_message = str(e)
|
existing.error_message = str(e)
|
||||||
db.commit() # noqa: SVC-006 - Webhook handler controls its own transaction
|
db.commit()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
@@ -34,7 +34,12 @@ All subscription models are defined in `models/database/subscription.py`:
|
|||||||
| `BillingService` | `app/services/billing_service.py` | Subscription operations, checkout, portal |
|
| `BillingService` | `app/services/billing_service.py` | Subscription operations, checkout, portal |
|
||||||
| `SubscriptionService` | `app/services/subscription_service.py` | Limit checks, usage tracking |
|
| `SubscriptionService` | `app/services/subscription_service.py` | Limit checks, usage tracking |
|
||||||
| `StripeService` | `app/services/stripe_service.py` | Core Stripe API operations |
|
| `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
|
### API Endpoints
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from app.services.stripe_webhook_handler import StripeWebhookHandler
|
from app.handlers.stripe_webhook import StripeWebhookHandler
|
||||||
from models.database.subscription import (
|
from models.database.subscription import (
|
||||||
BillingHistory,
|
BillingHistory,
|
||||||
StripeWebhookEvent,
|
StripeWebhookEvent,
|
||||||
@@ -59,7 +59,7 @@ class TestStripeWebhookHandlerCheckout:
|
|||||||
"""Initialize handler instance before each test."""
|
"""Initialize handler instance before each test."""
|
||||||
self.handler = StripeWebhookHandler()
|
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(
|
def test_handle_checkout_completed_success(
|
||||||
self, mock_stripe_retrieve, db, test_vendor, test_subscription, mock_checkout_event
|
self, mock_stripe_retrieve, db, test_vendor, test_subscription, mock_checkout_event
|
||||||
):
|
):
|
||||||
|
|||||||
Reference in New Issue
Block a user