Add subcription feature 8

This commit is contained in:
Urtzi Alfaro
2026-01-16 16:09:32 +01:00
parent 5e01b34cc0
commit fa7b62bd6c
2 changed files with 115 additions and 47 deletions

View File

@@ -39,20 +39,30 @@ async def start_registration(
""" """
Start secure registration flow with SetupIntent-first approach Start secure registration flow with SetupIntent-first approach
This is the FIRST step in the new registration architecture: This is the FIRST step in the atomic registration architecture:
1. Creates payment customer 1. Creates Stripe customer via tenant service
2. Attaches payment method 2. Creates SetupIntent with confirm=True
3. Creates SetupIntent for verification 3. Returns SetupIntent data to frontend
4. Returns SetupIntent to frontend for 3DS handling
If 3DS is required, frontend must confirm SetupIntent and call complete-registration IMPORTANT: NO subscription or user is created in this step!
If no 3DS required, user is created immediately and registration completes
Two possible outcomes:
- requires_action=True: 3DS required, frontend must confirm SetupIntent then call complete-registration
- requires_action=False: No 3DS required, but frontend STILL must call complete-registration
In BOTH cases, the frontend must call complete-registration to create the subscription and user.
This ensures consistent flow and prevents duplicate subscriptions.
Args: Args:
user_data: User registration data with payment info user_data: User registration data with payment info
Returns: Returns:
Registration result (may require 3DS) SetupIntent result with:
- requires_action: True if 3DS required, False if not
- setup_intent_id: SetupIntent ID for verification
- client_secret: For 3DS authentication (when requires_action=True)
- customer_id: Stripe customer ID
- Other SetupIntent metadata
Raises: Raises:
HTTPException: 400 for validation errors, 500 for server errors HTTPException: 400 for validation errors, 500 for server errors
@@ -124,6 +134,7 @@ async def start_registration(
return { return {
"requires_action": False, "requires_action": False,
"setup_intent_id": result.get('setup_intent_id'),
"user": user_data_response, "user": user_data_response,
"subscription_id": result.get('subscription_id'), "subscription_id": result.get('subscription_id'),
"payment_customer_id": result.get('payment_customer_id'), "payment_customer_id": result.get('payment_customer_id'),
@@ -156,31 +167,45 @@ async def start_registration(
@router.post("/complete-registration", @router.post("/complete-registration",
response_model=Dict[str, Any], response_model=Dict[str, Any],
summary="Complete registration after 3DS verification") summary="Complete registration after SetupIntent verification")
async def complete_registration( async def complete_registration(
verification_data: Dict[str, Any], verification_data: Dict[str, Any],
request: Request, request: Request,
auth_service: AuthService = Depends(get_auth_service) auth_service: AuthService = Depends(get_auth_service)
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
Complete registration after frontend confirms SetupIntent (3DS handled) Complete registration after frontend confirms SetupIntent
This is the SECOND step in the registration architecture: This is the SECOND step in the atomic registration architecture:
1. Called after frontend confirms SetupIntent 1. Called after frontend confirms SetupIntent (with or without 3DS)
2. Called after user completes 3DS authentication (if required) 2. Verifies SetupIntent status with Stripe
3. Verifies SetupIntent status 3. Creates subscription with verified payment method (FIRST time subscription is created)
4. Creates subscription with verified payment method 4. Creates user record in auth database
5. Creates user record 5. Saves onboarding progress
6. Saves onboarding progress 6. Generates auth tokens for auto-login
This endpoint is called in TWO scenarios:
1. After user completes 3DS authentication (requires_action=True flow)
2. Immediately after start-registration (requires_action=False flow)
In BOTH cases, this is where the subscription and user are actually created.
This ensures consistent flow and prevents duplicate subscriptions.
Args: Args:
verification_data: SetupIntent verification data verification_data: Must contain:
- setup_intent_id: Verified SetupIntent ID
- user_data: Original user registration data
Returns: Returns:
Complete registration result Complete registration result with:
- user: Created user data
- subscription_id: Created subscription ID
- payment_customer_id: Stripe customer ID
- access_token: JWT access token
- refresh_token: JWT refresh token
Raises: Raises:
HTTPException: 400 for validation errors, 500 for server errors HTTPException: 400 if setup_intent_id is missing, 500 for server errors
""" """
try: try:
# Validate required fields # Validate required fields

View File

@@ -130,20 +130,37 @@ class AuthService:
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
Complete registration after successful payment verification. Complete registration after successful payment verification.
NEW ARCHITECTURE: This calls tenant service to create subscription This is the CORE method that creates the subscription and user.
AFTER SetupIntent verification. No subscription exists until this point. It is called by complete_registration_with_verified_payment in both scenarios:
1. After 3DS authentication (requires_action=True flow)
2. Immediately after start-registration (requires_action=False flow)
CRITICAL: This is the FIRST and ONLY place where:
- Subscription is created via tenant service
- User record is created in auth database
- Onboarding progress is saved
- Auth tokens are generated
This ensures that subscriptions are only created after SetupIntent verification,
preventing duplicate subscriptions and maintaining payment security.
Args: Args:
setup_intent_id: Verified SetupIntent ID setup_intent_id: Verified SetupIntent ID (must not be None)
user_data: User registration data user_data: User registration data
payment_setup_result: Optional payment setup result with customer_id etc. payment_setup_result: Optional payment setup result with customer_id etc.
Returns: Returns:
Complete registration result Complete registration result with:
- user: Created user data
- subscription_id: Created subscription ID
- payment_customer_id: Stripe customer ID
- status: Subscription status
- access_token: JWT access token
- refresh_token: JWT refresh token
Raises: Raises:
RegistrationError: If registration completion fails RegistrationError: If registration completion fails (e.g., missing setup_intent_id)
""" """
try: try:
logger.info(f"Completing registration after verification, email={user_data.email}, setup_intent_id={setup_intent_id}") logger.info(f"Completing registration after verification, email={user_data.email}, setup_intent_id={setup_intent_id}")
@@ -250,13 +267,28 @@ class AuthService:
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
Start secure registration flow with SetupIntent-first approach Start secure registration flow with SetupIntent-first approach
Main entry point for new registration architecture
This is the FIRST step in the atomic registration flow:
1. Creates Stripe customer
2. Creates SetupIntent with confirm=True
3. Returns SetupIntent data to frontend
IMPORTANT: NO subscription is created in this step!
Subscription creation happens in complete_registration_after_payment_verification
Two possible outcomes:
- requires_action=True: 3DS required, frontend must confirm SetupIntent
- requires_action=False: No 3DS required, but frontend STILL must call complete-registration
Args: Args:
user_data: User registration data user_data: User registration data
Returns: Returns:
Registration flow result (may require 3DS) Registration flow result with SetupIntent data
- requires_action: True if 3DS required, False if not
- setup_intent_id: SetupIntent ID for verification
- client_secret: For 3DS authentication (when requires_action=True)
- Other SetupIntent metadata
Raises: Raises:
RegistrationError: If registration flow fails RegistrationError: If registration flow fails
@@ -292,23 +324,20 @@ class AuthService:
# No 3DS required - SetupIntent already succeeded # No 3DS required - SetupIntent already succeeded
logger.info(f"Registration SetupIntent succeeded without 3DS, email={user_data.email}, setup_intent_id={payment_setup_result.get('setup_intent_id')}") logger.info(f"Registration SetupIntent succeeded without 3DS, email={user_data.email}, setup_intent_id={payment_setup_result.get('setup_intent_id')}")
# Complete registration - create subscription now # Note: NO subscription created yet - frontend must call complete-registration
setup_intent_id = payment_setup_result.get('setup_intent_id') # This ensures consistent flow whether 3DS is required or not
registration_result = await self.complete_registration_after_payment_verification(
setup_intent_id,
user_data,
payment_setup_result
)
return { return {
'requires_action': False, 'requires_action': False,
'user': registration_result.get('user'), 'setup_intent_id': payment_setup_result.get('setup_intent_id'),
'subscription_id': registration_result.get('subscription_id'), 'customer_id': payment_setup_result.get('customer_id'),
'payment_customer_id': registration_result.get('payment_customer_id'), 'payment_customer_id': payment_setup_result.get('payment_customer_id'),
'status': registration_result.get('status'), 'plan_id': payment_setup_result.get('plan_id'),
'access_token': registration_result.get('access_token'), 'payment_method_id': payment_setup_result.get('payment_method_id'),
'refresh_token': registration_result.get('refresh_token'), 'billing_cycle': payment_setup_result.get('billing_cycle'),
'message': 'Registration completed successfully' 'trial_period_days': payment_setup_result.get('trial_period_days', 0),
'coupon_code': payment_setup_result.get('coupon_code'),
'email': payment_setup_result.get('email'),
'message': 'SetupIntent succeeded without 3DS. Frontend must call complete-registration to create subscription.'
} }
except Exception as e: except Exception as e:
@@ -321,15 +350,29 @@ class AuthService:
user_data: UserRegistration user_data: UserRegistration
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
Complete registration after frontend confirms SetupIntent (3DS handled) Complete registration after frontend confirms SetupIntent
This is called by frontend after user completes 3DS authentication
This is the SECOND step in the atomic registration flow and is called in TWO scenarios:
1. After user completes 3DS authentication (when requires_action=True)
2. Immediately after start-registration (when requires_action=False)
In BOTH cases, this method:
- Verifies the SetupIntent status with Stripe
- Calls tenant service to create the subscription (first time subscription is created)
- Creates the user record in auth database
- Saves onboarding progress
- Generates auth tokens for auto-login
This ensures a CONSISTENT flow whether 3DS is required or not:
Step 1: start-registration creates SetupIntent only (NO subscription yet)
Step 2: complete-registration verifies SetupIntent and creates subscription
Args: Args:
setup_intent_id: SetupIntent ID that was confirmed setup_intent_id: SetupIntent ID that was confirmed (either by Stripe or frontend)
user_data: User registration data user_data: User registration data
Returns: Returns:
Complete registration result Complete registration result with user data, tokens, and subscription info
Raises: Raises:
RegistrationError: If completion fails RegistrationError: If completion fails