|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async createPurchase(purchaseData: Omit<PurchaseRecord, 'id' | 'createdAt' | 'updatedAt'>): Promise<PurchaseRecord> { |
|
|
try { |
|
|
|
|
|
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}`); |
|
|
} |
|
|
|
|
|
|
|
|
return snakeToCamel(data[0]) as PurchaseRecord; |
|
|
} catch (error: any) { |
|
|
logger.error(`Error in createPurchase: ${error.message}`); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
} |