Перейти к содержанию

Notifications

Push-уведомления: Redis stream → notifications-service → FCM.

Архитектура

sequenceDiagram
    Service->>NotificationPublisher: publish(userId, channel, title, body, traceId, data)
    NotificationPublisher->>NotificationPublisher: проверить isInitialized
    alt Redis не инициализирован
        NotificationPublisher->>Session: log WARNING — skipping
    else
        NotificationPublisher->>Redis: XADD stream.notifications.jobs {payload: JSON}
        Note over Redis: best-effort (нет retry, нет ACK)
        Redis->>NotifService: XREADGROUP consume
        NotifService->>FCM: отправить push
        FCM->>Device: push notification
    end

Stream: stream.notifications.jobs. Payload — JSON с полями: userId, channel, title, body, traceId?, data?, badge?.

NotificationPublisher.publish() — параметры

Параметр Тип Обязателен Описание
userId int да числовой id получателя
channel String да payments / security / kyc / system
title String да заголовок уведомления
body String да текст уведомления
traceId String? нет для корреляции с payment intent
data Map? нет произвольные данные для Flutter-обработчика
badge int? нет счётчик badge (iOS)

NotificationTemplates — шаблоны

Фаза 1: шаблоны захардкожены в коде. Фаза 2: таблица notification_template + редактор в Admin.

Локали: ru (primary), th (Thailand), en (fallback). Нормализация: ru*ru, th*th, иное → en.

Шаблон Канал Когда Параметры
newDeviceLogin security вход с нового устройства locale, city?, country?, at
passwordChanged security пароль изменён или сброшен locale
nfcCloneDetected security клон NFC-метки — метка автоматически отозвана locale
nfcChargeRejectedLimit payments списание по NFC отклонено из-за превышения лимита locale
kycFullyVerified kyc оператор подтвердил верификацию (fully_verified) locale
transferReceived payments INTERNAL-перевод получен locale, amountMinor, currency, senderName?
transferSent payments INTERNAL-перевод отправлен locale, amountMinor, currency, recipientName?

Суммы форматируются из minor units (сатанги): amountMinor / 100 с 2 знаками после запятой.

NotificationEndpoint — DeviceToken

registerDevice

registerDevice(token, platform, appId, appVersion?, locale?)
  • Upsert: токен уже существует → UPDATE lastSeenAt, userId, appId, appVersion, locale
  • Новый токен → INSERT
  • Валидация: platform{ios, android, web}, длина токена 100–4096, locale{ru, th, en}
  • Хранит ipAddress из session.request.remoteInfo

unregisterDevice

unregisterDevice(token)
  • Идемпотентен: токен не найден или чужой userId → silent (нет ошибки)
  • Вызывается при logout перед FirebaseMessaging.deleteToken()

appId и роутинг

appId Приложение
wallet onewallet_base_flutter
closeloop closeloop_app_flutter
merchant merchant_app_flutter

appId хранится в device_token.appId и используется notifications-service для роутинга к правильному FCM проекту.

Best-effort семантика

Сбой публикации в Redis (NotificationPublisher.publish()) не откатывает вызывающую операцию.

Примеры: - finalizeVerification() — push failure логируется и игнорируется, kyc-транзакция уже зафиксирована - KycQueueService.submitKycJob() — fire-and-forget отдельно от DB write

Если NotificationsRedisClient.isInitialized == false (сервер поднялся без Redis), publish() логирует WARNING и возвращает без ошибки.