Перейти к основному содержимому

Provider Adapter: Viber Messaging (заглушка)

Назначение

Доставка канала Viber (../domain/channel.md) через Viber Business Messages (транзакционные сообщения). Заглушка: реализация отложена — контракт зафиксирован, отправка активируется per-канал флагом. Адрес — телефон E.164 (Business Messages) или Viber user-id (public-account подписчик). Provider input (delivery receipts) — недоверенный внешний ввод.

Границы Adapter-а

ПолеЗначение
ProviderViber Business Messages (BSP — вендор TBD)
Сервисnotifications
Capabilitymessaging (viber)
Adapter classViberChannelProvider
Typed clientIViberProviderClient
Внутренняя операцияdispatcher при Channel = Viber, Status = Pending
Реализациязаглушка (отложено) — до включения канала отправка не выполняется и возвращается код NTF_PROVIDER_NOT_IMPLEMENTED

Контракты Provider-а

ОперацияDirectionInternal modelProvider-specific model
sendMessageoutboundNotificationDispatchRequestViberMessageRequest (receiver/type/text/keyboard)
delivery/seen webhookcallbackProviderDeliveryReceiptViberStatusWebhook (подписанный)

Модель Internal NotificationDispatchRequest

Дом — ../domain/notification.md. Ключевые: NotificationId (idempotency), RecipientAddress.PhoneE164 (Business) / MessengerHandle (public-account) → receiver, RenderedTitle+RenderedBodytext, DeepLink → keyboard-кнопка. При недоставке в Viber возможен sms-fallback вендора (учитывается как отдельный billing-исход, здесь — статус Sent).

Модель Provider-Specific ViberMessageRequest

ПолеТипОбязательностьProvider formatValidationInternal mapping
receiverstringДаE.164 / Viber idвалидный адрес (иначе skip)RecipientAddress
typestringДаtextконфиг
textstringДа≤ 1000не пустоеRenderedTitle+RenderedBody
keyboardобъект?НетViber keyboardDeepLink
clientReferencestringДаidempotency= NotificationIdNotificationId

Безопасность

КонтрольПравило
Authenticationapi-key/auth-token BSP
Signature validationwebhook — HMAC/подпись BSP + timestamp + replay-window
Secretsapi-key/webhook-secret — secret-store, ротация
PIIтелефон/тело — Sensitive (маска, тело не логируется)
Tenant bindingполучатель по RecipientUserId; sender — платформенный аккаунт

Resilience И Устойчивость

АспектПравило
Timeout5с к API
Retry5xx/429/timeout → перевод в Failed → повтор retry-worker-ом; invalid receiver → не retry
Circuit breakerразмыкание на клиенте провайдера
IdempotencyclientReference = NotificationId
Rate limitпо лимиту BSP + ChannelConfig.RateLimitPerMinute

Обработка Callback

ШагПравило
Verifyподпись webhook + timestamp
NormalizeViberStatusWebhook → ProviderDeliveryReceipt (sent/delivered/seen/failed)
ValidateclientReference = известный NotificationId
Dispatchприменить delivery-receipt: перевод уведомления в Sent/Failed
Storeстатус/ProviderMessageId/LastError на NotificationEntity

Маппинг Ошибок

Provider codeInternal errorПовторяемостьВидно пользователю
5xx / timeoutNTF_PROVIDER_TRANSIENTДаНет
429NTF_PROVIDER_RATE_LIMITEDДаНет
invalid receiver / no viberNTF_PROVIDER_INVALID_RECIPIENTНетНет
не активирован (заглушка)NTF_PROVIDER_NOT_IMPLEMENTEDНетНет

Поток доставки

Happy path и все ветки отказа отправки уведомления Channel = Viber. Статусы и переходы — по notification.md (INV-NTF-5/7); коды — таблица «Маппинг Ошибок».

  1. Выбор к отправке. Dispatcher берёт свежее Pending, retry-worker — Failed с наступившим NextAttemptAt и Attempts < MaxAttempts; уведомление Channel = Viber. Канал выключен (ChannelConfig.IsEnabled = false, заглушка, INV-CHN-2) → попытки нет, статус Pending, исход NTF_PROVIDER_NOT_IMPLEMENTED (skip, метрика channel-disabled).
  2. Резолв адреса. RecipientAddress.PhoneE164 (Business Messages) или MessengerHandle (public-account подписчик) → receiver. Адрес пуст на момент попытки → уведомление ждёт апдейта реплики контактов/retry (graceful); receiver невалиден / получатель не в Viber → NTF_PROVIDER_INVALID_RECIPIENT (non-retriable, терминальный Failed).
  3. Сборка сообщения. RenderedTitle+RenderedBodytext (≤ 1000; усечение по MaxBodyLength, если задан, INV-CHN-3), DeepLink → keyboard-кнопка, clientReference = NotificationId (идемпотентность).
  4. Отправка (синхронно, timeout 5с). Повтор той же отправки (clientReference = NotificationId) не создаёт второе сообщение.
  5. Классификация ответа:
    • принято BSP → Pending/Failed → Sent, ProviderMessageId; вклад в NotificationDispatchedEvent(Sent) (топик pin.notifications.dispatched.v1); при недоставке в Viber возможен sms-fallback вендора — отдельный billing-исход, для агрегата это по-прежнему Sent;
    • 5xx/timeout → NTF_PROVIDER_TRANSIENT (retriable): → Failed, Attempts++, NextAttemptAt = now + backoff (ChannelConfig.RetryBackoff);
    • 429NTF_PROVIDER_RATE_LIMITED (retriable): дефер по backoff;
    • invalid receiver / no viber → NTF_PROVIDER_INVALID_RECIPIENT (non-retriable): терминальный Failed.
  6. Порог повторов. Attempts >= MaxAttempts (INV-NTF-7) → терминальный Failed + NotificationDispatchedEvent(Failed) + алерт; запись выбывает из retry-цикла.

Обратный вызов и согласованность

Delivery/seen webhook приходит асинхронно, позже синхронного приёма BSP:

  • по clientReference = NotificationId применяется delivery-исход (sent/delivered/seen/failedSent/Failed); webhook по неизвестному NotificationId игнорируется (лог).
  • Повтор того же webhook идемпотентен: терминальный статус не откатывается (INV-NTF-5); seen после delivered не меняет Sent.
  • Окно согласованности: Sent по приёму BSP предшествует финальному webhook-у; failed-webhook может переоткрыть уведомление в Failed и вернуть в retry-цикл.

Пред- и постусловия

  • Предусловие: канал Viber включён; есть уведомление Channel = Viber в Pending/Failed(due); RecipientAddress.PhoneE164/MessengerHandle резолвится (или догоняется апдейтом реплики контактов/retry).
  • Постусловие: уведомление в терминальном Sent (есть ProviderMessageId) либо Failed (терминально после MaxAttempts или invalid receiver); на финальном исходе эмитирован NotificationDispatchedEvent.

Кросс-сервисные ожидания и гарантии

НаправлениеКонтракт
Ожидает от ChannelConfig (channel.md)IsEnabled, ProviderKey, MaxAttempts, RetryBackoff, RateLimitPerMinute, MaxBodyLength
Ожидает от рендера шаблонанепустой RenderedBody (INV-NTF-2); опц. RenderedTitle/DeepLink
Ожидает от адресаRecipientAddress.PhoneE164/MessengerHandle; пусто → ждать апдейта реплики контактов/retry, невалиден → терминальный Failed
Предоставляет агрегатупереход статуса Sent/Failed, ProviderMessageId, LastError (безопасный код)
Предоставляет наружувклад в NotificationDispatchedEvent (ключ партиционирования NotificationId, ContractVersion = 1)
Репликиадрес — снапшот из контактной реплики RecipientContactEntity (ведётся из identity/profile); отставание реплики → адрес пуст, догоняется на retry (не потеря)

Acceptance Criteria

#Given / When / Then
AC1Given канал Viber выключен, When dispatcher берёт Pending, Then попытки нет, статус Pending, метрика channel-disabled (NTF_PROVIDER_NOT_IMPLEMENTED).
AC2Given валидный receiver и канал включён, When BSP принял, Then Sent + ProviderMessageId + вклад в NotificationDispatchedEvent(Sent).
AC3Given 5xx/429, When обработка ответа, Then Failed, Attempts++, NextAttemptAt по backoff; повтор до успеха/MaxAttempts.
AC4Given MaxAttempts исчерпан, When очередная ошибка, Then терминальный Failed + NotificationDispatchedEvent(Failed) + алерт.
AC5Given невалидный receiver / не в Viber, When ответ BSP, Then терминальный Failed (NTF_PROVIDER_INVALID_RECIPIENT), без повторов.
AC6Given недоставка в Viber с sms-fallback вендора, When BSP подтвердил доставку, Then для агрегата статус Sent (fallback — отдельный billing-исход).
AC7Given delivery/seen webhook по известному NotificationId, When обработка callback, Then статус приведён к webhook-исходу; повтор идемпотентен.

Наблюдаемость

ТипПравило
Logssend-started/sent/provider-error/webhook-received/signature-failure (телефон/тело — маска)
Metricsnotifications_viber_sent_total, notifications_viber_error_rate, notifications_viber_retry_total, circuit-state
TracescorrelationId, NotificationId, provider message id
Redactionapi-key/secret и raw телефон/тело — исключены

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы