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() { const email = process.env.SMS_AERO_EMAIL const apiKey = process.env.SMS_AERO_API_KEY this.isDevelopment = process.env.NODE_ENV === 'development' || process.env.SMS_DEV_MODE === 'true' if (!this.isDevelopment && (!email || !apiKey)) { console.warn('⚠️ SMS Aero credentials not configured. SMS sending will be disabled.') } this.email = email || '' this.apiKey = apiKey || '' } 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.warn(`Development mode: SMS code ${code} for phone ${formattedPhone}`) return { success: true, message: 'SMS код отправлен успешно (режим разработки)', } } // Проверяем наличие учетных данных перед отправкой if (!this.email || !this.apiKey) { console.warn('SMS Aero credentials not configured, SMS not sent') return { success: true, message: 'SMS код сохранен (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.warn('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: 'Ошибка при проверке кода', } } } }