Files
sfera-new/src/services/sms-service.ts

243 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<SmsResponse> {
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<SmsVerificationResponse> {
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: 'Ошибка при проверке кода'
}
}
}
}