feat(billing): migrate frontend templates to feature provider system
Replace hardcoded subscription fields (orders_limit, products_limit,
team_members_limit) across 5 frontend pages with dynamic feature
provider APIs. Add admin convenience endpoint for store subscription
lookup. Remove legacy stubs (StoreSubscription, FeatureCode, Feature,
TIER_LIMITS, FeatureInfo, FeatureUpgradeInfo) and schema aliases.
Pages updated:
- Admin subscriptions: dynamic feature overrides editor
- Admin tiers: correct feature catalog/limits API URLs
- Store billing: usage metrics from /store/billing/usage
- Merchant subscription detail: tier.feature_limits rendering
- Admin store detail: new GET /admin/subscriptions/store/{id} endpoint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,48 +6,47 @@ This is the canonical location for billing schemas.
|
||||
|
||||
Usage:
|
||||
from app.modules.billing.schemas import (
|
||||
SubscriptionCreate,
|
||||
SubscriptionResponse,
|
||||
MerchantSubscriptionCreate,
|
||||
MerchantSubscriptionResponse,
|
||||
TierInfo,
|
||||
)
|
||||
"""
|
||||
|
||||
from app.modules.billing.schemas.subscription import (
|
||||
# Tier schemas
|
||||
TierFeatures,
|
||||
TierLimits,
|
||||
TierFeatureLimitResponse,
|
||||
TierInfo,
|
||||
# Subscription CRUD schemas
|
||||
SubscriptionCreate,
|
||||
SubscriptionUpdate,
|
||||
SubscriptionResponse,
|
||||
# Usage schemas
|
||||
SubscriptionUsage,
|
||||
UsageSummary,
|
||||
SubscriptionStatusResponse,
|
||||
# Subscription schemas
|
||||
MerchantSubscriptionCreate,
|
||||
MerchantSubscriptionUpdate,
|
||||
MerchantSubscriptionResponse,
|
||||
MerchantSubscriptionStatusResponse,
|
||||
# Feature summary schemas
|
||||
FeatureSummaryResponse,
|
||||
# Limit check schemas
|
||||
LimitCheckResult,
|
||||
CanCreateOrderResponse,
|
||||
CanAddProductResponse,
|
||||
CanAddTeamMemberResponse,
|
||||
FeatureCheckResponse,
|
||||
)
|
||||
from app.modules.billing.schemas.billing import (
|
||||
# Subscription Tier Admin schemas
|
||||
TierFeatureLimitEntry,
|
||||
SubscriptionTierBase,
|
||||
SubscriptionTierCreate,
|
||||
SubscriptionTierUpdate,
|
||||
SubscriptionTierResponse,
|
||||
SubscriptionTierListResponse,
|
||||
# Vendor Subscription schemas
|
||||
VendorSubscriptionResponse,
|
||||
VendorSubscriptionWithVendor,
|
||||
VendorSubscriptionListResponse,
|
||||
VendorSubscriptionCreate,
|
||||
VendorSubscriptionUpdate,
|
||||
# Merchant Subscription Admin schemas
|
||||
MerchantSubscriptionAdminResponse,
|
||||
MerchantSubscriptionWithMerchant,
|
||||
MerchantSubscriptionListResponse,
|
||||
MerchantSubscriptionAdminCreate,
|
||||
MerchantSubscriptionAdminUpdate,
|
||||
# Merchant Feature Override schemas
|
||||
MerchantFeatureOverrideEntry,
|
||||
MerchantFeatureOverrideResponse,
|
||||
# Billing History schemas
|
||||
BillingHistoryResponse,
|
||||
BillingHistoryWithVendor,
|
||||
BillingHistoryWithMerchant,
|
||||
BillingHistoryListResponse,
|
||||
# Checkout & Portal schemas
|
||||
CheckoutRequest,
|
||||
@@ -55,42 +54,44 @@ from app.modules.billing.schemas.billing import (
|
||||
PortalSessionResponse,
|
||||
# Stats schemas
|
||||
SubscriptionStatsResponse,
|
||||
# Feature Catalog schemas
|
||||
FeatureDeclarationResponse,
|
||||
FeatureCatalogResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Tier schemas (subscription.py)
|
||||
"TierFeatures",
|
||||
"TierLimits",
|
||||
"TierFeatureLimitResponse",
|
||||
"TierInfo",
|
||||
# Subscription CRUD schemas (subscription.py)
|
||||
"SubscriptionCreate",
|
||||
"SubscriptionUpdate",
|
||||
"SubscriptionResponse",
|
||||
# Usage schemas (subscription.py)
|
||||
"SubscriptionUsage",
|
||||
"UsageSummary",
|
||||
"SubscriptionStatusResponse",
|
||||
# Subscription schemas (subscription.py)
|
||||
"MerchantSubscriptionCreate",
|
||||
"MerchantSubscriptionUpdate",
|
||||
"MerchantSubscriptionResponse",
|
||||
"MerchantSubscriptionStatusResponse",
|
||||
# Feature summary schemas (subscription.py)
|
||||
"FeatureSummaryResponse",
|
||||
# Limit check schemas (subscription.py)
|
||||
"LimitCheckResult",
|
||||
"CanCreateOrderResponse",
|
||||
"CanAddProductResponse",
|
||||
"CanAddTeamMemberResponse",
|
||||
"FeatureCheckResponse",
|
||||
# Subscription Tier Admin schemas (billing.py)
|
||||
"TierFeatureLimitEntry",
|
||||
"SubscriptionTierBase",
|
||||
"SubscriptionTierCreate",
|
||||
"SubscriptionTierUpdate",
|
||||
"SubscriptionTierResponse",
|
||||
"SubscriptionTierListResponse",
|
||||
# Vendor Subscription schemas (billing.py)
|
||||
"VendorSubscriptionResponse",
|
||||
"VendorSubscriptionWithVendor",
|
||||
"VendorSubscriptionListResponse",
|
||||
"VendorSubscriptionCreate",
|
||||
"VendorSubscriptionUpdate",
|
||||
# Merchant Subscription Admin schemas (billing.py)
|
||||
"MerchantSubscriptionAdminResponse",
|
||||
"MerchantSubscriptionWithMerchant",
|
||||
"MerchantSubscriptionListResponse",
|
||||
"MerchantSubscriptionAdminCreate",
|
||||
"MerchantSubscriptionAdminUpdate",
|
||||
# Merchant Feature Override schemas (billing.py)
|
||||
"MerchantFeatureOverrideEntry",
|
||||
"MerchantFeatureOverrideResponse",
|
||||
# Billing History schemas (billing.py)
|
||||
"BillingHistoryResponse",
|
||||
"BillingHistoryWithVendor",
|
||||
"BillingHistoryWithMerchant",
|
||||
"BillingHistoryListResponse",
|
||||
# Checkout & Portal schemas (billing.py)
|
||||
"CheckoutRequest",
|
||||
@@ -98,4 +99,7 @@ __all__ = [
|
||||
"PortalSessionResponse",
|
||||
# Stats schemas (billing.py)
|
||||
"SubscriptionStatsResponse",
|
||||
# Feature Catalog schemas (billing.py)
|
||||
"FeatureDeclarationResponse",
|
||||
"FeatureCatalogResponse",
|
||||
]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# app/modules/billing/schemas/subscription.py
|
||||
"""
|
||||
Pydantic schemas for subscription operations.
|
||||
Pydantic schemas for merchant-level subscription operations.
|
||||
|
||||
Supports subscription management and tier limit checks.
|
||||
Supports subscription management, tier information, and feature summaries.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
@@ -15,48 +15,23 @@ from pydantic import BaseModel, ConfigDict, Field
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TierFeatures(BaseModel):
|
||||
"""Features included in a tier."""
|
||||
class TierFeatureLimitResponse(BaseModel):
|
||||
"""Feature limit entry for a tier."""
|
||||
|
||||
letzshop_sync: bool = True
|
||||
inventory_basic: bool = True
|
||||
inventory_locations: bool = False
|
||||
inventory_purchase_orders: bool = False
|
||||
invoice_lu: bool = True
|
||||
invoice_eu_vat: bool = False
|
||||
invoice_bulk: bool = False
|
||||
customer_view: bool = True
|
||||
customer_export: bool = False
|
||||
analytics_dashboard: bool = False
|
||||
accounting_export: bool = False
|
||||
api_access: bool = False
|
||||
automation_rules: bool = False
|
||||
team_roles: bool = False
|
||||
white_label: bool = False
|
||||
multi_vendor: bool = False
|
||||
custom_integrations: bool = False
|
||||
sla_guarantee: bool = False
|
||||
dedicated_support: bool = False
|
||||
|
||||
|
||||
class TierLimits(BaseModel):
|
||||
"""Limits for a subscription tier."""
|
||||
|
||||
orders_per_month: int | None = Field(None, description="None = unlimited")
|
||||
products_limit: int | None = Field(None, description="None = unlimited")
|
||||
team_members: int | None = Field(None, description="None = unlimited")
|
||||
order_history_months: int | None = Field(None, description="None = unlimited")
|
||||
feature_code: str
|
||||
limit_value: int | None = Field(None, description="None = unlimited")
|
||||
|
||||
|
||||
class TierInfo(BaseModel):
|
||||
"""Full tier information."""
|
||||
"""Full tier information with feature limits."""
|
||||
|
||||
code: str
|
||||
name: str
|
||||
description: str | None = None
|
||||
price_monthly_cents: int
|
||||
price_annual_cents: int | None
|
||||
limits: TierLimits
|
||||
features: list[str]
|
||||
feature_codes: list[str] = Field(default_factory=list)
|
||||
feature_limits: list[TierFeatureLimitResponse] = Field(default_factory=list)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -64,47 +39,43 @@ class TierInfo(BaseModel):
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class SubscriptionCreate(BaseModel):
|
||||
"""Schema for creating a subscription (admin/internal use)."""
|
||||
class MerchantSubscriptionCreate(BaseModel):
|
||||
"""Schema for creating a merchant subscription."""
|
||||
|
||||
tier: str = Field(default="essential", pattern="^(essential|professional|business|enterprise)$")
|
||||
tier_code: str = Field(default="essential")
|
||||
is_annual: bool = False
|
||||
trial_days: int = Field(default=14, ge=0, le=30)
|
||||
|
||||
|
||||
class SubscriptionUpdate(BaseModel):
|
||||
"""Schema for updating a subscription."""
|
||||
class MerchantSubscriptionUpdate(BaseModel):
|
||||
"""Schema for updating a merchant subscription."""
|
||||
|
||||
tier: str | None = Field(None, pattern="^(essential|professional|business|enterprise)$")
|
||||
tier_code: str | None = None
|
||||
status: str | None = Field(None, pattern="^(trial|active|past_due|cancelled|expired)$")
|
||||
is_annual: bool | None = None
|
||||
custom_orders_limit: int | None = None
|
||||
custom_products_limit: int | None = None
|
||||
custom_team_limit: int | None = None
|
||||
|
||||
|
||||
class SubscriptionResponse(BaseModel):
|
||||
"""Schema for subscription response."""
|
||||
class MerchantSubscriptionResponse(BaseModel):
|
||||
"""Schema for merchant subscription response."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
vendor_id: int
|
||||
tier: str
|
||||
status: str
|
||||
merchant_id: int
|
||||
platform_id: int
|
||||
tier_id: int | None
|
||||
|
||||
status: str
|
||||
is_annual: bool
|
||||
period_start: datetime
|
||||
period_end: datetime
|
||||
is_annual: bool
|
||||
|
||||
trial_ends_at: datetime | None
|
||||
orders_this_period: int
|
||||
orders_limit_reached_at: datetime | None
|
||||
|
||||
# Effective limits (with custom overrides applied)
|
||||
orders_limit: int | None
|
||||
products_limit: int | None
|
||||
team_members_limit: int | None
|
||||
# Stripe info (optional, may be hidden from client)
|
||||
stripe_customer_id: str | None = None
|
||||
|
||||
# Cancellation
|
||||
cancelled_at: datetime | None = None
|
||||
|
||||
# Computed properties
|
||||
is_active: bool
|
||||
@@ -115,47 +86,36 @@ class SubscriptionResponse(BaseModel):
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class SubscriptionUsage(BaseModel):
|
||||
"""Current subscription usage statistics."""
|
||||
|
||||
orders_used: int
|
||||
orders_limit: int | None
|
||||
orders_remaining: int | None
|
||||
orders_percent_used: float | None
|
||||
|
||||
products_used: int
|
||||
products_limit: int | None
|
||||
products_remaining: int | None
|
||||
products_percent_used: float | None
|
||||
|
||||
team_members_used: int
|
||||
team_members_limit: int | None
|
||||
team_members_remaining: int | None
|
||||
team_members_percent_used: float | None
|
||||
# ============================================================================
|
||||
# Feature Summary Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class UsageSummary(BaseModel):
|
||||
"""Usage summary for billing page display."""
|
||||
class FeatureSummaryResponse(BaseModel):
|
||||
"""Feature summary for merchant portal display."""
|
||||
|
||||
orders_this_period: int
|
||||
orders_limit: int | None
|
||||
orders_remaining: int | None
|
||||
|
||||
products_count: int
|
||||
products_limit: int | None
|
||||
products_remaining: int | None
|
||||
|
||||
team_count: int
|
||||
team_limit: int | None
|
||||
team_remaining: int | None
|
||||
code: str
|
||||
name_key: str
|
||||
description_key: str
|
||||
category: str
|
||||
feature_type: str
|
||||
scope: str
|
||||
enabled: bool
|
||||
limit: int | None = None
|
||||
current: int | None = None
|
||||
remaining: int | None = None
|
||||
percent_used: float | None = None
|
||||
is_override: bool = False
|
||||
unit_key: str | None = None
|
||||
ui_icon: str | None = None
|
||||
|
||||
|
||||
class SubscriptionStatusResponse(BaseModel):
|
||||
"""Subscription status with usage and limits."""
|
||||
class MerchantSubscriptionStatusResponse(BaseModel):
|
||||
"""Full subscription status with tier info and feature summary."""
|
||||
|
||||
subscription: SubscriptionResponse
|
||||
usage: SubscriptionUsage
|
||||
tier_info: TierInfo
|
||||
subscription: MerchantSubscriptionResponse
|
||||
tier: TierInfo | None = None
|
||||
features: list[FeatureSummaryResponse] = Field(default_factory=list)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -173,37 +133,11 @@ class LimitCheckResult(BaseModel):
|
||||
message: str | None = None
|
||||
|
||||
|
||||
class CanCreateOrderResponse(BaseModel):
|
||||
"""Response for order creation check."""
|
||||
|
||||
allowed: bool
|
||||
orders_this_period: int
|
||||
orders_limit: int | None
|
||||
message: str | None = None
|
||||
|
||||
|
||||
class CanAddProductResponse(BaseModel):
|
||||
"""Response for product addition check."""
|
||||
|
||||
allowed: bool
|
||||
products_count: int
|
||||
products_limit: int | None
|
||||
message: str | None = None
|
||||
|
||||
|
||||
class CanAddTeamMemberResponse(BaseModel):
|
||||
"""Response for team member addition check."""
|
||||
|
||||
allowed: bool
|
||||
team_count: int
|
||||
team_limit: int | None
|
||||
message: str | None = None
|
||||
|
||||
|
||||
class FeatureCheckResponse(BaseModel):
|
||||
"""Response for feature check."""
|
||||
|
||||
feature: str
|
||||
feature_code: str
|
||||
enabled: bool
|
||||
tier_required: str | None = None
|
||||
message: str | None = None
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user