import axios from 'axios' import { prisma } from '@/lib/prisma' export interface SmsResponse { success: boolean message: string } export interface SmsVerificationResponse { success: boolean message: string } export class SmsService { private email: string private apiKey: string private isDevelopment: boolean constructor() { this.email = process.env.SMS_AERO_EMAIL! this.apiKey = process.env.SMS_AERO_API_KEY! this.isDevelopment = process.env.NODE_ENV === 'development' || process.env.SMS_DEV_MODE === 'true' if (!this.isDevelopment && (!this.email || !this.apiKey)) { throw new Error('SMS Aero credentials not configured') } } private generateSmsCode(): string { if (this.isDevelopment) { return '1234' } return Math.floor(1000 + Math.random() * 9000).toString() } private validatePhoneNumber(phone: string): boolean { const phoneRegex = /^7\d{10}$/ return phoneRegex.test(phone) } private formatPhoneNumber(phone: string): string { // Убираем все символы кроме цифр const cleanPhone = phone.replace(/\D/g, '') // Если номер начинается с 8, заменяем на 7 if (cleanPhone.startsWith('8')) { return '7' + cleanPhone.slice(1) } // Если номер начинается с +7, убираем + if (cleanPhone.startsWith('7')) { return cleanPhone } // Если номер без кода страны, добавляем 7 if (cleanPhone.length === 10) { return '7' + cleanPhone } return cleanPhone } async sendSmsCode(phone: string): Promise { try { const formattedPhone = this.formatPhoneNumber(phone) if (!this.validatePhoneNumber(formattedPhone)) { return { success: false, message: 'Неверный формат номера телефона' } } const code = this.generateSmsCode() const expiresAt = new Date(Date.now() + 5 * 60 * 1000) // 5 минут // Удаляем старые коды для этого номера await prisma.smsCode.deleteMany({ where: { phone: formattedPhone } }) // Сохраняем код в базе данных await prisma.smsCode.create({ data: { code, phone: formattedPhone, expiresAt, attempts: 0, maxAttempts: 3 } }) // В режиме разработки не отправляем SMS if (this.isDevelopment) { console.log(`Development mode: SMS code ${code} for phone ${formattedPhone}`) return { success: true, message: 'SMS код отправлен успешно (режим разработки)' } } // Отправляем SMS через SMS Aero API с HTTP Basic Auth const response = await axios.get( `https://gate.smsaero.ru/v2/sms/send`, { params: { number: formattedPhone, text: `Код подтверждения SferaV: ${code}`, sign: 'SMS Aero' }, auth: { username: this.email, password: this.apiKey }, headers: { 'Accept': 'application/json' } } ) console.log('SMS Aero response:', response.data) if (response.data.success) { return { success: true, message: 'SMS код отправлен успешно' } } else { console.error('SMS Aero API error:', response.data) return { success: false, message: response.data.message || 'Ошибка при отправке SMS' } } } catch (error: unknown) { console.error('Error sending SMS:', error) // Детальная информация об ошибке if (axios.isAxiosError(error)) { console.error('Response status:', error.response?.status) console.error('Response data:', error.response?.data) if (error.response?.status === 401) { return { success: false, message: 'Ошибка авторизации SMS API. Проверьте настройки.' } } } return { success: false, message: 'Ошибка при отправке SMS' } } } async verifySmsCode(phone: string, code: string): Promise { try { const formattedPhone = this.formatPhoneNumber(phone) if (!this.validatePhoneNumber(formattedPhone)) { return { success: false, message: 'Неверный формат номера телефона' } } // Ищем активный код для этого номера const smsCode = await prisma.smsCode.findFirst({ where: { phone: formattedPhone, isUsed: false, expiresAt: { gte: new Date() } }, orderBy: { createdAt: 'desc' } }) if (!smsCode) { return { success: false, message: 'Код не найден или истек' } } // Проверяем количество попыток if (smsCode.attempts >= smsCode.maxAttempts) { // Помечаем код как использованный при превышении лимита попыток await prisma.smsCode.update({ where: { id: smsCode.id }, data: { isUsed: true } }) return { success: false, message: 'Превышено количество попыток ввода кода' } } // Проверяем правильность кода if (smsCode.code !== code) { // Увеличиваем счетчик попыток при неправильном коде await prisma.smsCode.update({ where: { id: smsCode.id }, data: { attempts: smsCode.attempts + 1 } }) const remainingAttempts = smsCode.maxAttempts - smsCode.attempts - 1 return { success: false, message: remainingAttempts > 0 ? `Неверный код. Осталось попыток: ${remainingAttempts}` : 'Неверный код. Превышено количество попыток' } } // Код правильный - помечаем как использованный await prisma.smsCode.update({ where: { id: smsCode.id }, data: { isUsed: true } }) return { success: true, message: 'Код подтвержден успешно' } } catch (error) { console.error('Error verifying SMS code:', error) return { success: false, message: 'Ошибка при проверке кода' } } } }