2026-01-13 14:25:44 +01:00

172 lines
4.7 KiB
TypeScript

import { apiService } from '@/services/api';
import { DEFAULT_MERCHANT, MERCHANT_IDS, type TMerchant } from '@/config/api';
import type {
TCashierFlowState,
IApiCurrency,
IPaymentRequest,
IFormMetadataField,
} from '../types';
import type { IPaymentMethod } from '@/features/payment-methods/types';
import { getCashierConfig } from '@/config/cashierConfig';
type StateChangeListener = (state: TCashierFlowState) => void;
class CashierService {
private state: TCashierFlowState = 'loading';
private methods: IPaymentMethod[] = [];
private currencies: IApiCurrency[] = [];
private selectedMethod: IPaymentMethod | null = null;
private formMetadata: IFormMetadataField[] = [];
private error: Error | null = null;
private paymentUrl: string | null = null;
private listeners: Set<StateChangeListener> = new Set();
getState(): TCashierFlowState {
return this.state;
}
getMethods(): IPaymentMethod[] {
return this.methods;
}
getCurrencies(): IApiCurrency[] {
return this.currencies;
}
getError(): Error | null {
return this.error;
}
getPaymentUrl(): string | null {
return this.paymentUrl;
}
getSelectedMethod(): IPaymentMethod | null {
return this.selectedMethod;
}
getFormMetadata(): IFormMetadataField[] {
return this.formMetadata;
}
async setSelectedMethod(
method: IPaymentMethod | null,
merchant: TMerchant = DEFAULT_MERCHANT
): Promise<void> {
this.selectedMethod = method;
this.formMetadata = [];
if (method) {
try {
const config = getCashierConfig();
console.log('config', config);
const paymentType = config.paymentType || 'deposit';
// Fetch form metadata and currencies for the selected method in parallel
const [formMetadataData, currenciesData] = await Promise.all([
apiService.getFormMetadata(method.code, paymentType, merchant),
apiService.getCurrencies(merchant, method.code),
]);
this.formMetadata = formMetadataData;
this.currencies = currenciesData;
} catch (err) {
// If form metadata or currencies fetch fails, we still allow method selection
// Error can be handled by the UI if needed
console.error('Failed to fetch form metadata or currencies:', err);
}
} else {
// Reset currencies when method is deselected
// Optionally reload all currencies or keep empty
this.currencies = [];
}
this.notifyListeners();
}
private setState(newState: TCashierFlowState): void {
if (this.state !== newState) {
this.state = newState;
this.notifyListeners();
}
}
subscribe(listener: StateChangeListener): () => void {
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
}
private notifyListeners(): void {
this.listeners.forEach(listener => listener(this.state));
}
async loadData(merchant: TMerchant = DEFAULT_MERCHANT): Promise<void> {
try {
this.setState('loading');
this.error = null;
// Only fetch methods, currencies will be fetched when a method is selected
const methodsData = await apiService.getMethods(merchant);
this.methods = methodsData;
this.currencies = []; // Currencies will be loaded when method is selected
this.setState('ready');
} catch (err) {
this.error = err instanceof Error ? err : new Error('Failed to load cashier data');
this.setState('error');
throw this.error;
}
}
async initiatePayment(
paymentRequest: Omit<IPaymentRequest, 'merchant_id'>,
merchant: TMerchant = DEFAULT_MERCHANT
): Promise<void> {
try {
this.setState('submitting');
this.error = null;
const fullRequest = {
...paymentRequest,
merchant_id: MERCHANT_IDS[merchant],
} as IPaymentRequest;
const response = await apiService.initiatePayment(fullRequest, merchant);
this.paymentUrl = response.redirect_url;
this.setState('redirecting');
} catch (err) {
this.error = err instanceof Error ? err : new Error('Failed to initiate payment');
this.setState('error');
throw this.error;
}
}
reset(): void {
this.state = 'loading';
this.selectedMethod = null;
this.formMetadata = [];
this.currencies = [];
this.error = null;
this.paymentUrl = null;
this.notifyListeners();
}
goBack(): void {
this.selectedMethod = null;
this.formMetadata = [];
this.currencies = [];
this.error = null;
// Reset to ready state if we have methods available, otherwise keep current state
if (this.methods.length > 0) {
this.setState('ready');
} else {
this.notifyListeners();
}
}
}
export const cashierService = new CashierService();