Add subcription feature 8
This commit is contained in:
@@ -39,20 +39,30 @@ async def start_registration(
|
||||
"""
|
||||
Start secure registration flow with SetupIntent-first approach
|
||||
|
||||
This is the FIRST step in the new registration architecture:
|
||||
1. Creates payment customer
|
||||
2. Attaches payment method
|
||||
3. Creates SetupIntent for verification
|
||||
4. Returns SetupIntent to frontend for 3DS handling
|
||||
This is the FIRST step in the atomic registration architecture:
|
||||
1. Creates Stripe customer via tenant service
|
||||
2. Creates SetupIntent with confirm=True
|
||||
3. Returns SetupIntent data to frontend
|
||||
|
||||
If 3DS is required, frontend must confirm SetupIntent and call complete-registration
|
||||
If no 3DS required, user is created immediately and registration completes
|
||||
IMPORTANT: NO subscription or user is created in this step!
|
||||
|
||||
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:
|
||||
user_data: User registration data with payment info
|
||||
|
||||
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:
|
||||
HTTPException: 400 for validation errors, 500 for server errors
|
||||
@@ -124,6 +134,7 @@ async def start_registration(
|
||||
|
||||
return {
|
||||
"requires_action": False,
|
||||
"setup_intent_id": result.get('setup_intent_id'),
|
||||
"user": user_data_response,
|
||||
"subscription_id": result.get('subscription_id'),
|
||||
"payment_customer_id": result.get('payment_customer_id'),
|
||||
@@ -156,31 +167,45 @@ async def start_registration(
|
||||
|
||||
@router.post("/complete-registration",
|
||||
response_model=Dict[str, Any],
|
||||
summary="Complete registration after 3DS verification")
|
||||
summary="Complete registration after SetupIntent verification")
|
||||
async def complete_registration(
|
||||
verification_data: Dict[str, Any],
|
||||
request: Request,
|
||||
auth_service: AuthService = Depends(get_auth_service)
|
||||
) -> 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:
|
||||
1. Called after frontend confirms SetupIntent
|
||||
2. Called after user completes 3DS authentication (if required)
|
||||
3. Verifies SetupIntent status
|
||||
4. Creates subscription with verified payment method
|
||||
5. Creates user record
|
||||
6. Saves onboarding progress
|
||||
This is the SECOND step in the atomic registration architecture:
|
||||
1. Called after frontend confirms SetupIntent (with or without 3DS)
|
||||
2. Verifies SetupIntent status with Stripe
|
||||
3. Creates subscription with verified payment method (FIRST time subscription is created)
|
||||
4. Creates user record in auth database
|
||||
5. 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:
|
||||
verification_data: SetupIntent verification data
|
||||
verification_data: Must contain:
|
||||
- setup_intent_id: Verified SetupIntent ID
|
||||
- user_data: Original user registration data
|
||||
|
||||
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:
|
||||
HTTPException: 400 for validation errors, 500 for server errors
|
||||
HTTPException: 400 if setup_intent_id is missing, 500 for server errors
|
||||
"""
|
||||
try:
|
||||
# Validate required fields
|
||||
|
||||
@@ -130,20 +130,37 @@ class AuthService:
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Complete registration after successful payment verification.
|
||||
|
||||
NEW ARCHITECTURE: This calls tenant service to create subscription
|
||||
AFTER SetupIntent verification. No subscription exists until this point.
|
||||
|
||||
This is the CORE method that creates the subscription and user.
|
||||
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:
|
||||
setup_intent_id: Verified SetupIntent ID
|
||||
setup_intent_id: Verified SetupIntent ID (must not be None)
|
||||
user_data: User registration data
|
||||
payment_setup_result: Optional payment setup result with customer_id etc.
|
||||
|
||||
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:
|
||||
RegistrationError: If registration completion fails
|
||||
RegistrationError: If registration completion fails (e.g., missing setup_intent_id)
|
||||
"""
|
||||
try:
|
||||
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]:
|
||||
"""
|
||||
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:
|
||||
user_data: User registration data
|
||||
|
||||
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:
|
||||
RegistrationError: If registration flow fails
|
||||
@@ -292,23 +324,20 @@ class AuthService:
|
||||
# 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')}")
|
||||
|
||||
# Complete registration - create subscription now
|
||||
setup_intent_id = payment_setup_result.get('setup_intent_id')
|
||||
registration_result = await self.complete_registration_after_payment_verification(
|
||||
setup_intent_id,
|
||||
user_data,
|
||||
payment_setup_result
|
||||
)
|
||||
|
||||
# Note: NO subscription created yet - frontend must call complete-registration
|
||||
# This ensures consistent flow whether 3DS is required or not
|
||||
return {
|
||||
'requires_action': False,
|
||||
'user': registration_result.get('user'),
|
||||
'subscription_id': registration_result.get('subscription_id'),
|
||||
'payment_customer_id': registration_result.get('payment_customer_id'),
|
||||
'status': registration_result.get('status'),
|
||||
'access_token': registration_result.get('access_token'),
|
||||
'refresh_token': registration_result.get('refresh_token'),
|
||||
'message': 'Registration completed successfully'
|
||||
'setup_intent_id': payment_setup_result.get('setup_intent_id'),
|
||||
'customer_id': payment_setup_result.get('customer_id'),
|
||||
'payment_customer_id': payment_setup_result.get('payment_customer_id'),
|
||||
'plan_id': payment_setup_result.get('plan_id'),
|
||||
'payment_method_id': payment_setup_result.get('payment_method_id'),
|
||||
'billing_cycle': payment_setup_result.get('billing_cycle'),
|
||||
'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:
|
||||
@@ -321,15 +350,29 @@ class AuthService:
|
||||
user_data: UserRegistration
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Complete registration after frontend confirms SetupIntent (3DS handled)
|
||||
This is called by frontend after user completes 3DS authentication
|
||||
Complete registration after frontend confirms SetupIntent
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
Returns:
|
||||
Complete registration result
|
||||
Complete registration result with user data, tokens, and subscription info
|
||||
|
||||
Raises:
|
||||
RegistrationError: If completion fails
|
||||
|
||||
Reference in New Issue
Block a user