# app/api/v1/platform/signup.py """ Platform signup API endpoints. Handles the multi-step signup flow: 1. Start signup (select tier) 2. Claim Letzshop vendor (optional) 3. Create account 4. Setup payment (collect card via SetupIntent) 5. Complete signup (create subscription with trial) All endpoints are public (no authentication required). """ import logging from fastapi import APIRouter, Depends from pydantic import BaseModel, EmailStr from sqlalchemy.orm import Session from app.core.database import get_db from app.services.platform_signup_service import platform_signup_service router = APIRouter() logger = logging.getLogger(__name__) # ============================================================================= # Request/Response Schemas # ============================================================================= class SignupStartRequest(BaseModel): """Start signup - select tier.""" tier_code: str is_annual: bool = False class SignupStartResponse(BaseModel): """Response from signup start.""" session_id: str tier_code: str is_annual: bool class ClaimVendorRequest(BaseModel): """Claim Letzshop vendor.""" session_id: str letzshop_slug: str letzshop_vendor_id: str | None = None class ClaimVendorResponse(BaseModel): """Response from vendor claim.""" session_id: str letzshop_slug: str vendor_name: str | None class CreateAccountRequest(BaseModel): """Create account.""" session_id: str email: EmailStr password: str first_name: str last_name: str company_name: str phone: str | None = None class CreateAccountResponse(BaseModel): """Response from account creation.""" session_id: str user_id: int vendor_id: int stripe_customer_id: str class SetupPaymentRequest(BaseModel): """Request payment setup.""" session_id: str class SetupPaymentResponse(BaseModel): """Response with Stripe SetupIntent client secret.""" session_id: str client_secret: str stripe_customer_id: str class CompleteSignupRequest(BaseModel): """Complete signup after card collection.""" session_id: str setup_intent_id: str class CompleteSignupResponse(BaseModel): """Response from signup completion.""" success: bool vendor_code: str vendor_id: int redirect_url: str trial_ends_at: str # ============================================================================= # Endpoints # ============================================================================= @router.post("/signup/start", response_model=SignupStartResponse) # public async def start_signup(request: SignupStartRequest) -> SignupStartResponse: """ Start the signup process. Step 1: User selects a tier and billing period. Creates a signup session to track the flow. """ session_id = platform_signup_service.create_session( tier_code=request.tier_code, is_annual=request.is_annual, ) return SignupStartResponse( session_id=session_id, tier_code=request.tier_code, is_annual=request.is_annual, ) @router.post("/signup/claim-vendor", response_model=ClaimVendorResponse) # public async def claim_letzshop_vendor( request: ClaimVendorRequest, db: Session = Depends(get_db), ) -> ClaimVendorResponse: """ Claim a Letzshop vendor. Step 2 (optional): User claims their Letzshop shop. This pre-fills vendor info during account creation. """ vendor_name = platform_signup_service.claim_vendor( db=db, session_id=request.session_id, letzshop_slug=request.letzshop_slug, letzshop_vendor_id=request.letzshop_vendor_id, ) return ClaimVendorResponse( session_id=request.session_id, letzshop_slug=request.letzshop_slug, vendor_name=vendor_name, ) @router.post("/signup/create-account", response_model=CreateAccountResponse) # public async def create_account( request: CreateAccountRequest, db: Session = Depends(get_db), ) -> CreateAccountResponse: """ Create user and vendor accounts. Step 3: User provides account details. Creates User, Company, Vendor, and Stripe Customer. """ result = platform_signup_service.create_account( db=db, session_id=request.session_id, email=request.email, password=request.password, first_name=request.first_name, last_name=request.last_name, company_name=request.company_name, phone=request.phone, ) return CreateAccountResponse( session_id=request.session_id, user_id=result.user_id, vendor_id=result.vendor_id, stripe_customer_id=result.stripe_customer_id, ) @router.post("/signup/setup-payment", response_model=SetupPaymentResponse) # public async def setup_payment(request: SetupPaymentRequest) -> SetupPaymentResponse: """ Create Stripe SetupIntent for card collection. Step 4: Collect card details without charging. The card will be charged after the trial period ends. """ client_secret, stripe_customer_id = platform_signup_service.setup_payment( session_id=request.session_id, ) return SetupPaymentResponse( session_id=request.session_id, client_secret=client_secret, stripe_customer_id=stripe_customer_id, ) @router.post("/signup/complete", response_model=CompleteSignupResponse) # public async def complete_signup( request: CompleteSignupRequest, db: Session = Depends(get_db), ) -> CompleteSignupResponse: """ Complete signup after card collection. Step 5: Verify SetupIntent, attach payment method, create subscription. """ result = platform_signup_service.complete_signup( db=db, session_id=request.session_id, setup_intent_id=request.setup_intent_id, ) return CompleteSignupResponse( success=result.success, vendor_code=result.vendor_code, vendor_id=result.vendor_id, redirect_url=result.redirect_url, trial_ends_at=result.trial_ends_at, ) @router.get("/signup/session/{session_id}") # public async def get_signup_session(session_id: str) -> dict: """ Get signup session status. Useful for resuming an incomplete signup. """ session = platform_signup_service.get_session_or_raise(session_id) # Return safe subset of session data return { "session_id": session_id, "step": session.get("step"), "tier_code": session.get("tier_code"), "is_annual": session.get("is_annual"), "letzshop_slug": session.get("letzshop_slug"), "vendor_name": session.get("vendor_name"), "created_at": session.get("created_at"), }