feat: SMS verification через n8n webhook

- Перенесена проверка SMS кода в n8n webhook (N8N_SMS_VERIFY_WEBHOOK)
- Упрощен формат ответа: убран токен, только success/message
- sms-verify.php теперь проксирует запросы на n8n
- Обновлен JS код: убрано использование токена
- Обновлена документация с упрощенным форматом ответа
- Протестировано: верный и неверный коды работают корректно
This commit is contained in:
Fedor
2026-01-15 18:11:18 +03:00
parent 2c516362df
commit ed4270312e
6 changed files with 918 additions and 96 deletions

261
docs/n8n_migration_plan.md Normal file
View File

@@ -0,0 +1,261 @@
# План миграции SMS верификации в n8n
## Текущая логика (PHP)
### 1. Отправка SMS (`sms-verify.php?action=send`)
```
┌─────────────┐
│ Frontend │
│ (JS) │
└──────┬──────┘
│ POST: phonenumber
┌─────────────────────┐
│ sms-verify.php │
│ action=send │
└──────┬──────────────┘
├─► Нормализация номера (clear_phone)
├─► Проверка rate limit (Redis)
├─► Генерация кода (generateCode)
├─► Сохранение в Redis (setex, 10 мин)
├─► Отправка SMS → n8n webhook
└─► Ответ: success
```
### 2. Проверка кода (`sms-verify.php?action=verify`)
```
┌─────────────┐
│ Frontend │
│ (JS) │
└──────┬──────┘
│ POST: phonenumber, code
┌─────────────────────┐
│ sms-verify.php │
│ action=verify │
└──────┬──────────────┘
├─► Нормализация номера
├─► Проверка rate limit попыток
├─► Чтение кода из Redis
├─► Сравнение кодов
├─► Удаление кода из Redis
├─► Создание токена верификации
└─► Ответ: success, token
```
---
## Новая логика (n8n)
### Workflow 1: Отправка SMS
```
┌─────────────────┐
│ Webhook │ POST /webhook/sms-send
│ Trigger │ Body: { phonenumber: "+79262306381" }
└────────┬────────┘
┌─────────────────┐
│ Function │ Нормализация номера
│ (JS Code) │ phone.replace(/[() -+]/g, '').replace(/^(\+?7|8)/, '')
└────────┬────────┘
┌─────────────────┐
│ Redis │ GET sms:ratelimit:send:9262306381
│ (Get) │ Если >= 5 → ошибка
└────────┬────────┘
┌─────────────────┐
│ Function │ Генерация кода
│ (JS Code) │ Math.floor(100000 + Math.random() * 900000)
└────────┬────────┘
┌─────────────────┐
│ Redis │ SETEX sms:code:9262306381 600 "106574"
│ (Set) │
└────────┬────────┘
┌─────────────────┐
│ Redis │ INCR sms:ratelimit:send:9262306381
│ (Increment) │ EXPIRE 3600
└────────┬────────┘
┌─────────────────┐
│ HTTP Request │ POST к SMS API
│ (SMS Send) │ Body: { phone, text: "Код: 106574" }
└────────┬────────┘
┌─────────────────┐
│ IF (error) │ Если ошибка → удалить код из Redis
│ (Error Handle) │
└────────┬────────┘
┌─────────────────┐
│ Respond to │ { success: true, message: "..." }
│ Webhook │
└─────────────────┘
```
### Workflow 2: Проверка кода
```
┌─────────────────┐
│ Webhook │ POST /webhook/sms-verify
│ Trigger │ Body: { phonenumber: "+79262306381", code: "106574" }
└────────┬────────┘
┌─────────────────┐
│ Function │ Нормализация номера
│ (JS Code) │
└────────┬────────┘
┌─────────────────┐
│ Redis │ GET sms:ratelimit:attempts:9262306381
│ (Get) │ Если >= 10 → ошибка
└────────┬────────┘
┌─────────────────┐
│ Redis │ INCR sms:ratelimit:attempts:9262306381
│ (Increment) │ EXPIRE 900
└────────┬────────┘
┌─────────────────┐
│ Redis │ GET sms:code:9262306381
│ (Get) │ Если null → ошибка "Код не найден"
└────────┬────────┘
┌─────────────────┐
│ IF │ Если code !== stored_code → ошибка "Неверный код"
│ (Compare) │
└────────┬────────┘
│ (success)
┌─────────────────┐
│ Redis │ DEL sms:code:9262306381
│ (Delete) │ DEL sms:ratelimit:attempts:9262306381
└────────┬────────┘
┌─────────────────┐
│ Function │ Генерация токена
│ (JS Code) │ crypto.randomBytes(32).toString('hex')
└────────┬────────┘
┌─────────────────┐
│ Redis │ SETEX sms:verified:9262306381 3600 "token"
│ (Set) │
└────────┬────────┘
┌─────────────────┐
│ Respond to │ { success: true, token: "..." }
│ Webhook │
└─────────────────┘
```
---
## Пошаговая миграция
### Шаг 1: Создать workflows в n8n
1. Зайти в n8n: https://n8n.clientright.pro
2. Создать новый workflow "SMS Send"
3. Создать новый workflow "SMS Verify"
4. Создать новый workflow "SMS Check Verified" (опционально)
### Шаг 2: Настроить Redis в n8n
- Добавить Redis credentials в n8n
- Host: `crm.clientright.ru`
- Port: `6379`
- Password: (из .env)
### Шаг 3: Протестировать workflows
- Запустить тестовые запросы через Postman/curl
- Проверить, что коды сохраняются в Redis
- Проверить, что SMS отправляются
### Шаг 4: Изменить PHP код
**Вариант A: Прокси через PHP (проще)**
```php
// sms-verify.php просто перенаправляет на n8n
$n8n_url = 'https://n8n.clientright.pro/webhook/sms-send';
// ... curl запрос к n8n
```
**Вариант B: Прямой вызов n8n из JS (лучше)**
```javascript
// js/common.js
$.ajax({
url: 'https://n8n.clientright.pro/webhook/sms-send',
// ...
});
```
### Шаг 5: Удалить старую логику из PHP
- Удалить генерацию кода
- Удалить работу с Redis (оставить только fallback на файлы, если нужно)
- Оставить только проксирование запросов
---
## Преимущества
**Визуализация** - видно весь процесс в n8n
**Логирование** - автоматические логи каждого шага
**Мониторинг** - видно ошибки и задержки
**Гибкость** - легко добавить новые шаги
**Тестирование** - можно тестировать каждый шаг отдельно
**Масштабируемость** - легко добавить несколько SMS провайдеров
---
## Примеры кода для n8n
### Нормализация номера (Function Node)
```javascript
const phone = $input.item.json.phonenumber || '';
const cleaned = phone
.replace(/[() -]/g, '')
.replace(/^(\+?7|8)/, '');
return { json: { phone_cleaned: cleaned } };
```
### Генерация кода (Function Node)
```javascript
const code = Math.floor(100000 + Math.random() * 900000).toString();
return { json: { code } };
```
### Генерация токена (Function Node)
```javascript
const crypto = require('crypto');
const token = crypto.randomBytes(32).toString('hex');
return { json: { token } };
```

201
docs/n8n_redis_example.md Normal file
View File

@@ -0,0 +1,201 @@
# Пример использования Redis в n8n через Webhook
## Как это работает
```
Webhook (точка входа) → Redis Node (чтение/запись) → Ответ
```
## Workflow: Проверка SMS кода
### Структура:
```
1. Webhook Trigger
2. Function Node (нормализация номера)
3. Redis Node (GET) - читаем код из Redis
4. Function Node (сравнение кодов)
5. Redis Node (DEL) - удаляем код после проверки
6. Respond to Webhook
```
### Детализация:
#### Шаг 1: Webhook Trigger
- **URL:** `/webhook/sms-verify`
- **Method:** POST
- **Body:**
```json
{
"phonenumber": "+79262306381",
"code": "106574"
}
```
#### Шаг 2: Function Node - Нормализация
```javascript
// Код для Function Node
const phone = $input.item.json.phonenumber || '';
const cleaned = phone
.replace(/[() -]/g, '')
.replace(/^(\+?7|8)/, '');
return {
json: {
phone_cleaned: cleaned,
code: $input.item.json.code
}
};
```
#### Шаг 3: Redis Node - Чтение кода
- **Operation:** Get
- **Key:** `sms:code:{{ $json.phone_cleaned }}`
- **Результат:**
```json
{
"phone_cleaned": "9262306381",
"code": "106574",
"stored_code": "106574" // из Redis
}
```
#### Шаг 4: Function Node - Сравнение
```javascript
// Код для Function Node
const inputCode = $input.item.json.code;
const storedCode = $input.item.json.stored_code;
if (!storedCode) {
return {
json: {
success: false,
message: "Код не найден или истек"
}
};
}
if (inputCode !== storedCode) {
return {
json: {
success: false,
message: "Неверный код"
}
};
}
// Код верный - продолжаем
return {
json: {
success: true,
phone_cleaned: $input.item.json.phone_cleaned
}
};
```
#### Шаг 5: Redis Node - Удаление кода
- **Operation:** Delete
- **Key:** `sms:code:{{ $json.phone_cleaned }}`
#### Шаг 6: Redis Node - Создание токена
- **Operation:** Set
- **Key:** `sms:verified:{{ $json.phone_cleaned }}`
- **Value:** `{{ $json.token }}` (генерируется в Function Node)
- **TTL:** 3600 секунд
#### Шаг 7: Respond to Webhook
```json
{
"success": true,
"message": "Код подтвержден",
"token": "abc123..."
}
```
---
## Workflow: Отправка SMS кода
### Структура:
```
1. Webhook Trigger
2. Function Node (нормализация)
3. Redis Node (GET) - проверка rate limit
4. Function Node (генерация кода)
5. Redis Node (SETEX) - сохранение кода
6. HTTP Request - отправка SMS
7. Respond to Webhook
```
### Детализация:
#### Шаг 3: Redis Node - Проверка rate limit
- **Operation:** Get
- **Key:** `sms:ratelimit:send:{{ $json.phone_cleaned }}`
- **Если значение >= 5** → ошибка через IF Node
#### Шаг 5: Redis Node - Сохранение кода
- **Operation:** Set with Expiration
- **Key:** `sms:code:{{ $json.phone_cleaned }}`
- **Value:** `{{ $json.code }}`
- **TTL:** 600 секунд (10 минут)
#### Шаг 6: HTTP Request - Отправка SMS
- **Method:** POST
- **URL:** (SMS API провайдера)
- **Body:**
```json
{
"phone": "{{ $json.phone_cleaned }}",
"text": "Код подтверждения: {{ $json.code }}"
}
```
---
## Настройка Redis в n8n
### Credentials:
1. Зайти в n8n → Credentials → New
2. Выбрать "Redis"
3. Заполнить:
- **Host:** `crm.clientright.ru`
- **Port:** `6379`
- **Password:** (из .env или пусто)
- **Database:** `0` (по умолчанию)
### Использование в Node:
1. Добавить **Redis Node** в workflow
2. Выбрать созданные credentials
3. Выбрать операцию (Get, Set, Set with Expiration, Delete, etc.)
4. Указать ключ (можно использовать переменные: `sms:code:{{ $json.phone_cleaned }}`)
---
## Преимущества
**Всё в одном месте** - webhook принимает запрос, Redis Node читает/пишет
**Визуализация** - видно весь процесс на графике
**Логирование** - n8n автоматически логирует все операции
**Обработка ошибок** - можно добавить IF Nodes для проверок
**Тестирование** - можно тестировать каждый шаг отдельно
---
## Пример полного workflow (JSON для импорта в n8n)
Можно создать workflow вручную или импортировать готовый JSON (если нужно, могу создать).

150
docs/n8n_sms_workflow.md Normal file
View File

@@ -0,0 +1,150 @@
# N8N Workflow для SMS верификации
## Текущая архитектура
```
Frontend (JS) → PHP (sms-verify.php) → Redis + n8n (только отправка SMS)
```
## Предлагаемая архитектура
```
Frontend (JS) → n8n Webhook → Redis + SMS отправка (всё в n8n)
```
## Workflow 1: Отправка SMS кода
**Webhook URL:** `https://n8n.clientright.pro/webhook/sms-send`
### Шаги:
1. **Webhook Trigger** (POST)
- Принимает: `{ "phonenumber": "+79262306381" }`
2. **Нормализация номера телефона**
- Убрать пробелы, скобки, дефисы
- Убрать +7 или 8 в начале
- Результат: `9262306381`
3. **Проверка Rate Limit (Redis)**
- Ключ: `sms:ratelimit:send:9262306381`
- Если значение >= 5 → ошибка "Превышен лимит"
- Иначе → увеличить счетчик (TTL: 1 час)
4. **Генерация кода**
- 6-значный случайный код: `106574`
5. **Сохранение кода в Redis**
- Ключ: `sms:code:9262306381`
- Значение: `106574`
- TTL: 600 секунд (10 минут)
6. **Отправка SMS (HTTP Request)**
- URL: API SMS провайдера (SigmaSMS или другой)
- Метод: POST
- Body: `{ "phone": "9262306381", "text": "Код подтверждения: 106574" }`
7. **Обработка ошибок**
- Если SMS не отправилось → удалить код из Redis
- Вернуть ошибку
8. **Ответ**
```json
{
"success": true,
"message": "Код отправлен на ваш номер телефона"
}
```
---
## Workflow 2: Проверка SMS кода
**Webhook URL:** `https://n8n.clientright.pro/webhook/sms-verify`
### Шаги:
1. **Webhook Trigger** (POST)
- Принимает: `{ "phonenumber": "+79262306381", "code": "106574" }`
2. **Нормализация номера телефона**
- Аналогично workflow 1
3. **Проверка Rate Limit для попыток (Redis)**
- Ключ: `sms:ratelimit:attempts:9262306381`
- Если значение >= 10 → ошибка "Превышено количество попыток"
- Иначе → увеличить счетчик (TTL: 15 минут)
4. **Чтение кода из Redis**
- Ключ: `sms:code:9262306381`
- Если не найден → ошибка "Код не найден или истек"
5. **Сравнение кодов**
- Если `введенный код !== сохраненный код` → ошибка "Неверный код"
6. **Успешная верификация**
- Удалить код из Redis: `sms:code:9262306381`
- Удалить счетчик попыток: `sms:ratelimit:attempts:9262306381`
- Создать токен верификации: `sms:verified:9262306381` = `random_token` (TTL: 1 час)
7. **Ответ**
```json
{
"success": true,
"message": "Код подтвержден",
"token": "abc123def456..."
}
```
---
## Workflow 3: Проверка статуса верификации
**Webhook URL:** `https://n8n.clientright.pro/webhook/sms-check-verified`
### Шаги:
1. **Webhook Trigger** (POST)
- Принимает: `{ "phonenumber": "+79262306381", "token": "abc123..." }`
2. **Нормализация номера**
3. **Проверка токена в Redis**
- Ключ: `sms:verified:9262306381`
- Если токен совпадает → `verified: true`
- Иначе → `verified: false`
4. **Ответ**
```json
{
"success": true,
"verified": true
}
```
---
## Преимущества переноса в n8n
1. ✅ **Визуализация** - видно весь процесс на графике
2. ✅ **Логирование** - все шаги логируются автоматически
3. ✅ **Мониторинг** - видно ошибки и задержки
4. ✅ **Гибкость** - легко добавить новые шаги (например, уведомления в Telegram)
5. ✅ **Тестирование** - можно тестировать каждый шаг отдельно
6. ✅ **Масштабируемость** - легко добавить несколько SMS провайдеров с fallback
---
## Изменения в PHP коде
После переноса в n8n, `sms-verify.php` станет простым прокси:
```php
// sms-verify.php?action=send
// Просто перенаправляет на n8n webhook
// sms-verify.php?action=verify
// Просто перенаправляет на n8n webhook
```
Или можно вообще убрать PHP и вызывать n8n напрямую из JS.

View File

@@ -0,0 +1,230 @@
# Формат ответа N8N Webhook для проверки SMS кода
## URL Webhook
`https://n8n.clientright.pro/webhook/erv_sms_verify`
## Входящие данные (POST)
```json
{
"phonenumber": "+79262306381",
"code": "106574"
}
```
---
## Формат ответа
### ✅ Успешная проверка (HTTP 200)
```json
{
"success": true,
"message": "Код подтвержден"
}
```
**Поля:**
- `success` (boolean, обязательное) - `true` при успехе
- `message` (string, обязательное) - сообщение для пользователя
---
### ❌ Ошибки (HTTP 400)
#### 1. Неверный код
```json
{
"success": false,
"error": "invalid_code",
"message": "Неверный код"
}
```
**Поля:**
- `success` (boolean) - `false`
- `error` (string) - код ошибки: `"invalid_code"`
- `message` (string) - сообщение для пользователя
---
#### 2. Код не найден или истек
```json
{
"success": false,
"error": "code_not_found",
"message": "Код не найден или истек. Запросите новый код."
}
```
**Поля:**
- `success` (boolean) - `false`
- `error` (string) - код ошибки: `"code_not_found"`
- `message` (string) - сообщение для пользователя
---
#### 3. Превышен лимит попыток
```json
{
"success": false,
"error": "rate_limit_exceeded",
"message": "Превышено количество попыток. Попробуйте позже."
}
```
**Поля:**
- `success` (boolean) - `false`
- `error` (string) - код ошибки: `"rate_limit_exceeded"`
- `message` (string) - сообщение для пользователя
---
#### 4. Номер телефона или код не указаны
```json
{
"success": false,
"error": "missing_data",
"message": "Номер телефона или код не указаны"
}
```
**Поля:**
- `success` (boolean) - `false`
- `error` (string) - код ошибки: `"missing_data"`
- `message` (string) - сообщение для пользователя
---
#### 5. Внутренняя ошибка сервера (HTTP 500)
```json
{
"success": false,
"error": "internal_error",
"message": "Сервис временно недоступен. Попробуйте позже."
}
```
**Поля:**
- `success` (boolean) - `false`
- `error` (string) - код ошибки: `"internal_error"`
- `message` (string) - сообщение для пользователя
---
## Коды ошибок
| Код ошибки | HTTP Status | Описание |
|------------|-------------|----------|
| `invalid_code` | 400 | Введенный код не совпадает с сохраненным |
| `code_not_found` | 400 | Код не найден в Redis или истек (TTL) |
| `rate_limit_exceeded` | 400 | Превышен лимит попыток проверки (10 за 15 минут) |
| `missing_data` | 400 | Не указан номер телефона или код |
| `internal_error` | 500 | Внутренняя ошибка (Redis недоступен и т.д.) |
---
## Примеры использования в n8n
### Успешная проверка
**Workflow шаги:**
1. Webhook Trigger → получает `phonenumber` и `code`
2. Function Node → нормализация номера
3. Redis Node (GET) → чтение кода: `sms:code:9262306381`
4. Function Node → сравнение кодов
5. Redis Node (DEL) → удаление кода и счетчика попыток
6. **Respond to Webhook** → возвращает успешный ответ
**Код для Respond to Webhook:**
```json
{
"success": true,
"message": "Код подтвержден"
}
```
---
### Ошибка: Неверный код
**Workflow шаги:**
1. ... (аналогично успешной проверке)
2. Function Node → сравнение кодов
3. IF Node → если коды не совпадают
4. **Respond to Webhook** → возвращает ошибку
**Код для Respond to Webhook:**
```json
{
"success": false,
"error": "invalid_code",
"message": "Неверный код"
}
```
---
### Ошибка: Код не найден
**Workflow шаги:**
1. ... (аналогично)
2. Redis Node (GET) → чтение кода
3. IF Node → если код = null
4. **Respond to Webhook** → возвращает ошибку
**Код для Respond to Webhook:**
```json
{
"success": false,
"error": "code_not_found",
"message": "Код не найден или истек. Запросите новый код."
}
```
---
## Совместимость с текущим кодом
Текущий JS код (`common.js`) ожидает:
```javascript
success: function(data) {
if (data.success) {
// Успех - сохраняем токен
sms_verify_token = data.token;
// ... показываем форму
} else {
// Ошибка - показываем сообщение
$('.modal .form-item__warning').text(data.message || "Неверный код");
}
}
```
**Предложенный формат полностью совместим!**
---
## Дополнительные рекомендации
1. **HTTP Status Codes:**
- Успех: `200 OK`
- Ошибки клиента: `400 Bad Request`
- Ошибки сервера: `500 Internal Server Error`
2. **Content-Type:**
- Всегда: `application/json; charset=utf-8`
3. **Логирование:**
- Логировать все попытки проверки (успешные и неуспешные)
- Логировать ошибки с деталями
4. **Безопасность:**
- Не возвращать детали внутренних ошибок в production
- Не возвращать реальные коды в ответах

View File

@@ -687,8 +687,8 @@ $(function() {
dataType: 'json',
success: function(data) {
if (data.success) {
// Сохраняем токен верификации
sms_verify_token = data.token;
// Код подтвержден (токен больше не используется)
// sms_verify_token = data.token; // Убрано - токен больше не нужен
$('.sms-success').removeClass('d-none');
@@ -729,7 +729,7 @@ $(function() {
var $phoneField = $('.js-phone-mask');
var confirmedPhone = $phoneField.val();
$phoneField.attr('data-confirmed-phone', confirmedPhone)
.attr('data-verify-token', sms_verify_token)
// .attr('data-verify-token', sms_verify_token) // Убрано - токен больше не нужен
.prop('readonly', true)
.attr('autocomplete', 'off');

View File

@@ -353,11 +353,10 @@ try {
], JSON_UNESCAPED_UNICODE);
} elseif ($action === 'verify') {
// Проверка кода
// Проверка кода через n8n webhook
log_message("=== НАЧАЛО ПРОВЕРКИ КОДА ===");
log_message("REQUEST_METHOD: " . ($_SERVER['REQUEST_METHOD'] ?? 'не установлен'));
log_message("POST данные: " . json_encode($_POST, JSON_UNESCAPED_UNICODE));
log_message("GET данные: " . json_encode($_GET, JSON_UNESCAPED_UNICODE));
$phone = $_POST['phonenumber'] ?? '';
$code = $_POST['code'] ?? '';
@@ -370,108 +369,89 @@ try {
throw new Exception("Номер телефона или код не указаны");
}
$phone_cleaned = clear_phone($phone);
log_message("Номер после очистки: '$phone_cleaned'");
// Получаем URL webhook из .env
$webhook_url = env('N8N_SMS_VERIFY_WEBHOOK', '');
// Проверка rate limiting - пытаемся переподключиться при необходимости
log_message("Попытка подключения к Redis для проверки кода...");
$redis = getRedis(true); // Принудительно пытаемся переподключиться
$stored_code = false;
$stored_code = false;
// Пытаемся получить код из Redis
if ($redis) {
log_message("Redis подключен успешно для проверки кода");
try {
// Проверяем количество попыток
$key_attempts = "sms:ratelimit:attempts:$phone_cleaned";
$attempts = $redis->get($key_attempts) ?: 0;
if ($attempts >= 10) {
log_message("Превышено количество попыток для номера $phone_cleaned");
throw new Exception("Превышено количество попыток. Попробуйте позже.");
}
// Увеличиваем счетчик попыток
$redis->setex($key_attempts, 900, $attempts + 1); // 15 минут
// Проверяем код
$key = "sms:code:$phone_cleaned";
log_message("Проверка кода для номера $phone_cleaned, ключ Redis: $key, введенный код: $code");
$stored_code = $redis->get($key);
if ($stored_code !== null) {
log_message("Код найден в Redis: $stored_code");
}
} catch (Exception $e) {
// Пробрасываем исключение, если это ошибка rate limiting
if (strpos($e->getMessage(), 'Превышено количество попыток') !== false) {
throw $e;
}
log_message("Ошибка при работе с Redis: " . $e->getMessage());
}
if (empty($webhook_url)) {
log_message("Ошибка: не указан N8N_SMS_VERIFY_WEBHOOK в .env");
throw new Exception("Сервис временно недоступен. Попробуйте позже.");
}
// Если код не найден в Redis, пытаемся получить из файла (fallback)
if ($stored_code === null) {
log_message("Код не найден в Redis, проверяем файловое хранилище...");
$stored_code = getCodeFromFile($phone_cleaned);
if ($stored_code !== false) {
log_message("Код найден в файловом хранилище для номера $phone_cleaned");
}
log_message("Отправка запроса на n8n webhook: $webhook_url");
// Отправляем запрос на n8n webhook
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $webhook_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'phonenumber' => $phone,
'code' => $code
], JSON_UNESCAPED_UNICODE));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json; charset=utf-8'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
$curl_errno = curl_errno($ch);
curl_close($ch);
if ($curl_error) {
log_message("Ошибка CURL при проверке кода через n8n: $curl_error (код: $curl_errno)");
throw new Exception("Ошибка соединения с сервисом. Попробуйте позже.");
}
// Если код все еще не найден, выдаем ошибку
if ($stored_code === null || $stored_code === false) {
log_message("КРИТИЧЕСКАЯ ОШИБКА: Код не найден ни в Redis, ни в файловом хранилище для номера $phone_cleaned");
throw new Exception("Код не найден или истек. Запросите новый код.");
// Логируем ответ от n8n
log_message("Ответ от n8n: HTTP $http_code, ответ: " . substr($response, 0, 200));
// Парсим ответ
$response_data = json_decode($response, true);
if ($response_data === null) {
log_message("Ошибка: не удалось распарсить ответ от n8n: " . substr($response, 0, 200));
throw new Exception("Ошибка обработки ответа. Попробуйте позже.");
}
log_message("Проверка кода: сохраненный=$stored_code, введенный=$code");
// Проверяем успешность
if ($http_code >= 200 && $http_code < 300) {
// Успешный ответ от n8n
if (isset($response_data['success']) && $response_data['success'] === true) {
log_message("Код подтвержден через n8n для номера: " . ($phone ?: 'не указан'));
if ($stored_code !== $code) {
log_message("Неверный код для номера $phone_cleaned. Введен: $code, ожидался: $stored_code");
throw new Exception("Неверный код");
}
// Возвращаем упрощенный ответ (без токена)
echo json_encode([
'success' => true,
'message' => $response_data['message'] ?? 'Код подтвержден'
], JSON_UNESCAPED_UNICODE);
} else {
// Ошибка от n8n
$error_message = $response_data['message'] ?? 'Неверный код';
log_message("Ошибка проверки кода через n8n: $error_message");
// Код верный - удаляем его из Redis и файла, создаем сессию подтверждения
if ($redis) {
try {
$key = "sms:code:$phone_cleaned";
$redis->del($key);
$key_attempts = "sms:ratelimit:attempts:$phone_cleaned";
$redis->del($key_attempts); // Сбрасываем счетчик попыток
} catch (Exception $e) {
log_message("Ошибка при удалении кода из Redis: " . $e->getMessage());
}
}
deleteCodeFromFile($phone_cleaned); // Удаляем из файла тоже
// Создаем токен подтверждения (действует 1 час) - только если Redis доступен
$verify_token = bin2hex(random_bytes(32));
if ($redis) {
try {
$verify_key = "sms:verified:$phone_cleaned";
$redis->setex($verify_key, 3600, $verify_token);
log_message("Токен верификации сохранен в Redis для номера $phone_cleaned");
} catch (Exception $e) {
log_message("Ошибка при создании токена верификации: " . $e->getMessage());
http_response_code(400);
echo json_encode([
'success' => false,
'message' => $error_message
], JSON_UNESCAPED_UNICODE);
}
} else {
log_message("Токен верификации сгенерирован, но не сохранен (Redis недоступен)");
// HTTP ошибка
$error_message = $response_data['message'] ?? "Ошибка сервиса (HTTP $http_code)";
log_message("HTTP ошибка от n8n: $http_code, сообщение: $error_message");
http_response_code($http_code >= 500 ? 500 : 400);
echo json_encode([
'success' => false,
'message' => $error_message
], JSON_UNESCAPED_UNICODE);
}
log_message("Код подтвержден для номера: $phone_cleaned");
echo json_encode([
'success' => true,
'message' => 'Код подтвержден',
'token' => $verify_token // Токен для последующей проверки
], JSON_UNESCAPED_UNICODE);
} elseif ($action === 'check_verified') {
// Проверка статуса верификации (для проверки перед отправкой формы)
$phone = $_POST['phonenumber'] ?? '';