fix(lint): auto-fix ruff violations and tune lint rules
- Auto-fixed 4,496 lint issues (import sorting, modern syntax, etc.) - Added ignore rules for patterns intentional in this codebase: E402 (late imports), E712 (SQLAlchemy filters), B904 (raise from), SIM108/SIM105/SIM117 (readability preferences) - Added per-file ignores for tests and scripts - Excluded broken scripts/rename_terminology.py (has curly quotes) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,8 +17,6 @@ from sqlalchemy.orm import Session
|
||||
from app.api.deps import get_current_admin_api, require_module_access
|
||||
from app.core.database import get_db
|
||||
from app.exceptions import ResourceNotFoundException
|
||||
from app.modules.billing.services import admin_subscription_service, subscription_service
|
||||
from app.modules.enums import FrontendType
|
||||
from app.modules.billing.schemas import (
|
||||
BillingHistoryListResponse,
|
||||
BillingHistoryWithMerchant,
|
||||
@@ -33,6 +31,11 @@ from app.modules.billing.schemas import (
|
||||
SubscriptionTierResponse,
|
||||
SubscriptionTierUpdate,
|
||||
)
|
||||
from app.modules.billing.services import (
|
||||
admin_subscription_service,
|
||||
subscription_service,
|
||||
)
|
||||
from app.modules.enums import FrontendType
|
||||
from models.schema.auth import UserContext
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -17,16 +17,19 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_admin_api, require_module_access
|
||||
from app.core.database import get_db
|
||||
from app.modules.billing.services.feature_aggregator import feature_aggregator
|
||||
from app.modules.billing.models.tier_feature_limit import TierFeatureLimit, MerchantFeatureOverride
|
||||
from app.modules.billing.models import SubscriptionTier
|
||||
from app.modules.billing.models.tier_feature_limit import (
|
||||
MerchantFeatureOverride,
|
||||
TierFeatureLimit,
|
||||
)
|
||||
from app.modules.billing.schemas import (
|
||||
FeatureDeclarationResponse,
|
||||
FeatureCatalogResponse,
|
||||
TierFeatureLimitEntry,
|
||||
FeatureDeclarationResponse,
|
||||
MerchantFeatureOverrideEntry,
|
||||
MerchantFeatureOverrideResponse,
|
||||
TierFeatureLimitEntry,
|
||||
)
|
||||
from app.modules.billing.services.feature_aggregator import feature_aggregator
|
||||
from app.modules.enums import FrontendType
|
||||
from models.schema.auth import UserContext
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ Provides subscription management and billing operations for merchant owners:
|
||||
- Stripe checkout session creation
|
||||
- Invoice history
|
||||
|
||||
Authentication: merchant_token cookie or Authorization header.
|
||||
Authentication: Authorization header (API-only, no cookies for CSRF safety).
|
||||
The user must own at least one active merchant (validated by
|
||||
get_current_merchant_from_cookie_or_header).
|
||||
get_merchant_for_current_user).
|
||||
|
||||
Auto-discovered by the route system (merchant.py in routes/api/ triggers
|
||||
registration under /api/v1/merchants/billing/*).
|
||||
@@ -18,22 +18,26 @@ registration under /api/v1/merchants/billing/*).
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request
|
||||
from pydantic import BaseModel
|
||||
from fastapi import APIRouter, Depends, Path, Query, Request
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_merchant_from_cookie_or_header
|
||||
from app.api.deps import get_merchant_for_current_user
|
||||
from app.core.database import get_db
|
||||
from app.modules.billing.schemas import (
|
||||
ChangeTierRequest,
|
||||
ChangeTierResponse,
|
||||
CheckoutRequest,
|
||||
CheckoutResponse,
|
||||
MerchantPortalAvailableTiersResponse,
|
||||
MerchantPortalInvoiceListResponse,
|
||||
MerchantPortalSubscriptionDetailResponse,
|
||||
MerchantPortalSubscriptionItem,
|
||||
MerchantPortalSubscriptionListResponse,
|
||||
MerchantSubscriptionResponse,
|
||||
TierInfo,
|
||||
)
|
||||
from app.modules.billing.services.billing_service import billing_service
|
||||
from app.modules.billing.services.subscription_service import subscription_service
|
||||
from app.modules.tenancy.models import Merchant
|
||||
from models.schema.auth import UserContext
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -44,49 +48,15 @@ ROUTE_CONFIG = {
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Helpers
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def _get_user_merchant(db: Session, user_context: UserContext) -> Merchant:
|
||||
"""
|
||||
Get the first active merchant owned by the current user.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
user_context: Authenticated user context
|
||||
|
||||
Returns:
|
||||
Merchant: The user's active merchant
|
||||
|
||||
Raises:
|
||||
HTTPException 404: If the user has no active merchants
|
||||
"""
|
||||
merchant = (
|
||||
db.query(Merchant)
|
||||
.filter(
|
||||
Merchant.owner_user_id == user_context.id,
|
||||
Merchant.is_active == True, # noqa: E712
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not merchant:
|
||||
raise HTTPException(status_code=404, detail="No active merchant found")
|
||||
|
||||
return merchant
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Subscription Endpoints
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@router.get("/subscriptions")
|
||||
@router.get("/subscriptions", response_model=MerchantPortalSubscriptionListResponse)
|
||||
def list_merchant_subscriptions(
|
||||
request: Request,
|
||||
current_user: UserContext = Depends(get_current_merchant_from_cookie_or_header),
|
||||
merchant=Depends(get_merchant_for_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
@@ -95,7 +65,6 @@ def list_merchant_subscriptions(
|
||||
Returns subscriptions across all platforms the merchant is subscribed to,
|
||||
including tier information and status.
|
||||
"""
|
||||
merchant = _get_user_merchant(db, current_user)
|
||||
subscriptions = subscription_service.get_merchant_subscriptions(db, merchant.id)
|
||||
|
||||
items = []
|
||||
@@ -104,16 +73,21 @@ def list_merchant_subscriptions(
|
||||
data["tier"] = sub.tier.code if sub.tier else None
|
||||
data["tier_name"] = sub.tier.name if sub.tier else None
|
||||
data["platform_name"] = sub.platform.name if sub.platform else ""
|
||||
items.append(data)
|
||||
items.append(MerchantPortalSubscriptionItem(**data))
|
||||
|
||||
return {"subscriptions": items, "total": len(items)}
|
||||
return MerchantPortalSubscriptionListResponse(
|
||||
subscriptions=items, total=len(items)
|
||||
)
|
||||
|
||||
|
||||
@router.get("/subscriptions/{platform_id}")
|
||||
@router.get(
|
||||
"/subscriptions/{platform_id}",
|
||||
response_model=MerchantPortalSubscriptionDetailResponse,
|
||||
)
|
||||
def get_merchant_subscription(
|
||||
request: Request,
|
||||
platform_id: int = Path(..., description="Platform ID"),
|
||||
current_user: UserContext = Depends(get_current_merchant_from_cookie_or_header),
|
||||
merchant=Depends(get_merchant_for_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
@@ -121,21 +95,25 @@ def get_merchant_subscription(
|
||||
|
||||
Returns the subscription with tier information for the given platform.
|
||||
"""
|
||||
merchant = _get_user_merchant(db, current_user)
|
||||
subscription = subscription_service.get_merchant_subscription(
|
||||
db, merchant.id, platform_id
|
||||
)
|
||||
|
||||
if not subscription:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"No subscription found for platform {platform_id}",
|
||||
from app.exceptions.base import ResourceNotFoundException
|
||||
|
||||
raise ResourceNotFoundException(
|
||||
resource_type="Subscription",
|
||||
identifier=f"merchant={merchant.id}, platform={platform_id}",
|
||||
error_code="SUBSCRIPTION_NOT_FOUND",
|
||||
)
|
||||
|
||||
sub_data = MerchantSubscriptionResponse.model_validate(subscription).model_dump()
|
||||
sub_data["tier"] = subscription.tier.code if subscription.tier else None
|
||||
sub_data["tier_name"] = subscription.tier.name if subscription.tier else None
|
||||
sub_data["platform_name"] = subscription.platform.name if subscription.platform else ""
|
||||
sub_data["platform_name"] = (
|
||||
subscription.platform.name if subscription.platform else ""
|
||||
)
|
||||
|
||||
tier_info = None
|
||||
if subscription.tier:
|
||||
@@ -146,20 +124,25 @@ def get_merchant_subscription(
|
||||
description=tier.description,
|
||||
price_monthly_cents=tier.price_monthly_cents,
|
||||
price_annual_cents=tier.price_annual_cents,
|
||||
feature_codes=tier.get_feature_codes() if hasattr(tier, "get_feature_codes") else [],
|
||||
feature_codes=(
|
||||
tier.get_feature_codes() if hasattr(tier, "get_feature_codes") else []
|
||||
),
|
||||
)
|
||||
|
||||
return {
|
||||
"subscription": sub_data,
|
||||
"tier": tier_info,
|
||||
}
|
||||
return MerchantPortalSubscriptionDetailResponse(
|
||||
subscription=MerchantPortalSubscriptionItem(**sub_data),
|
||||
tier=tier_info,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/subscriptions/{platform_id}/tiers")
|
||||
@router.get(
|
||||
"/subscriptions/{platform_id}/tiers",
|
||||
response_model=MerchantPortalAvailableTiersResponse,
|
||||
)
|
||||
def get_available_tiers(
|
||||
request: Request,
|
||||
platform_id: int = Path(..., description="Platform ID"),
|
||||
current_user: UserContext = Depends(get_current_merchant_from_cookie_or_header),
|
||||
merchant=Depends(get_merchant_for_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
@@ -168,7 +151,6 @@ def get_available_tiers(
|
||||
Returns all public tiers with upgrade/downgrade flags relative to
|
||||
the merchant's current tier.
|
||||
"""
|
||||
merchant = _get_user_merchant(db, current_user)
|
||||
subscription = subscription_service.get_merchant_subscription(
|
||||
db, merchant.id, platform_id
|
||||
)
|
||||
@@ -182,25 +164,21 @@ def get_available_tiers(
|
||||
if subscription and subscription.tier:
|
||||
current_tier_code = subscription.tier.code
|
||||
|
||||
return {
|
||||
"tiers": tier_list,
|
||||
"current_tier": current_tier_code,
|
||||
}
|
||||
return MerchantPortalAvailableTiersResponse(
|
||||
tiers=tier_list,
|
||||
current_tier=current_tier_code,
|
||||
)
|
||||
|
||||
|
||||
class ChangeTierRequest(BaseModel):
|
||||
"""Request for changing subscription tier."""
|
||||
|
||||
tier_code: str
|
||||
is_annual: bool = False
|
||||
|
||||
|
||||
@router.post("/subscriptions/{platform_id}/change-tier")
|
||||
@router.post(
|
||||
"/subscriptions/{platform_id}/change-tier",
|
||||
response_model=ChangeTierResponse,
|
||||
)
|
||||
def change_subscription_tier(
|
||||
request: Request,
|
||||
tier_data: ChangeTierRequest,
|
||||
platform_id: int = Path(..., description="Platform ID"),
|
||||
current_user: UserContext = Depends(get_current_merchant_from_cookie_or_header),
|
||||
merchant=Depends(get_merchant_for_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
@@ -208,7 +186,6 @@ def change_subscription_tier(
|
||||
|
||||
Handles both Stripe-connected and non-Stripe subscriptions.
|
||||
"""
|
||||
merchant = _get_user_merchant(db, current_user)
|
||||
result = billing_service.change_tier(
|
||||
db, merchant.id, platform_id, tier_data.tier_code, tier_data.is_annual
|
||||
)
|
||||
@@ -230,7 +207,7 @@ def create_checkout_session(
|
||||
request: Request,
|
||||
checkout_data: CheckoutRequest,
|
||||
platform_id: int = Path(..., description="Platform ID"),
|
||||
current_user: UserContext = Depends(get_current_merchant_from_cookie_or_header),
|
||||
merchant=Depends(get_merchant_for_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
@@ -239,12 +216,14 @@ def create_checkout_session(
|
||||
Starts a new subscription or upgrades an existing one to the
|
||||
requested tier.
|
||||
"""
|
||||
merchant = _get_user_merchant(db, current_user)
|
||||
|
||||
# Build success/cancel URLs from request
|
||||
base_url = str(request.base_url).rstrip("/")
|
||||
success_url = f"{base_url}/merchants/billing/subscriptions/{platform_id}?checkout=success"
|
||||
cancel_url = f"{base_url}/merchants/billing/subscriptions/{platform_id}?checkout=cancelled"
|
||||
success_url = (
|
||||
f"{base_url}/merchants/billing/subscriptions/{platform_id}?checkout=success"
|
||||
)
|
||||
cancel_url = (
|
||||
f"{base_url}/merchants/billing/subscriptions/{platform_id}?checkout=cancelled"
|
||||
)
|
||||
|
||||
result = billing_service.create_checkout_session(
|
||||
db=db,
|
||||
@@ -274,12 +253,12 @@ def create_checkout_session(
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@router.get("/invoices")
|
||||
@router.get("/invoices", response_model=MerchantPortalInvoiceListResponse)
|
||||
def get_invoices(
|
||||
request: Request,
|
||||
skip: int = Query(0, ge=0, description="Number of records to skip"),
|
||||
limit: int = Query(20, ge=1, le=100, description="Max records to return"),
|
||||
current_user: UserContext = Depends(get_current_merchant_from_cookie_or_header),
|
||||
merchant=Depends(get_merchant_for_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
@@ -287,14 +266,12 @@ def get_invoices(
|
||||
|
||||
Returns paginated billing history entries ordered by date descending.
|
||||
"""
|
||||
merchant = _get_user_merchant(db, current_user)
|
||||
|
||||
invoices, total = billing_service.get_invoices(
|
||||
db, merchant.id, skip=skip, limit=limit
|
||||
)
|
||||
|
||||
return {
|
||||
"invoices": [
|
||||
return MerchantPortalInvoiceListResponse(
|
||||
invoices=[
|
||||
{
|
||||
"id": inv.id,
|
||||
"invoice_number": inv.invoice_number,
|
||||
@@ -309,11 +286,13 @@ def get_invoices(
|
||||
"pdf_url": inv.invoice_pdf_url,
|
||||
"hosted_url": inv.hosted_invoice_url,
|
||||
"description": inv.description,
|
||||
"created_at": inv.created_at.isoformat() if inv.created_at else None,
|
||||
"created_at": (
|
||||
inv.created_at.isoformat() if inv.created_at else None
|
||||
),
|
||||
}
|
||||
for inv in invoices
|
||||
],
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
}
|
||||
total=total,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
@@ -14,8 +14,10 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.exceptions import ResourceNotFoundException
|
||||
from app.modules.billing.services.platform_pricing_service import platform_pricing_service
|
||||
from app.modules.billing.models import TierCode, SubscriptionTier
|
||||
from app.modules.billing.models import SubscriptionTier, TierCode
|
||||
from app.modules.billing.services.platform_pricing_service import (
|
||||
platform_pricing_service,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/pricing")
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_store_api, require_module_access
|
||||
from app.core.config import settings
|
||||
from app.core.database import get_db
|
||||
from app.modules.billing.services import billing_service, subscription_service
|
||||
from app.modules.enums import FrontendType
|
||||
@@ -218,9 +217,9 @@ def get_invoices(
|
||||
# ============================================================================
|
||||
# Include all billing-related store sub-routers
|
||||
|
||||
from app.modules.billing.routes.api.store_features import store_features_router
|
||||
from app.modules.billing.routes.api.store_checkout import store_checkout_router
|
||||
from app.modules.billing.routes.api.store_addons import store_addons_router
|
||||
from app.modules.billing.routes.api.store_checkout import store_checkout_router
|
||||
from app.modules.billing.routes.api.store_features import store_features_router
|
||||
from app.modules.billing.routes.api.store_usage import store_usage_router
|
||||
|
||||
store_router.include_router(store_features_router, tags=["store-features"])
|
||||
|
||||
@@ -22,7 +22,7 @@ from sqlalchemy.orm import Session
|
||||
from app.api.deps import get_current_store_api, require_module_access
|
||||
from app.core.config import settings
|
||||
from app.core.database import get_db
|
||||
from app.modules.billing.services import billing_service, subscription_service
|
||||
from app.modules.billing.services import billing_service
|
||||
from app.modules.enums import FrontendType
|
||||
from models.schema.auth import UserContext
|
||||
|
||||
|
||||
Reference in New Issue
Block a user