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
|
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
|
||||||
|
|||||||
@@ -131,19 +131,36 @@ class AuthService:
|
|||||||
"""
|
"""
|
||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user