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<PurchaseStep, 'completed' | 'current' | 'error'>[] = [
{
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 };
}
}