# SMS-напоминания RocketSMS

Внешняя отправка после установки выключена (`enabled=false`, `dry_run=true`). Календарь работает независимо от RocketSMS. Рекламного маршрута отправки нет: направление технически закрыто.

## Миграции и резервная копия

До изменения схемы сохраните защищённую копию БД. После `upgrade_customer_schema(PDO)` выполните `apply_sms_migrations(PDO)` из `app/SmsMigrations.php` предусмотренной командой общей миграции. На HTTP-запросах миграции не запускаются. Добавляются `sms_preferences`, `sms_notifications`, `sms_delivery_attempts`, `sms_phone_challenges`, `sms_request_limits`; существующие заказы и каталог не удаляются. Внешние ключи указывают на покупателей и события. Для входа до создания покупателя `sms_notifications.user_id` и `sms_phone_challenges.user_id` допускают NULL; непрозрачный `token_hash` и `session_hash` связывают код с конкретным браузером. Повторная миграция безопасно добавляет эти поля и разрешает NULL в ранее созданных таблицах.

При откате сначала отключите отправку и фоновое задание, сохраните очередь и доказательства согласий. Откат кода не требует удаления добавленных таблиц. Если нужен откат схемы, восстановите проверенную резервную копию; не удаляйте журналы без утверждённого основания. Копии содержат персональные данные: доступ только ответственным сотрудникам, защищённое хранение, срок по утверждённой политике.

## Конфигурация

Секреты хранит `config/rocketsms.local.php`, закрытый от HTTP и исключённый из контроля версий. Альтернатива — серверное окружение (оно имеет приоритет):

```dotenv
ROCKETSMS_ENABLED=false
ROCKETSMS_DRY_RUN=true
ROCKETSMS_USERNAME=
ROCKETSMS_PASSWORD=
ROCKETSMS_SENDER=
ROCKETSMS_API_BASE=https://api.rocketsms.by
ROCKETSMS_PROTOCOL_CONFIRMED=false
ROCKETSMS_LEGAL_REVIEWED=false
ROCKETSMS_LEGAL_OVERRIDE=false
ROCKETSMS_CATEGORY_CONFIRMED=false
ROCKETSMS_TEST_CUSTOMER_IDS=
SMS_DEFAULT_TIMEZONE=Europe/Minsk
SMS_DEFAULT_SEND_TIME=10:00
```

Здесь пароль — исходный пароль аккаунта; адаптер однократно вычисляет MD5 исключительно для протокола RocketSMS. Пароли покупателей хешируются отдельно современным алгоритмом. Пустое поле пароля в админке сохраняет предыдущий пароль. Ключи не передаются в HTML, JSON-статус, технические журналы. Время и зона фиксированы правилами продукта: 10:00 Europe/Minsk.

`ROCKETSMS_TEST_CUSTOMER_IDS` — явный серверный список ID покупателей для контролируемого теста, через запятую. Даже для этих ID нужны актуальные согласия, подтверждённый собственный телефон и включённые уведомления; произвольный номер ввести нельзя.

### Пропуск юридической проверки

Владелец может включить «Игнорировать юридическую проверку для SMS» в разделе «SMS и календарь» и сохранить настройки. Параметр `legal_override` по умолчанию выключен; серверная альтернатива — `ROCKETSMS_LEGAL_OVERRIDE=true` (окружение имеет приоритет над сохранённой настройкой).

При включении эта проверка допускает отправку по условию `legal_reviewed || legal_override`. Исходная отметка `legal_reviewed` сохраняет своё значение; административная панель отдельно показывает разрешённый пропуск. Параметр действует на SMS-коды входа и календарные SMS, не изменяет юридический допуск магазина к заказам и не открывает рекламную рассылку. Согласия на календарь/напоминания, подтверждение номера, настройки пользователя, лимиты, реквизиты и протокол провайдера, отправитель, категория и контрольная доставка продолжают проверяться на своих этапах.

Запрос изменения фиксируется в `audit_log` как `sms.legal_override_requested` с прежним и запрошенным значением; после сохранения `sms.settings` содержит прежнее и фактическое новое значение. Журнал связывает действие с администратором и временем, без паролей и текстов сообщений. У менеджера и поддержки нет права менять настройку. После снятия галочки при отсутствии `legal_reviewed` дальнейшая внешняя отправка снова блокируется при следующей проверке. Настройка не меняет сам факт проведения проверки документов.

Проверки: `tests/sms.php` — 112/112 с подменённым провайдером и временными таблицами, включая пропуск/возврат юридической блокировки и сохранение остальных условий; `tests/sms-admin-legal-override.php` — 20/20 на каждом из двух локальных серверов. Последний тест проверяет видимость настройки и запреты по роли/CSRF, выполняя только чтение и заведомо отклоняемые сохранения. Рабочая конфигурация остаётся неизменной, реальных SMS в этих проверках нет.

## Подключение и обязательные проверки

Выбран подробный протокол [PDF v1.5.0 от 01.11.2025](https://rocketsms.by/storage/rocketsms_api.pdf): `POST https://api.rocketsms.by/simple/send`, `application/x-www-form-urlencoded`, `username`, `password=md5(исходный пароль)`, `phone` цифрами без `+`, `text` UTF-8, `sender`. Быстрые примеры сайта используют другой вариант API; автоматического переключения или смешивания протоколов нет. До отправки подтвердите применимость `/simple/*` и MD5 для конкретного аккаунта у поддержки провайдера.

Для статуса, баланса и отправителей используются GET `/simple/status?id=…`, `/simple/balance`, `/simple/senders` с той же авторизацией. TLS-сертификаты проверяются; только HTTPS, таймаут соединения 5 секунд, общий 15 секунд, переходы по редиректам запрещены. Пароль в query GET не журналируется приложением; проверьте исключение полного URL в HTTP-прокси и инфраструктурных журналах.

Зарегистрируйте имя отправителя в кабинете RocketSMS и дождитесь проверки/регистрации. Проверка аккаунта в админке получает список, к выбору допускаются только имена с `checked` и `registered`. После смены учётных данных проверку повторяют. Сам факт приёма авторизационных SMS не подтверждает доступность информационных напоминаний.

Перед production ответственное лицо проверяет актуальные юридические тексты, реквизиты оператора, правовое основание и договор RocketSMS/уполномоченных лиц, состав передаваемых данных, трансграничную передачу при наличии, сроки хранения, права субъектов и формы доказуемых согласий. Отдельно подтверждает доступность именно информационных напоминаний для аккаунта. Юридические материалы и источники — `docs/account-legal-research.md`.

Порядок включения: указать реквизиты; проверить протокол для аккаунта; получить зарегистрированного отправителя проверкой аккаунта; подтвердить юридическую проверку; разрешить OTP (`enabled=true`, `dry_run=false`); покупатель самостоятельно предоставляет два согласия и подтверждает свой телефон кодом; включает уведомления; добавить его ID в серверный тестовый список; подтвердить доступность категории напоминаний; выполнить один тест в админке. Рабочие напоминания откроются **только после статуса DELIVERED** этого теста от провайдера. Галочка не заменяет фактический тест. Доказательство теста привязано к логину, паролю и отправителю; смена настроек требует повторного теста. Все платные тесты выполняет ответственный сотрудник сознательным действием; автоматические тесты используют fake.

## Основной вход по телефону

Вход и регистрация используют номер и одноразовый код. В интерфейсе по умолчанию предлагается Беларусь `+375`, сервер принимает международные номера. Email остаётся необязательным контактом. `requestLoginCode(phone, sessionId, ip, purpose, userId)` не создаёт покупателя: сохраняются только техническое уведомление и краткоживущий запрос. Клиент получает непрозрачный токен, замаскированный номер и сроки, код ему не возвращается. `verifyLoginCode` требует совпадения токена, браузерной сессии, назначения и владельца; после одноразового использования возвращает доказанный номер только серверному контроллеру. Контроллер передаёт его доверенному методу покупателя, который находит аккаунт по ранее подтверждённому номеру или создаёт новый. Совпадение с неподтверждённым телефоном/email не объединяет аккаунты.

Код авторизации — сервисная проверка доступа. Он не требует согласия на календарь или напоминания и не включает их автоматически. Общие проверки применимости протокола, отправителя и условий обработки данных обязательны. Когда RocketSMS выключен, запрос возвращает понятную недоступность; имитации кода и автоматического подтверждения нет. Ранее реально отправленный и ещё действующий код допускается проверить.

Срок кода — 10 минут, максимум пять попыток ввода, повторный запрос на номер не ранее минуты; часовые ограничения на номер и IP используют атомарные SQL-счётчики. Новый номер профиля подтверждается отдельным назначением `change_phone`, связанным с текущим владельцем; до запроса требуется недавнее доказательство входа на прежний номер. Код этого назначения нельзя использовать для входа в другой аккаунт.

Воркер очищает хеш кода после использования/истечения TTL. Строка запроса с номером сохраняется только пока нужна для скользящего часового ограничения и затем удаляется. Закрытые часовые корзины ограничений тоже удаляются. Технические уведомления и доказательства согласий этой очисткой не затрагиваются: сроки их хранения определяются утверждённой политикой оператора, произвольный срок модуль не назначает. Журнал показывает анонимную авторизацию без номера и кода.

## Очередь и фоновая работа

```powershell
& 'C:\OSPanel\modules\php\PHP_8.1\php.exe' tools\sms-worker.php run
& 'C:\OSPanel\modules\php\PHP_8.1\php.exe' tools\sms-status.php
```

Планировщик запускает `run` каждую минуту от ограниченной служебной учётной записи, с рабочей папкой проекта. Для Windows используйте приложенный установщик задания планировщика, подготовленный для проекта. Linux: cron каждую минуту с абсолютными путями PHP/скрипта. Не запускайте демона из HTTP-запроса. SQL `GET_LOCK` запрещает одновременные воркеры; уникальный ключ и блокировки строк защищают от повторов. Опция `queue` только рассчитывает очередь, `poll` только обновляет доставки. Никакой SMS-отправки при открытии страницы календаря нет.

Команды установки, проверки, отключения и удаления задания Windows, а также проверка последнего запуска описаны в [инструкции планировщика](sms-scheduler.md).

Отправка допускается в запланированный день между 10:00 и 20:00 включительно по Минску. После 20:00 и после даты события сообщение отмечается пропущенным. Для 29 февраля соблюдается выбранное правило 28 февраля/1 марта. Уникальность — покупатель, событие, год наступления, интервал 0/1/2/7: перенос даты в том же году не повторяет уже переданную SMS.

Непосредственно перед передачей повторно проверяются согласия с актуальной версией текста, телефон, блокировка получателя, настройки, владелец/состояние/версия события. Отзыв согласия и смена телефона отменяют локальную очередь и неиспользованные коды. До пяти уведомлений в сутки на покупателя, максимум три попытки только для явно подтверждённого временного отказа и в пределах окна. Неизвестные ошибки не считаются временными.

`provider_queued` означает приём провайдером, `sent` — отправку, `delivered` — подтверждение доставки. ID сообщения сам по себе не означает доставку. Таймаут/невалидный ответ при отправке дают `unknown`, повторной отправки нет. Если есть ID, опрашивается его статус; если ID нет, автоматическое выяснение невозможно по документированному API — состояние остаётся неопределённым до разбирательства с провайдером, вручную перепосылать запрещено без установления факта неотправки. Зависшая более пяти минут `processing` также становится `unknown`.

Каждая передача, включая коды входа, привязана к отпечатку настроек аккаунта и отправителя до обращения к сети. При смене настроек старые идентификаторы не опрашиваются через новый аккаунт. Ответ статуса обязан содержать тот же ID, который запрашивался: чужой ID не подтверждает доставку и не открывает рабочие напоминания. Рабочие настройки перечитываются непосредственно перед каждой передачей, поэтому отключение не ждёт завершения всей очереди.

При недоступности провайдера календарь продолжает работать. Для немедленного отключения снимите «Отправка SMS» или установите `ROCKETSMS_ENABLED=false`; следующая проверка воркера не выполняет внешнюю отправку. Дополнительно можно остановить задание. Локальные отказы обязательны: PDF предупреждает, что чёрный список кабинета провайдера при API может игнорироваться.

## Проверки

`php tests/sms.php` после применения общей миграции. Тесты создают временные таблицы только своего соединения, используют случайные служебные аккаунты и подменённый транспорт/провайдер. Реальная очередь, покупатели, согласия и настройки не изменяются; платные сообщения не отправляются. Покрываются протокол/MD5, HTTPS, обработка статусов и ошибок, отсутствие согласий, повторные запуски, окно, ежегодные даты, перенос/удаление, отзыв/смена телефона, OTP/лимиты, исключение рекламы и неопределённого повторного запроса. Клиентские формы/авторизация/IDOR тестируются отдельными тестами кабинета.
