Spaces:
Paused
Paused
| 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<PurchaseRecord, 'id' | 'createdAt' | 'updatedAt'>): Promise<PurchaseRecord> { | |
| 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<PurchaseRecord> = {} | |
| ): Promise<PurchaseRecord> { | |
| 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<PurchaseRecord | null> { | |
| 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<PurchaseRecord[]> { | |
| 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<PurchaseRecord[]> { | |
| 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; | |
| } | |
| } | |
| } |