Add subcription feature 3

This commit is contained in:
Urtzi Alfaro
2026-01-15 20:45:49 +01:00
parent a4c3b7da3f
commit b674708a4c
83 changed files with 9451 additions and 6828 deletions

View File

@@ -211,7 +211,8 @@ async def clone_demo_data(
subscription_data.get('cancellation_effective_date'),
session_time,
"cancellation_effective_date"
)
),
is_tenant_linked=True # Required for check constraint when tenant_id is set
)
db.add(subscription)
@@ -245,7 +246,8 @@ async def clone_demo_data(
"api_access": True,
"priority_support": True
},
next_billing_date=datetime.now(timezone.utc) + timedelta(days=90)
next_billing_date=datetime.now(timezone.utc) + timedelta(days=90),
is_tenant_linked=True # Required for check constraint when tenant_id is set
)
db.add(subscription)
@@ -323,7 +325,8 @@ async def clone_demo_data(
max_users=-1, # Unlimited for demo
max_locations=max_locations,
max_products=-1, # Unlimited for demo
features={}
features={},
is_tenant_linked=True # Required for check constraint when tenant_id is set
)
db.add(demo_subscription)
@@ -699,7 +702,8 @@ async def create_child_outlet(
max_users=10, # Demo limits
max_locations=1, # Single location for outlet
max_products=200,
features={}
features={},
is_tenant_linked=True # Required for check constraint when tenant_id is set
)
db.add(child_subscription)

File diff suppressed because it is too large Load Diff

View File

@@ -1122,6 +1122,88 @@ async def upgrade_subscription_plan(
detail="Failed to upgrade subscription plan"
)
# ============================================================================
# REGISTRATION ORCHESTRATION (SECURE 3DS FLOW)
# ============================================================================
@router.post("/registration-payment-setup")
async def registration_payment_setup(
user_data: Dict[str, Any],
payment_service: PaymentService = Depends(get_payment_service)
):
"""
Orchestrate initial payment setup for a new registration.
This creates the customer and SetupIntent for 3DS verification.
"""
try:
logger.info("Orchestrating registration payment setup",
email=user_data.get('email'))
result = await payment_service.complete_registration_payment_flow(user_data)
return result
except Exception as e:
logger.error("Registration payment setup orchestration failed",
email=user_data.get('email'),
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Registration payment setup failed: {str(e)}"
)
@router.post("/verify-and-complete-registration")
async def verify_and_complete_registration(
registration_data: Dict[str, Any],
payment_service: PaymentService = Depends(get_payment_service),
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
):
"""
Final step: Verify SetupIntent and create subscription + tenant.
Called after frontend confirms 3DS with Stripe.
"""
try:
setup_intent_id = registration_data.get('setup_intent_id')
user_data = registration_data.get('user_data', {})
logger.info("Verifying and completing registration",
email=user_data.get('email'),
setup_intent_id=setup_intent_id)
# 1. Complete subscription creation after verification
payment_result = await payment_service.complete_subscription_after_verification(
setup_intent_id, user_data
)
# 2. Create the bakery/tenant record
# Note: In a real flow, we'd use the payment result (customer_id, subscription_id)
# to properly link the new tenant.
bakery_registration = BakeryRegistration(
name=user_data.get('name', f"{user_data.get('full_name')}'s Bakery"),
subdomain=user_data.get('subdomain', user_data.get('email').split('@')[0]),
business_type=user_data.get('business_type', 'bakery'),
link_existing_subscription=True,
subscription_id=payment_result['subscription_id']
)
# We need the user_id from the auth service call
user_id = user_data.get('user_id')
if not user_id:
raise HTTPException(status_code=400, detail="Missing user_id in registration data")
tenant_result = await tenant_service.create_bakery(bakery_registration, user_id)
return {
"success": True,
"tenant": tenant_result,
"payment": payment_result
}
except Exception as e:
logger.error("Verify and complete registration failed",
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Registration completion failed: {str(e)}"
)
# ============================================================================
# PAYMENT OPERATIONS
# ============================================================================
@@ -1244,7 +1326,7 @@ async def register_with_subscription(
**result
}
except Exception as e:
logger.error("Failed to register with subscription", error=str(e))
logger.error(f"Failed to register with subscription: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to register with subscription"

View File

@@ -88,7 +88,10 @@ async def stripe_webhook(
raise
except Exception as e:
logger.error("Error processing Stripe webhook", error=str(e), exc_info=True)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Webhook processing error"
)
# Return 200 OK even on processing errors to prevent Stripe retries
# Only return 4xx for signature verification failures
return {
"success": False,
"error": "Webhook processing error",
"details": str(e)
}