feat(billing): plan taxonomy reconciliation + Stripe sync + internal-tester allowlist (#164)
Co-authored-by: Michael Chihlas <michael@resolutionflow.com> Co-committed-by: Michael Chihlas <michael@resolutionflow.com>
This commit was merged in pull request #164.
This commit is contained in:
@@ -972,7 +972,7 @@ async def update_user_plan(
|
||||
current_user: Annotated[User, Depends(require_admin)],
|
||||
):
|
||||
"""Change a user's subscription plan (super admin only)."""
|
||||
if data.plan not in ("free", "pro", "team"):
|
||||
if data.plan not in ("free", "pro", "starter", "enterprise"):
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid plan")
|
||||
user, subscription = await _get_user_subscription(user_id, db)
|
||||
old_plan = subscription.plan
|
||||
@@ -991,7 +991,7 @@ async def update_account_plan(
|
||||
current_user: Annotated[User, Depends(require_admin)],
|
||||
):
|
||||
"""Change an account subscription plan (super admin only)."""
|
||||
if data.plan not in ("free", "pro", "team"):
|
||||
if data.plan not in ("free", "pro", "starter", "enterprise"):
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid plan")
|
||||
account, subscription = await _get_account_subscription(account_id, db)
|
||||
old_plan = subscription.plan
|
||||
|
||||
@@ -28,7 +28,7 @@ async def get_dashboard_metrics(
|
||||
) or 0
|
||||
paid_accounts = await db.scalar(
|
||||
select(func.count()).select_from(Subscription).where(
|
||||
Subscription.plan.in_(["pro", "team"])
|
||||
Subscription.plan.in_(["pro", "starter", "enterprise"])
|
||||
)
|
||||
) or 0
|
||||
total_trees = await db.scalar(
|
||||
|
||||
@@ -150,7 +150,7 @@ async def register(
|
||||
# and so paid/trial-bearing codes still apply when supplied.
|
||||
if (
|
||||
settings.REQUIRE_INVITE_CODE
|
||||
and not settings.SELF_SERVE_ENABLED
|
||||
and not settings.is_self_serve_active_for(user_data.email)
|
||||
and not user_data.invite_code
|
||||
):
|
||||
raise HTTPException(
|
||||
|
||||
@@ -11,22 +11,31 @@ frontend codegen and other call sites if needed.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter
|
||||
from typing import Annotated, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import get_current_user_optional
|
||||
from app.core.config import settings
|
||||
from app.models.user import User
|
||||
from app.schemas.config import PublicConfigResponse
|
||||
|
||||
router = APIRouter(prefix="/config", tags=["config"])
|
||||
|
||||
|
||||
@router.get("/public", response_model=PublicConfigResponse)
|
||||
async def get_public_config() -> PublicConfigResponse:
|
||||
async def get_public_config(
|
||||
current_user: Annotated[Optional[User], Depends(get_current_user_optional)],
|
||||
) -> PublicConfigResponse:
|
||||
"""Return public-safe runtime config.
|
||||
|
||||
`oauth_providers` reflects which OAuth client IDs are configured server
|
||||
side; the frontend uses it to render only buttons that will actually
|
||||
succeed. `self_serve_enabled` is the master switch for the new public
|
||||
self-serve signup flow.
|
||||
self-serve signup flow; an authenticated caller whose email is on the
|
||||
INTERNAL_TESTER_EMAILS allowlist sees `True` even when the global flag
|
||||
is off, so internal validation in prod test mode can exercise the full
|
||||
surface before the public flip.
|
||||
"""
|
||||
providers: list[str] = []
|
||||
if settings.GOOGLE_CLIENT_ID:
|
||||
@@ -34,7 +43,8 @@ async def get_public_config() -> PublicConfigResponse:
|
||||
if settings.MS_CLIENT_ID:
|
||||
providers.append("microsoft")
|
||||
|
||||
user_email = current_user.email if current_user else None
|
||||
return PublicConfigResponse(
|
||||
self_serve_enabled=settings.SELF_SERVE_ENABLED,
|
||||
self_serve_enabled=settings.is_self_serve_active_for(user_email),
|
||||
oauth_providers=providers,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user