Модуль notifications¶
Назначение и границы¶
Модуль notifications отвечает за доставку сообщений через внешних провайдеров (SMS через
smsc.ru, Telegram и MAX через их Bot API). Это инфраструктурный слой: он не знает, кто и почему
отправляет сообщение — клиент запрашивает код входа, сотруднику ФЧ нужно системное уведомление или
что-то ещё. Эти решения принимают модули-потребители через публичный контракт.
Модуль не является целевой моделью уведомлений, описанной в docs/technical/legacy/10-notifications.md
§10.8 (событийная подписка, шаблоны, согласия, журнал доставки, адресация по ролям/точкам) — это
отдельная задача, которую нужно делать явно, когда появится конкретный потребитель (например,
служебные уведомления сотрудникам ФЧ). Сейчас notifications — это только провайдеры за общим
интерфейсом, без домена правил и без собственной схемы БД.
Модуль владеет:
- HTTP-взаимодействием с smsc.ru, Telegram Bot API и MAX Bot API;
- учётными данными провайдеров (логин/пароль smsc.ru; токен и секрет вебхука у каждого бота).
Модуль не отвечает за:
- кому, когда и по какому поводу отправлять сообщение — это решает модуль-потребитель
(
customer— для кода входа); - кто такой получатель: для SMS это телефон (сам по себе адрес), а для Telegram/MAX —
chat_id, который ещё нужно узнать по телефону. Привязку телефона кchat_idхранит и отдаётcustomer(customer.telegram_contact/customer.max_contact, см. модуль customer) —notificationsо привязках не знает, только умеет слать по уже готовомуchat_id; - шаблоны текста, повторные попытки, журнал доставки, согласия и отписку — этого пока нет нигде,
см. открытые вопросы ОВ-25–ОВ-32 в
10-notifications.md.
Единственный текущий потребитель — customer (доставка одноразового кода по SMS, Telegram и MAX,
ProviderCustomerCodeSender, см. модуль customer).
Структура¶
ru.panampizza.monolith.notifications
├── api
│ ├── SmsSender публичный контракт отправки SMS
│ ├── TelegramSender, TelegramWebhookAuthentication Telegram по chat_id + проверка вебхука
│ └── MaxSender, MaxWebhookAuthentication MAX по chat_id + проверка вебхука
├── Secrets.kt resolveSecret — общая логика value/file для секретов
├── SmscProperties, SmscClient smsc.ru
├── TelegramProperties, TelegramClient Telegram Bot API
└── MaxProperties, MaxClient MAX Bot API
Корневой пакет объявлен модулем Spring Modulith, allowedDependencies = {} — модуль ни от кого не
зависит. Пакет notifications.api объявлен named interface api.
Публичный контракт¶
interface SmsSender {
fun send(phone: String, text: String)
}
interface TelegramSender {
fun send(chatId: Long, text: String)
fun requestContact(chatId: Long, text: String)
}
interface TelegramWebhookAuthentication {
fun verify(secretHeader: String?): Boolean
}
MaxSender/MaxWebhookAuthentication — те же сигнатуры, что TelegramSender/
TelegramWebhookAuthentication, отдельный интерфейс на канал, а не общий MessengerSender: у
Telegram и MAX разные провайдеры под капотом (разный HTTP, разная схема ошибок), общий контракт
без пользы преждевременно объединял бы их.
Вызовы синхронные. Ошибка (сетевая, код/описание ошибки от Telegram, HTTP-статус с телом
{code, message} от MAX) выбрасывается исключением — решение, что с этим делать (повторить,
откатить транзакцию, залогировать), остаётся за вызывающей стороной. Так,
customer.ProviderCustomerCodeSender вызывает send() внутри @Transactional-метода
CustomerAuthChallengeService.request(): исключение откатывает уже созданный challenge, клиент
видит ошибку и может повторить запрос.
requestContact — отдельный от send метод, а не общий параметр «с клавиатурой»: это одноразовое
сообщение с кнопкой «поделиться номером», используемое только на шаге привязки контакта (см.
StorefrontTelegramWebhookController/StorefrontMaxWebhookController в storefront), а не часть
контракта доставки кода.
Конфигурация¶
smsc.ru — panam.notifications.smsc¶
panam:
notifications:
smsc:
login: ${SMSC_LOGIN:}
password: ${PANAM_SMSC_PASSWORD_SECRET:}
password-file: ${PANAM_SMSC_PASSWORD_SECRET_FILE:}
sender: ${SMSC_SENDER:}
| Параметр | Назначение | Значение по умолчанию |
|---|---|---|
base-url |
Адрес API smsc.ru | https://smsc.ru/sys/send.php |
login |
Логин аккаунта smsc.ru | — |
password / password-file |
Пароль аккаунта — ровно один из двух, секрет | — |
sender |
Имя отправителя (буквенно-цифровое, до 11 символов); пусто — номер аккаунта по умолчанию | — |
Telegram — panam.notifications.telegram¶
panam:
notifications:
telegram:
bot-token: ${PANAM_TELEGRAM_BOT_TOKEN_SECRET:}
bot-token-file: ${PANAM_TELEGRAM_BOT_TOKEN_SECRET_FILE:}
webhook-secret: ${PANAM_TELEGRAM_WEBHOOK_SECRET:}
webhook-secret-file: ${PANAM_TELEGRAM_WEBHOOK_SECRET_FILE:}
webhook-url: ${PANAM_TELEGRAM_WEBHOOK_URL:}
| Параметр | Назначение | Значение по умолчанию |
|---|---|---|
api-base-url |
Адрес Telegram Bot API | https://api.telegram.org |
bot-token / bot-token-file |
Токен бота — ровно один из двух, секрет | — |
webhook-secret / webhook-secret-file |
Секрет X-Telegram-Bot-Api-Secret-Token — ровно один из двух, секрет |
— |
webhook-url |
Публичный HTTPS-адрес вебхука; пусто — регистрация при старте пропускается | — |
MAX — panam.notifications.max¶
panam:
notifications:
max:
bot-token: ${PANAM_MAX_BOT_TOKEN_SECRET:}
bot-token-file: ${PANAM_MAX_BOT_TOKEN_SECRET_FILE:}
webhook-secret: ${PANAM_MAX_WEBHOOK_SECRET:}
webhook-secret-file: ${PANAM_MAX_WEBHOOK_SECRET_FILE:}
webhook-url: ${PANAM_MAX_WEBHOOK_URL:}
| Параметр | Назначение | Значение по умолчанию |
|---|---|---|
api-base-url |
Адрес MAX Bot API | https://platform-api.max.ru |
bot-token / bot-token-file |
Токен бота — ровно один из двух, секрет | — |
webhook-secret / webhook-secret-file |
Секрет X-Max-Bot-Api-Secret — ровно один из двух, секрет |
— |
webhook-url |
Публичный HTTPS-адрес вебхука; пусто — регистрация при старте пропускается | — |
Все секреты (пароль smsc.ru, токены и секреты вебхуков Telegram/MAX) — production-секреты,
задаются по той же схеме, что JWT-секреты и pepper покупателя
(docs/technical/modules/customer.md): инлайн-значение для разработки, _FILE (путь к файлу,
например из Kubernetes Secret) — в общих средах и production. Отсутствие ровно одного значения или
обоих сразу не даёт приложению стартовать (SmscClient.validatePassword, TelegramClient.init,
MaxClient.init, @PostConstruct).
Запрос к smsc.ru¶
POST /sys/send.php, application/x-www-form-urlencoded, fmt=3 (JSON-ответ):
Телефон передаётся без знака + — так ожидает smsc.ru. Ответ с error_code != 0 превращается в
исключение с расшифровкой кода (коды 1–9 замаплены на читаемые сообщения; неизвестный код —
исходная строка error от провайдера).
Telegram Bot API¶
POST /bot<token>/sendMessage (JSON-тело {chat_id, text}, опционально reply_markup с одной
кнопкой «Поделиться номером телефона» для requestContact). Ответ Telegram — {"ok": true|false,
"description": "..."}; ok: false превращается в исключение с текстом description.
При старте, если задан webhook-url, TelegramClient идемпотентно регистрирует его через
POST /bot<token>/setWebhook {url, secret_token}. Без webhook-url (типично для локальной
разработки без публичного HTTPS) регистрация пропускается без ошибки — это не мешает приложению
стартовать и не мешает send/requestContact работать, если вебхук уже зарегистрирован вручную.
Входящие сообщения от Telegram и MAX обрабатывает не notifications, а вебхуки в storefront
(StorefrontTelegramWebhookController/StorefrontMaxWebhookController,
POST /api/v1/auth/telegram/webhook и POST /api/v1/auth/max/webhook) — они вызывают
TelegramWebhookAuthentication.verify/MaxWebhookAuthentication.verify для проверки секрета и
customer.api.CustomerTelegramContacts/CustomerMaxContacts для привязки телефона к chat_id.
Разбор ботов не входит в notifications, потому что привязка телефона — customer-домен, а не
деталь провайдера; см. модуль customer.
MAX Bot API¶
POST /messages?chat_id=<id>&v=1.2.5 (JSON-тело {text, attachments}; attachments — пустой
массив либо одна inline_keyboard-клавиатура с кнопкой request_contact для requestContact),
заголовок Authorization: <токен> (без Bearer). Ошибки сигналятся HTTP-статусом (не полем в
теле, как у Telegram) с JSON {"code": "...", "message": "..."} — текст ответа целиком попадает в
исключение.
При старте, если задан webhook-url, MaxClient идемпотентно регистрирует его через
POST /subscriptions?v=1.2.5 {url, secret}. Без webhook-url регистрация пропускается без ошибки
— как и у Telegram.
Контакт в вебхуке MAX приходит не отдельным полем phone_number (как у Telegram), а VCF-блоком
(attachment.payload.vcf_info, многострочный текст вида TEL:+79001234567) —
StorefrontMaxWebhookController.extractPhoneFromVcf находит строку, начинающуюся на TEL, и берёт
текст после :.
Тесты¶
SmscClientTests— успешная отправка (проверка полей запроса), маппинг кода ошибки в сообщение, отказ старта при отсутствующем пароле.TelegramClientTests— отправка текста и запроса контакта, маппингok: falseв исключение, регистрация/пропуск вебхука при старте в зависимости отwebhook-url, проверка секрета вебхука, отказ старта при отсутствующем токене.MaxClientTests— то же самое для MAX: авторизационный заголовок и?v=, отправка текста и запроса контакта, маппинг HTTP-ошибки с телом в исключение, регистрация/пропуск вебхука, проверка секрета, отказ старта при отсутствующем токене.ModularityTestsпроверяет границы Spring Modulith.
Полный запуск:
Следующие этапы¶
Целевая модель уведомлений (10-notifications.md §10.8) — событийная подписка, шаблоны, контакты,
согласия, журнал доставки, адресация служебных уведомлений — отдельная задача, когда появится
конкретный потребитель за пределами кода входа покупателя (три канала кода — SMS, Telegram, MAX —
уже подключены).