fix(billing): complete billing module — fix tier change, platform support, merchant portal
- Fix admin tier change: resolve tier_code→tier_id in update_subscription(), delegate to billing_service.change_tier() for Stripe-connected subs - Add platform support to admin tiers page: platform column, filter dropdown, platform selector in create/edit modal, platform_name in tier API response - Filter used platforms in create subscription modal on merchant detail page - Enrich merchant portal API responses with tier code, tier_name, platform_name - Add eager-load of platform relationship in get_merchant_subscription() - Remove stale store_name/store_code references from merchant templates - Add merchant tier change endpoint (POST /change-tier) and tier selector UI replacing broken requestUpgrade() button - Fix subscription detail link to use platform_id instead of sub.id Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -59,8 +59,17 @@ def list_subscription_tiers(
|
||||
"""List all subscription tiers."""
|
||||
tiers = admin_subscription_service.get_tiers(db, include_inactive=include_inactive, platform_id=platform_id)
|
||||
|
||||
from app.modules.tenancy.models import Platform
|
||||
|
||||
platforms_map = {p.id: p.name for p in db.query(Platform).all()}
|
||||
tiers_response = []
|
||||
for t in tiers:
|
||||
resp = SubscriptionTierResponse.model_validate(t)
|
||||
resp.platform_name = platforms_map.get(t.platform_id) if t.platform_id else None
|
||||
tiers_response.append(resp)
|
||||
|
||||
return SubscriptionTierListResponse(
|
||||
tiers=[SubscriptionTierResponse.model_validate(t) for t in tiers],
|
||||
tiers=tiers_response,
|
||||
total=len(tiers),
|
||||
)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ registration under /api/v1/merchants/billing/*).
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_merchant_from_cookie_or_header
|
||||
@@ -97,13 +98,15 @@ def list_merchant_subscriptions(
|
||||
merchant = _get_user_merchant(db, current_user)
|
||||
subscriptions = subscription_service.get_merchant_subscriptions(db, merchant.id)
|
||||
|
||||
return {
|
||||
"subscriptions": [
|
||||
MerchantSubscriptionResponse.model_validate(sub)
|
||||
for sub in subscriptions
|
||||
],
|
||||
"total": len(subscriptions),
|
||||
}
|
||||
items = []
|
||||
for sub in subscriptions:
|
||||
data = MerchantSubscriptionResponse.model_validate(sub).model_dump()
|
||||
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)
|
||||
|
||||
return {"subscriptions": items, "total": len(items)}
|
||||
|
||||
|
||||
@router.get("/subscriptions/{platform_id}")
|
||||
@@ -129,6 +132,11 @@ def get_merchant_subscription(
|
||||
detail=f"No subscription found for platform {platform_id}",
|
||||
)
|
||||
|
||||
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 ""
|
||||
|
||||
tier_info = None
|
||||
if subscription.tier:
|
||||
tier = subscription.tier
|
||||
@@ -142,7 +150,7 @@ def get_merchant_subscription(
|
||||
)
|
||||
|
||||
return {
|
||||
"subscription": MerchantSubscriptionResponse.model_validate(subscription),
|
||||
"subscription": sub_data,
|
||||
"tier": tier_info,
|
||||
}
|
||||
|
||||
@@ -180,6 +188,40 @@ def get_available_tiers(
|
||||
}
|
||||
|
||||
|
||||
class ChangeTierRequest(BaseModel):
|
||||
"""Request for changing subscription tier."""
|
||||
|
||||
tier_code: str
|
||||
is_annual: bool = False
|
||||
|
||||
|
||||
@router.post("/subscriptions/{platform_id}/change-tier")
|
||||
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),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Change the subscription tier for a specific platform.
|
||||
|
||||
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
|
||||
)
|
||||
db.commit()
|
||||
|
||||
logger.info(
|
||||
f"Merchant {merchant.id} ({merchant.name}) changed tier to "
|
||||
f"{tier_data.tier_code} on platform={platform_id}"
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.post(
|
||||
"/subscriptions/{platform_id}/checkout",
|
||||
response_model=CheckoutResponse,
|
||||
|
||||
Reference in New Issue
Block a user