# User Registration & Subscription Architecture Rearchitecture Proposal ## Executive Summary This proposal outlines a comprehensive rearchitecture of the user registration, payment processing, and subscription management flow to address the current limitations and implement the requested multi-phase registration process. ## Current Architecture Analysis ### Current Flow Limitations 1. **Monolithic Registration Process**: The current flow combines user creation, payment processing, and subscription creation in a single step 2. **Tenant-Subscription Coupling**: Subscriptions are created and immediately linked to tenants during registration 3. **Payment Processing Timing**: Payment is processed before user creation is complete 4. **Onboarding Complexity**: The onboarding flow assumes immediate tenant creation with subscription ### Key Components Analysis #### Frontend Components - `RegisterForm.tsx`: Multi-step form handling basic info, subscription selection, and payment - `PaymentForm.tsx`: Stripe payment processing component - `RegisterTenantStep.tsx`: Tenant creation during onboarding #### Backend Services - **Auth Service**: User creation, authentication, and onboarding progress tracking - **Tenant Service**: Tenant creation, subscription management, and payment processing - **Shared Clients**: Inter-service communication between auth and tenant services #### Current Data Flow ```mermaid graph TD A[Frontend RegisterForm] -->|User Data + Payment| B[Auth Service Register] B -->|Create User| C[User Created] B -->|Call Tenant Service| D[Tenant Service Payment Customer] D -->|Create Payment Customer| E[Payment Customer Created] C -->|Return Tokens| F[User Authenticated] F -->|Onboarding| G[RegisterTenantStep] G -->|Create Tenant + Subscription| H[Tenant Service Create Tenant] H -->|Create Subscription| I[Subscription Created] ``` ## Proposed Architecture ### New Multi-Phase Registration Flow ```mermaid graph TD subgraph Frontend A1[Basic Info Form] -->|Email + Password| A2[Subscription Selection] A2 -->|Plan + Billing Cycle| A3[Payment Form] A3 -->|Payment Method| A4[Process Payment] end subgraph Backend Services A4 -->|User Data + Payment| B1[Auth Service Register] B1 -->|Create User| B2[User Created with Payment ID] B2 -->|Call Tenant Service| B3[Tenant Service Create Subscription] B3 -->|Create Subscription| B4[Subscription Created] B4 -->|Return Subscription ID| B2 B2 -->|Return Auth Tokens| A4 end subgraph Onboarding A4 -->|Success| C1[Onboarding Flow] C1 -->|Tenant Creation| C2[RegisterTenantStep] C2 -->|Tenant Data| C3[Tenant Service Create Tenant] C3 -->|Link Subscription| C4[Link Subscription to Tenant] C4 -->|Complete| C5[Onboarding Complete] end ``` ### Detailed Component Changes #### 1. Frontend Changes **RegisterForm.tsx Modifications:** - **Phase 1**: Collect only email and password (basic info) - **Phase 2**: Plan selection with billing cycle options - **Phase 3**: Payment form with address and card details - **Payment Processing**: Call new backend endpoint with complete registration data **New Payment Flow:** ```typescript // Current: handleRegistrationSubmit calls authService.register directly // New: handleRegistrationSubmit calls new registration endpoint const handleRegistrationSubmit = async (paymentMethodId?: string) => { try { const registrationData = { email: formData.email, password: formData.password, full_name: formData.full_name, subscription_plan: selectedPlan, billing_cycle: billingCycle, payment_method_id: paymentMethodId, coupon_code: isPilot ? couponCode : undefined, // Address and billing info address: billingAddress, postal_code: billingPostalCode, city: billingCity, country: billingCountry }; // Call new registration endpoint const response = await authService.registerWithSubscription(registrationData); // Handle success and redirect to onboarding onSuccess?.(); } catch (err) { // Handle errors } }; ``` #### 2. Auth Service Changes **New Registration Endpoint:** ```python @router.post("/api/v1/auth/register-with-subscription") async def register_with_subscription( user_data: UserRegistrationWithSubscription, auth_service: EnhancedAuthService = Depends(get_auth_service) ): """Register user and create subscription in one call""" # Step 1: Create user user = await auth_service.register_user(user_data) # Step 2: Create payment customer via tenant service payment_result = await auth_service.create_payment_customer_via_tenant_service( user_data, user_data.payment_method_id ) # Step 3: Create subscription via tenant service subscription_result = await auth_service.create_subscription_via_tenant_service( user.id, user_data.subscription_plan, user_data.payment_method_id, user_data.billing_cycle, user_data.coupon_code ) # Step 4: Store subscription ID in user's onboarding progress await auth_service.save_subscription_to_onboarding_progress( user.id, subscription_result.subscription_id, user_data ) return { **user, subscription_id: subscription_result.subscription_id } ``` **Enhanced Auth Service Methods:** ```python class EnhancedAuthService: async def create_subscription_via_tenant_service( self, user_id: str, plan_id: str, payment_method_id: str, billing_cycle: str, coupon_code: Optional[str] = None ) -> Dict[str, Any]: """Create subscription via tenant service during registration""" try: from shared.clients.tenant_client import TenantServiceClient from app.core.config import settings tenant_client = TenantServiceClient(settings) # Prepare user data for tenant service user_data = await self.get_user_data_for_tenant_service(user_id) # Call tenant service to create subscription result = await tenant_client.create_subscription_for_registration( user_data=user_data, plan_id=plan_id, payment_method_id=payment_method_id, billing_cycle=billing_cycle, coupon_code=coupon_code ) return result except Exception as e: logger.error("Failed to create subscription via tenant service", user_id=user_id, error=str(e)) raise async def save_subscription_to_onboarding_progress( self, user_id: str, subscription_id: str, registration_data: Dict[str, Any] ): """Store subscription info in onboarding progress for later tenant linking""" try: # Get or create onboarding progress progress = await self.onboarding_repo.get_user_progress(user_id) if not progress: progress = await self.onboarding_repo.create_user_progress(user_id) # Store subscription data in user_registered step step_data = { "subscription_id": subscription_id, "subscription_plan": registration_data.subscription_plan, "billing_cycle": registration_data.billing_cycle, "coupon_code": registration_data.coupon_code, "payment_method_id": registration_data.payment_method_id, "payment_customer_id": registration_data.payment_customer_id, "created_at": datetime.now(timezone.utc).isoformat(), "status": "pending_tenant_linking" } await self.onboarding_repo.upsert_user_step( user_id=user_id, step_name="user_registered", completed=True, step_data=step_data ) logger.info("Subscription data saved to onboarding progress", user_id=user_id, subscription_id=subscription_id) except Exception as e: logger.error("Failed to save subscription to onboarding progress", user_id=user_id, error=str(e)) raise ``` #### 3. Tenant Service Changes **New Subscription Creation Endpoint:** ```python @router.post("/api/v1/subscriptions/create-for-registration") async def create_subscription_for_registration( user_data: Dict[str, Any], plan_id: str = Query(...), payment_method_id: str = Query(...), billing_cycle: str = Query("monthly"), coupon_code: Optional[str] = Query(None), payment_service: PaymentService = Depends(get_payment_service), db: AsyncSession = Depends(get_db) ): """ Create subscription during user registration (before tenant creation) This endpoint creates a subscription that is not yet linked to any tenant. The subscription will be linked to a tenant during the onboarding flow. """ try: # Use orchestration service for complete workflow orchestration_service = SubscriptionOrchestrationService(db) # Create subscription without tenant_id (tenant-independent subscription) result = await orchestration_service.create_tenant_independent_subscription( user_data, plan_id, payment_method_id, billing_cycle, coupon_code ) logger.info("Tenant-independent subscription created for registration", user_id=user_data.get('user_id'), subscription_id=result["subscription_id"]) return { "success": True, "subscription_id": result["subscription_id"], "customer_id": result["customer_id"], "status": result["status"], "plan": result["plan"], "billing_cycle": result["billing_cycle"] } except Exception as e: logger.error("Failed to create tenant-independent subscription", error=str(e), user_id=user_data.get('user_id')) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create subscription" ) ``` **Enhanced Subscription Orchestration Service:** ```python class SubscriptionOrchestrationService: async def create_tenant_independent_subscription( self, user_data: Dict[str, Any], plan_id: str, payment_method_id: str, billing_cycle: str = "monthly", coupon_code: Optional[str] = None ) -> Dict[str, Any]: """ Create a subscription that is not linked to any tenant yet This subscription will be linked to a tenant during onboarding when the user creates their bakery/tenant. """ try: logger.info("Creating tenant-independent subscription", user_id=user_data.get('user_id'), plan_id=plan_id) # Step 1: Create customer in payment provider customer = await self.payment_service.create_customer(user_data) # Step 2: Handle coupon logic trial_period_days = 0 coupon_discount = None if coupon_code: coupon_service = CouponService(self.db_session) success, discount_applied, error = await coupon_service.redeem_coupon( coupon_code, None, # No tenant_id yet base_trial_days=0 ) if success and discount_applied: coupon_discount = discount_applied trial_period_days = discount_applied.get("total_trial_days", 0) # Step 3: Create subscription in payment provider stripe_subscription = await self.payment_service.create_payment_subscription( customer.id, plan_id, payment_method_id, trial_period_days if trial_period_days > 0 else None, billing_cycle ) # Step 4: Create local subscription record WITHOUT tenant_id subscription_record = await self.subscription_service.create_tenant_independent_subscription_record( stripe_subscription.id, customer.id, plan_id, stripe_subscription.status, stripe_subscription.current_period_start, stripe_subscription.current_period_end, trial_period_days if trial_period_days > 0 else None, billing_cycle, user_data.get('user_id') ) # Step 5: Store subscription in pending_tenant_linking state await self.subscription_service.mark_subscription_as_pending_tenant_linking( subscription_record.id, user_data.get('user_id') ) return { "success": True, "customer_id": customer.id, "subscription_id": stripe_subscription.id, "status": stripe_subscription.status, "plan": plan_id, "billing_cycle": billing_cycle, "trial_period_days": trial_period_days, "current_period_end": stripe_subscription.current_period_end.isoformat(), "coupon_applied": bool(coupon_discount), "user_id": user_data.get('user_id') } except Exception as e: logger.error("Failed to create tenant-independent subscription", error=str(e), user_id=user_data.get('user_id')) raise ``` **New Subscription Service Methods:** ```python class SubscriptionService: async def create_tenant_independent_subscription_record( self, subscription_id: str, customer_id: str, plan: str, status: str, current_period_start: datetime, current_period_end: datetime, trial_period_days: Optional[int] = None, billing_cycle: str = "monthly", user_id: Optional[str] = None ) -> Subscription: """Create subscription record without tenant_id""" try: subscription_data = { "subscription_id": subscription_id, "customer_id": customer_id, "plan": plan, "status": status, "current_period_start": current_period_start, "current_period_end": current_period_end, "trial_period_days": trial_period_days, "billing_cycle": billing_cycle, "user_id": user_id, "tenant_id": None, # No tenant linked yet "is_tenant_linked": False, "created_at": datetime.now(timezone.utc), "updated_at": datetime.now(timezone.utc) } subscription = await self.subscription_repo.create(subscription_data) logger.info("Tenant-independent subscription record created", subscription_id=subscription.id, user_id=user_id) return subscription except Exception as e: logger.error("Failed to create tenant-independent subscription record", error=str(e)) raise async def mark_subscription_as_pending_tenant_linking( self, subscription_id: str, user_id: str ): """Mark subscription as pending tenant linking""" try: await self.subscription_repo.update( subscription_id, { "status": "pending_tenant_linking", "tenant_linking_status": "pending", "user_id": user_id } ) logger.info("Subscription marked as pending tenant linking", subscription_id=subscription_id, user_id=user_id) except Exception as e: logger.error("Failed to mark subscription as pending tenant linking", error=str(e), subscription_id=subscription_id) raise ``` #### 4. Onboarding Flow Changes **Enhanced RegisterTenantStep:** ```typescript // When tenant is created, link the pending subscription const handleSubmit = async () => { if (!validateForm()) { return; } try { let tenant; if (tenantId) { // Update existing tenant const updateData: TenantUpdate = { ... }; tenant = await updateTenant.mutateAsync({ tenantId, updateData }); } else { // Create new tenant and link subscription const registrationData: BakeryRegistrationWithSubscription = { ...formData, // Include subscription linking data from onboarding progress subscription_id: wizardContext.state.subscriptionId, link_existing_subscription: true }; tenant = await registerBakery.mutateAsync(registrationData); } // Continue with onboarding onComplete({ tenant, tenantId: tenant.id }); } catch (error) { console.error('Error registering bakery:', error); setErrors({ submit: t('onboarding:steps.tenant_registration.errors.register') }); } }; ``` **Enhanced Tenant Creation Endpoint:** ```python @router.post(route_builder.build_base_route("register", include_tenant_prefix=False)) async def register_bakery( bakery_data: BakeryRegistrationWithSubscription, current_user: Dict[str, Any] = Depends(get_current_user_dep), tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service), db: AsyncSession = Depends(get_db) ): """Register a new bakery/tenant with subscription linking""" try: // Create tenant first result = await tenant_service.create_bakery(bakery_data, current_user["user_id"]) tenant_id = result["tenant_id"] // Check if we need to link an existing subscription if bakery_data.link_existing_subscription and bakery_data.subscription_id: // Link the pending subscription to this tenant subscription_result = await tenant_service.link_subscription_to_tenant( tenant_id, bakery_data.subscription_id, current_user["user_id"] ) logger.info("Subscription linked to tenant during registration", tenant_id=tenant_id, subscription_id=bakery_data.subscription_id) else: // Fallback to current behavior for backward compatibility // Create new subscription if needed pass return result except Exception as e: logger.error("Failed to register bakery with subscription linking", error=str(e), user_id=current_user["user_id"]) raise ``` **New Tenant Service Method for Subscription Linking:** ```python class EnhancedTenantService: async def link_subscription_to_tenant( self, tenant_id: str, subscription_id: str, user_id: str ) -> Dict[str, Any]: """Link a pending subscription to a tenant""" try: async with self.database_manager.get_session() as db_session: async with UnitOfWork(db_session) as uow: # Register repositories subscription_repo = uow.register_repository( "subscriptions", SubscriptionRepository, Subscription ) tenant_repo = uow.register_repository( "tenants", TenantRepository, Tenant ) # Get the subscription subscription = await subscription_repo.get_by_id(subscription_id) if not subscription: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Subscription not found" ) # Verify subscription is in pending_tenant_linking state if subscription.tenant_linking_status != "pending": raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Subscription is not in pending tenant linking state" ) # Verify subscription belongs to this user if subscription.user_id != user_id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Subscription does not belong to this user" ) # Update subscription with tenant_id update_data = { "tenant_id": tenant_id, "is_tenant_linked": True, "tenant_linking_status": "completed", "linked_at": datetime.now(timezone.utc) } await subscription_repo.update(subscription_id, update_data) # Update tenant with subscription information tenant_update = { "stripe_customer_id": subscription.customer_id, "subscription_status": subscription.status, "subscription_plan": subscription.plan, "subscription_tier": subscription.plan, "billing_cycle": subscription.billing_cycle, "trial_period_days": subscription.trial_period_days } await tenant_repo.update_tenant(tenant_id, tenant_update) # Commit transaction await uow.commit() logger.info("Subscription successfully linked to tenant", tenant_id=tenant_id, subscription_id=subscription_id, user_id=user_id) return { "success": True, "tenant_id": tenant_id, "subscription_id": subscription_id, "status": "linked" } except Exception as e: logger.error("Failed to link subscription to tenant", error=str(e), tenant_id=tenant_id, subscription_id=subscription_id, user_id=user_id) raise ``` ## Database Schema Changes ### New Subscription Table Structure ```sql -- Add new columns to subscriptions table ALTER TABLE subscriptions ADD COLUMN IF NOT EXISTS user_id UUID; ALTER TABLE subscriptions ADD COLUMN IF NOT EXISTS is_tenant_linked BOOLEAN DEFAULT FALSE; ALTER TABLE subscriptions ADD COLUMN IF NOT EXISTS tenant_linking_status VARCHAR(50); ALTER TABLE subscriptions ADD COLUMN IF NOT EXISTS linked_at TIMESTAMP; -- Add index for user-based subscription queries CREATE INDEX IF NOT EXISTS idx_subscriptions_user_id ON subscriptions(user_id); CREATE INDEX IF NOT EXISTS idx_subscriptions_linking_status ON subscriptions(tenant_linking_status); -- Add constraint to ensure tenant_id is NULL when not linked ALTER TABLE subscriptions ADD CONSTRAINT chk_tenant_linking CHECK ((is_tenant_linked = FALSE AND tenant_id IS NULL) OR (is_tenant_linked = TRUE AND tenant_id IS NOT NULL)); ``` ### Onboarding Progress Data Structure ```json { "user_id": "user-uuid", "current_step": "user_registered", "steps": [ { "step_name": "user_registered", "completed": true, "completed_at": "2025-10-15T10:30:00Z", "data": { "subscription_id": "sub-uuid", "subscription_plan": "professional", "billing_cycle": "yearly", "coupon_code": "PILOT2025", "payment_method_id": "pm-123", "payment_customer_id": "cus-456", "status": "pending_tenant_linking", "created_at": "2025-10-15T10:30:00Z" } } ] } ``` ## Error Handling & Recovery ### Error Scenarios and Recovery Strategies 1. **Payment Processing Failure** - **Scenario**: Payment fails during registration - **Recovery**: Rollback user creation, show error to user, allow retry - **Implementation**: Transaction management in auth service 2. **Subscription Creation Failure** - **Scenario**: Subscription creation fails after user creation - **Recovery**: User created but marked as "registration_incomplete", allow retry in onboarding - **Implementation**: Store registration state, provide recovery endpoint 3. **Tenant Linking Failure** - **Scenario**: Tenant creation succeeds but subscription linking fails - **Recovery**: Tenant created with default trial subscription, manual linking available - **Implementation**: Fallback to current behavior, admin notification 4. **Orphaned Subscriptions** - **Scenario**: User registers but never completes onboarding - **Recovery**: Cleanup task to cancel subscriptions after 30 days - **Implementation**: Background job to monitor pending subscriptions ### Monitoring and Alerts ```python # Subscription linking monitoring class SubscriptionMonitoringService: async def monitor_pending_subscriptions(self): """Monitor subscriptions pending tenant linking""" pending_subscriptions = await self.subscription_repo.get_pending_tenant_linking() for subscription in pending_subscriptions: created_days_ago = (datetime.now(timezone.utc) - subscription.created_at).days if created_days_ago > 30: # Cancel subscription and notify user await self.cancel_orphaned_subscription(subscription.id) await self.notify_user_about_cancellation(subscription.user_id) elif created_days_ago > 7: # Send reminder to complete onboarding await self.send_onboarding_reminder(subscription.user_id) ``` ## Migration Strategy ### Phase 1: Backend Implementation 1. **Database Migration**: Add new columns to subscriptions table 2. **Auth Service Updates**: Implement new registration endpoint 3. **Tenant Service Updates**: Implement tenant-independent subscription creation 4. **Shared Clients**: Update inter-service communication ### Phase 2: Frontend Implementation 1. **Registration Form**: Update to collect billing address 2. **Payment Flow**: Integrate with new backend endpoints 3. **Onboarding Flow**: Add subscription linking logic ### Phase 3: Testing and Validation 1. **Unit Tests**: Verify individual component behavior 2. **Integration Tests**: Test service-to-service communication 3. **End-to-End Tests**: Validate complete user journey 4. **Load Testing**: Ensure performance under load ### Phase 4: Deployment and Rollout 1. **Feature Flags**: Enable gradual rollout 2. **A/B Testing**: Compare with existing flow 3. **Monitoring**: Track key metrics and errors 4. **Rollback Plan**: Prepare for quick rollback if needed ## Benefits of the New Architecture ### 1. Improved User Experience - **Clear Separation of Concerns**: Users understand each step of the process - **Progressive Commitment**: Users can complete registration without immediate tenant creation - **Flexible Onboarding**: Users can explore the platform before committing to a specific bakery ### 2. Better Error Handling - **Isolated Failure Points**: Failures in one step don't cascade to others - **Recovery Paths**: Clear recovery mechanisms for each failure scenario - **Graceful Degradation**: System remains functional even with partial failures ### 3. Enhanced Business Flexibility - **Multi-Tenant Support**: Users can create multiple tenants with the same subscription - **Subscription Portability**: Subscriptions can be moved between tenants - **Trial Management**: Better control over trial periods and conversions ### 4. Improved Security - **Data Isolation**: Sensitive payment data handled separately from user data - **Audit Trails**: Clear tracking of subscription lifecycle - **Compliance**: Better support for GDPR and payment industry standards ### 5. Scalability - **Microservice Alignment**: Better separation between auth and tenant services - **Independent Scaling**: Services can be scaled independently - **Future Extensibility**: Easier to add new features and integrations ## Implementation Timeline | Phase | Duration | Key Activities | |-------|----------|----------------| | 1. Analysis & Design | 2 weeks | Architecture review, technical design, stakeholder approval | | 2. Backend Implementation | 4 weeks | Database changes, service updates, API development | | 3. Frontend Implementation | 3 weeks | Form updates, payment integration, onboarding changes | | 4. Testing & QA | 3 weeks | Unit tests, integration tests, E2E tests, performance testing | | 5. Deployment & Rollout | 2 weeks | Staging deployment, production rollout, monitoring setup | | 6. Post-Launch | Ongoing | Bug fixes, performance optimization, feature enhancements | ## Risks and Mitigation ### Technical Risks 1. **Data Consistency**: Risk of inconsistent state between services - *Mitigation*: Strong transaction management, idempotent operations, reconciliation jobs 2. **Performance Impact**: Additional service calls may impact performance - *Mitigation*: Caching, async processing, performance optimization 3. **Complexity Increase**: More moving parts increase system complexity - *Mitigation*: Clear documentation, comprehensive monitoring, gradual rollout ### Business Risks 1. **User Confusion**: Multi-step process may confuse some users - *Mitigation*: Clear UI guidance, progress indicators, help documentation 2. **Conversion Impact**: Additional steps may reduce conversion rates - *Mitigation*: A/B testing, user feedback, iterative improvements 3. **Support Burden**: New flow may require additional support - *Mitigation*: Comprehensive documentation, self-service recovery, support training ## Success Metrics ### Key Performance Indicators 1. **Registration Completion Rate**: Percentage of users completing registration 2. **Onboarding Completion Rate**: Percentage of users completing onboarding 3. **Error Rates**: Frequency of errors in each step 4. **Conversion Rates**: Percentage of visitors becoming paying customers 5. **User Satisfaction**: Feedback and ratings from users ### Monitoring Dashboard ``` Registration Funnel: - Step 1 (Basic Info): 100% - Step 2 (Plan Selection): 85% - Step 3 (Payment): 75% - Onboarding Completion: 60% Error Metrics: - Registration Errors: < 1% - Payment Errors: < 2% - Subscription Linking Errors: < 0.5% Performance Metrics: - Registration Time: < 5s - Payment Processing Time: < 3s - Tenant Creation Time: < 2s ``` ## Conclusion This rearchitecture proposal addresses the current limitations by implementing a clear separation between user registration, payment processing, and tenant creation. The new multi-phase approach provides better user experience, improved error handling, and enhanced business flexibility while maintaining backward compatibility and providing clear migration paths. The proposed solution aligns with modern microservice architectures and provides a solid foundation for future growth and feature enhancements.