import { PricingCalculation } from './PricingService'; export interface PurchaseStep { id: string; title: string; description: string; completed: boolean; current: boolean; error?: string; } export interface PurchaseFlowState { currentStep: number; steps: PurchaseStep[]; calculation: PricingCalculation | null; paymentIntentId: string | null; clientSecret: string | null; error: any; isProcessing: boolean; } export class PurchaseFlowService { private static readonly STEPS: Omit[] = [ { id: 'quantity-selection', title: 'Select Tokens', description: 'Choose the number of tokens you want to purchase' }, { id: 'pricing-calculation', title: 'Calculate Price', description: 'Review pricing and available discounts' }, { id: 'payment-method', title: 'Payment Method', description: 'Select your preferred payment method' }, { id: 'payment-processing', title: 'Process Payment', description: 'Complete your payment securely' }, { id: 'confirmation', title: 'Confirmation', description: 'Payment successful and tokens allocated' } ]; /** * Initialize the purchase flow */ static initializeFlow(): PurchaseFlowState { const steps = this.STEPS.map((step, index) => ({ ...step, completed: false, current: index === 0, error: undefined })); return { currentStep: 0, steps, calculation: null, paymentIntentId: null, clientSecret: null, error: null, isProcessing: false }; } /** * Move to the next step */ static nextStep(state: PurchaseFlowState): PurchaseFlowState { const newState = { ...state }; if (newState.currentStep < newState.steps.length - 1) { // Mark current step as completed newState.steps[newState.currentStep] = { ...newState.steps[newState.currentStep], completed: true, current: false }; // Move to next step newState.currentStep += 1; newState.steps[newState.currentStep] = { ...newState.steps[newState.currentStep], current: true }; } return newState; } /** * Move to the previous step */ static previousStep(state: PurchaseFlowState): PurchaseFlowState { const newState = { ...state }; if (newState.currentStep > 0) { // Mark current step as not current newState.steps[newState.currentStep] = { ...newState.steps[newState.currentStep], current: false }; // Move to previous step newState.currentStep -= 1; newState.steps[newState.currentStep] = { ...newState.steps[newState.currentStep], current: true, completed: false // Allow editing previous step }; } return newState; } /** * Jump to a specific step */ static goToStep(state: PurchaseFlowState, stepIndex: number): PurchaseFlowState { const newState = { ...state }; if (stepIndex >= 0 && stepIndex < newState.steps.length) { // Reset all steps newState.steps = newState.steps.map((step, index) => ({ ...step, completed: index < stepIndex, current: index === stepIndex, error: undefined })); newState.currentStep = stepIndex; } return newState; } /** * Set error for current step */ static setStepError(state: PurchaseFlowState, error: any): PurchaseFlowState { const newState = { ...state }; newState.steps[newState.currentStep] = { ...newState.steps[newState.currentStep], error: error?.message || 'An error occurred' }; newState.error = error; return newState; } /** * Clear error for current step */ static clearStepError(state: PurchaseFlowState): PurchaseFlowState { const newState = { ...state }; newState.steps[newState.currentStep] = { ...newState.steps[newState.currentStep], error: undefined }; newState.error = null; return newState; } /** * Set processing state */ static setProcessing(state: PurchaseFlowState, isProcessing: boolean): PurchaseFlowState { return { ...state, isProcessing }; } /** * Set calculation data */ static setCalculation(state: PurchaseFlowState, calculation: PricingCalculation): PurchaseFlowState { return { ...state, calculation }; } /** * Set payment intent data */ static setPaymentIntent(state: PurchaseFlowState, paymentIntentId: string, clientSecret: string): PurchaseFlowState { return { ...state, paymentIntentId, clientSecret }; } /** * Complete the purchase flow */ static completeFlow(state: PurchaseFlowState): PurchaseFlowState { const newState = { ...state }; // Mark all steps as completed newState.steps = newState.steps.map(step => ({ ...step, completed: true, current: false })); newState.isProcessing = false; newState.error = null; return newState; } /** * Reset the flow to initial state */ static resetFlow(): PurchaseFlowState { return this.initializeFlow(); } /** * Get current step info */ static getCurrentStep(state: PurchaseFlowState): PurchaseStep | null { return state.steps[state.currentStep] || null; } /** * Check if flow can proceed to next step */ static canProceed(state: PurchaseFlowState): boolean { const currentStep = this.getCurrentStep(state); if (!currentStep) return false; switch (currentStep.id) { case 'quantity-selection': return state.calculation !== null; case 'pricing-calculation': return state.calculation !== null; case 'payment-method': return true; // Payment method selection is always valid case 'payment-processing': return state.clientSecret !== null; case 'confirmation': return true; // Confirmation step is always valid default: return false; } } /** * Get progress percentage */ static getProgress(state: PurchaseFlowState): number { const completedSteps = state.steps.filter(step => step.completed).length; return (completedSteps / state.steps.length) * 100; } /** * Check if flow is completed */ static isCompleted(state: PurchaseFlowState): boolean { return state.steps.every(step => step.completed); } /** * Get step by ID */ static getStepById(state: PurchaseFlowState, stepId: string): PurchaseStep | null { return state.steps.find(step => step.id === stepId) || null; } /** * Validate current step */ static validateCurrentStep(state: PurchaseFlowState): { isValid: boolean; error?: string; } { const currentStep = this.getCurrentStep(state); if (!currentStep) { return { isValid: false, error: 'Invalid step' }; } switch (currentStep.id) { case 'quantity-selection': if (!state.calculation) { return { isValid: false, error: 'Please select a quantity' }; } break; case 'pricing-calculation': if (!state.calculation) { return { isValid: false, error: 'Please calculate pricing first' }; } break; case 'payment-method': // Payment method selection is always valid break; case 'payment-processing': if (!state.clientSecret) { return { isValid: false, error: 'Payment intent not created' }; } break; case 'confirmation': // Confirmation step is always valid break; } return { isValid: true }; } }