Add subcription feature 4

This commit is contained in:
Urtzi Alfaro
2026-01-15 22:06:36 +01:00
parent b674708a4c
commit 483a9f64cd
10 changed files with 1209 additions and 1390 deletions

View File

@@ -129,13 +129,15 @@ class AuthService:
payment_setup_result: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Complete registration after successful payment verification
This is called AFTER frontend confirms SetupIntent and handles 3DS
Complete registration after successful payment verification.
NEW ARCHITECTURE: This calls tenant service to create subscription
AFTER SetupIntent verification. No subscription exists until this point.
Args:
setup_intent_id: Verified SetupIntent ID (may be None if no 3DS was required)
setup_intent_id: Verified SetupIntent ID
user_data: User registration data
payment_setup_result: Optional payment setup result with subscription info
payment_setup_result: Optional payment setup result with customer_id etc.
Returns:
Complete registration result
@@ -144,26 +146,37 @@ class AuthService:
RegistrationError: If registration completion fails
"""
try:
logger.info(f"Completing registration after payment 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}")
# If we already have subscription info from payment_setup_result, use it
# This happens when no 3DS was required and subscription was created immediately
if payment_setup_result and payment_setup_result.get('subscription_id'):
subscription_result = payment_setup_result
elif setup_intent_id:
# Step 1: Verify SetupIntent and create subscription via tenant service
subscription_result = await self.tenant_client.verify_and_complete_registration(
setup_intent_id,
{
"email": user_data.email,
"full_name": user_data.full_name,
"plan_id": user_data.subscription_plan or "professional",
"billing_cycle": user_data.billing_cycle or "monthly",
"coupon_code": user_data.coupon_code
}
)
else:
raise RegistrationError("No setup_intent_id or subscription_id available for registration completion")
if not setup_intent_id:
raise RegistrationError("SetupIntent ID is required for registration completion")
# Get customer_id and other data from payment_setup_result
customer_id = ""
payment_method_id = ""
trial_period_days = 0
if payment_setup_result:
customer_id = payment_setup_result.get('customer_id') or payment_setup_result.get('payment_customer_id', '')
payment_method_id = payment_setup_result.get('payment_method_id', '')
trial_period_days = payment_setup_result.get('trial_period_days', 0)
# Call tenant service to verify SetupIntent and CREATE subscription
subscription_result = await self.tenant_client.verify_and_complete_registration(
setup_intent_id,
{
"email": user_data.email,
"full_name": user_data.full_name,
"plan_id": user_data.subscription_plan or "professional",
"subscription_plan": user_data.subscription_plan or "professional",
"billing_cycle": user_data.billing_cycle or "monthly",
"billing_interval": user_data.billing_cycle or "monthly",
"coupon_code": user_data.coupon_code,
"customer_id": customer_id,
"payment_method_id": payment_method_id or user_data.payment_method_id,
"trial_period_days": trial_period_days
}
)
# Use a single database session for both user creation and onboarding progress
# to ensure proper transaction handling and avoid foreign key constraint violations
@@ -256,37 +269,35 @@ class AuthService:
# Check if SetupIntent requires action (3DS)
if payment_setup_result.get('requires_action', False):
logger.info(f"Registration requires SetupIntent confirmation (3DS), email={user_data.email}, setup_intent_id={payment_setup_result.get('setup_intent_id')}, subscription_id={payment_setup_result.get('subscription_id')}")
logger.info(f"Registration requires SetupIntent confirmation (3DS), email={user_data.email}, setup_intent_id={payment_setup_result.get('setup_intent_id')}")
# Return SetupIntent for frontend to handle 3DS
# Note: subscription_id is included because for trial subscriptions,
# the subscription is already created in 'trialing' status
# Note: NO subscription exists yet - subscription is created after verification
return {
'requires_action': True,
'action_type': 'setup_intent_confirmation',
'client_secret': payment_setup_result.get('client_secret'),
'setup_intent_id': payment_setup_result.get('setup_intent_id'),
'subscription_id': payment_setup_result.get('subscription_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'),
'coupon_info': payment_setup_result.get('coupon_info'),
'trial_info': payment_setup_result.get('trial_info'),
'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': 'Payment verification required. Frontend must confirm SetupIntent to handle 3DS.'
'message': 'Payment verification required. Frontend must confirm SetupIntent.'
}
else:
logger.info(f"Registration payment setup completed without 3DS, email={user_data.email}, customer_id={payment_setup_result.get('customer_id')}")
# 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')}")
# No 3DS required - proceed with user creation and subscription
# setup_intent_id may be None if no 3DS was required - use subscription_id instead
# 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 # Pass full result for additional context
payment_setup_result
)
return {
@@ -295,8 +306,8 @@ class AuthService:
'subscription_id': registration_result.get('subscription_id'),
'payment_customer_id': registration_result.get('payment_customer_id'),
'status': registration_result.get('status'),
'coupon_info': registration_result.get('coupon_info'),
'trial_info': registration_result.get('trial_info'),
'access_token': registration_result.get('access_token'),
'refresh_token': registration_result.get('refresh_token'),
'message': 'Registration completed successfully'
}