import { createLogger } from '../../utils/logger'; import { supabase } from '../../db/supabase'; import { camelToSnake, snakeToCamel } from '../../utils'; const logger = createLogger('PurchaseTrackingService'); export enum PurchaseState { PENDING = 'pending', SUCCESS = 'success', FAILED = 'failed', CANCELED = 'canceled', TIMEOUT = 'timeout' } export interface PurchaseRecord { id?: number; userId: number; telegramId: number; service: string; countryId: string; operator: string; phoneNumber?: string; orderId?: string; cost: number; state: PurchaseState; verificationCode?: string; verificationMessage?: string; createdAt?: string; updatedAt?: string; } export class PurchaseTrackingService { private static instance: PurchaseTrackingService; private constructor() {} public static getInstance(): PurchaseTrackingService { if (!PurchaseTrackingService.instance) { PurchaseTrackingService.instance = new PurchaseTrackingService(); } return PurchaseTrackingService.instance; } /** * Create a new purchase record */ async createPurchase(purchaseData: Omit): Promise { try { // Convert camelCase to snake_case for database const snakeData = camelToSnake(purchaseData); const { data, error } = await supabase .from('purchases') .insert(snakeData) .select(); if (error) { logger.error(`Error creating purchase record: ${error.message}`); throw new Error(`Failed to create purchase record: ${error.message}`); } // Convert snake_case back to camelCase return snakeToCamel(data[0]) as PurchaseRecord; } catch (error: any) { logger.error(`Error in createPurchase: ${error.message}`); throw error; } } /** * Update purchase state and related information */ async updatePurchaseState( orderId: string, state: PurchaseState, additionalData: Partial = {} ): Promise { try { const updateData = camelToSnake({ state, updatedAt: new Date().toISOString(), ...additionalData }); const { data, error } = await supabase .from('purchases') .update(updateData) .eq('order_id', orderId) .select(); if (error) { logger.error(`Error updating purchase state: ${error.message}`); throw new Error(`Failed to update purchase state: ${error.message}`); } return data && data.length > 0 ? snakeToCamel(data[0]) as PurchaseRecord : {} as PurchaseRecord; } catch (error: any) { logger.error(`Error in updatePurchaseState: ${error.message}`); throw error; } } /** * Get purchase by order ID */ async getPurchaseByOrderId(orderId: string): Promise { try { const { data, error } = await supabase .from('purchases') .select('*') .eq('order_id', orderId) .limit(1); if (error) { logger.error(`Error fetching purchase: ${error.message}`); throw new Error(`Failed to fetch purchase: ${error.message}`); } return data && data.length > 0 ? snakeToCamel(data[0]) as PurchaseRecord : null; } catch (error: any) { logger.error(`Error in getPurchaseByOrderId: ${error.message}`); throw error; } } /** * Get user's purchase history */ async getUserPurchases(telegramId: number, limit: number = 10, offset: number = 0): Promise { try { const { data, error } = await supabase .from('purchases') .select('*') .eq('telegram_id', telegramId) .order('created_at', { ascending: false }) .range(offset, offset + limit - 1); if (error) { logger.error(`Error fetching user purchases: ${error.message}`); throw new Error(`Failed to fetch user purchases: ${error.message}`); } return data ? data.map(record => snakeToCamel(record) as PurchaseRecord) : []; } catch (error: any) { logger.error(`Error in getUserPurchases: ${error.message}`); throw error; } } /** * Get user's purchase history filtered by state */ async getUserPurchasesByState(telegramId: number, state: PurchaseState, limit: number = 10, offset: number = 0): Promise { try { const { data, error } = await supabase .from('purchases') .select('*') .eq('telegram_id', telegramId) .eq('state', state) .order('created_at', { ascending: false }) .range(offset, offset + limit - 1); if (error) { logger.error(`Error fetching user purchases by state: ${error.message}`); throw new Error(`Failed to fetch user purchases by state: ${error.message}`); } return data ? data.map(record => snakeToCamel(record) as PurchaseRecord) : []; } catch (error: any) { logger.error(`Error in getUserPurchasesByState: ${error.message}`); throw error; } } }