diff --git a/docs/n8n_migration_plan.md b/docs/n8n_migration_plan.md new file mode 100644 index 0000000..4fc0a41 --- /dev/null +++ b/docs/n8n_migration_plan.md @@ -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 } }; +``` diff --git a/docs/n8n_redis_example.md b/docs/n8n_redis_example.md new file mode 100644 index 0000000..1495998 --- /dev/null +++ b/docs/n8n_redis_example.md @@ -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 (если нужно, могу создать). diff --git a/docs/n8n_sms_workflow.md b/docs/n8n_sms_workflow.md new file mode 100644 index 0000000..98ffa88 --- /dev/null +++ b/docs/n8n_sms_workflow.md @@ -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. diff --git a/docs/n8n_webhook_response.md b/docs/n8n_webhook_response.md new file mode 100644 index 0000000..f3aa9a6 --- /dev/null +++ b/docs/n8n_webhook_response.md @@ -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 + - Не возвращать реальные коды в ответах diff --git a/js/common.js b/js/common.js index 3198e52..a79a5fc 100644 --- a/js/common.js +++ b/js/common.js @@ -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'); diff --git a/sms-verify.php b/sms-verify.php index 5733fd8..34d53ad 100644 --- a/sms-verify.php +++ b/sms-verify.php @@ -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 ($stored_code !== $code) { - log_message("Неверный код для номера $phone_cleaned. Введен: $code, ожидался: $stored_code"); - throw new Exception("Неверный код"); - } - - // Код верный - удаляем его из 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()); + // Проверяем успешность + if ($http_code >= 200 && $http_code < 300) { + // Успешный ответ от n8n + if (isset($response_data['success']) && $response_data['success'] === true) { + log_message("Код подтвержден через n8n для номера: " . ($phone ?: 'не указан')); + + // Возвращаем упрощенный ответ (без токена) + echo json_encode([ + 'success' => true, + 'message' => $response_data['message'] ?? 'Код подтвержден' + ], JSON_UNESCAPED_UNICODE); + } else { + // Ошибка от n8n + $error_message = $response_data['message'] ?? 'Неверный код'; + log_message("Ошибка проверки кода через n8n: $error_message"); + + 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'] ?? '';