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

Вспомогательные сервисы

Монорепозиторий Univex содержит три вспомогательных микросервиса: email, sms-gate и sumsub-integration. Каждый реализован как самостоятельный сервис со своей базой данных и транспортным слоем.


Email Service

Назначение

Сервис отправки электронных писем. Используется UnivexID для уведомлений: подтверждение регистрации, смена пароля, OTP-коды, уведомления о безопасности и т.д.

Архитектура

┌──────────────────────────────────────────────┐
│              gRPC Server                     │
│  SendMessage (приём запросов напрямую)       │
└────────────────┬─────────────────────────────┘
┌────────────────▼─────────────────────────────┐
│              SQS Queue Worker                │
│  Читает задания на отправку из очереди       │
│  (асинхронная обработка)                     │
└────────────────┬─────────────────────────────┘
┌────────────────▼─────────────────────────────┐
│              SMTP Client                     │
│  Отправка письма через SMTP-сервер           │
└──────────────────────────────────────────────┘

Сервис поддерживает два режима приёма задач: - gRPC: синхронный вызов SendMessage — помещает задачу в очередь SQS. - SQS Worker: асинхронный воркер читает задачи из очереди и выполняет отправку через SMTP.

База данных

2 миграции PostgreSQL:

Таблица Назначение
email_messages История отправленных писем и их статусы
email_templates Шаблоны писем

gRPC-контракт

Прото-файл: proto/email_service.proto

service EmailService {
    rpc SendMessage(SendMessageRequest) returns (SendMessageResponse);
}

message SendMessageRequest {
    string email   = 1;  // Email-адрес получателя
    string message = 2;  // Тело письма (HTML или plain text)
    string subject = 3;  // Тема письма
}

message SendMessageResponse {
    // Пустой ответ — успех подтверждается кодом статуса gRPC
}

Коды ошибок

Код Описание
INVALID_ARGUMENT Не указан email, тема или тело письма
INTERNAL Ошибка SMTP или внутренняя ошибка сервиса
UNAVAILABLE Сервис недоступен

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

Параметр Описание
DATABASE_URL DSN для подключения к PostgreSQL
GRPC_PORT Порт gRPC-сервера
SQS_QUEUE_URL URL очереди AWS SQS
SMTP_HOST Хост SMTP-сервера
SMTP_PORT Порт SMTP-сервера
SMTP_USER Логин SMTP
SMTP_PASSWORD Пароль SMTP
SMTP_FROM Email-адрес отправителя

SMS Gate

Назначение

Шлюз для отправки SMS-сообщений. Используется для двухфакторной аутентификации (2FA), подтверждения операций, а также уведомлений. Поддерживает несколько каналов доставки: AWS SNS и Telegram.

Архитектура

┌──────────────────────────────────────────────┐
│              gRPC Server                     │
│  SendMessage (помещает задачу в SQS)         │
└────────────────┬─────────────────────────────┘
┌────────────────▼─────────────────────────────┐
│              SQS Queue Worker                │
│  Читает задания на отправку из очереди       │
└──────────┬────────────────┬──────────────────┘
           │                │
┌──────────▼──────┐  ┌──────▼──────────────┐
│   AWS SNS       │  │   Telegram Bot      │
│   (SMS через    │  │   (резервный канал) │
│   провайдеров)  │  │                     │
└─────────────────┘  └─────────────────────┘

Сервис использует AWS SNS как основной канал доставки SMS и Telegram-бота как резервный (или альтернативный) канал для доставки кодов пользователям, у которых привязан Telegram.

База данных

PostgreSQL. Таблица sms_messages хранит историю отправленных сообщений и их статусы.

gRPC-контракт

Прото-файл: proto/sms_gate.proto

service SMSGate {
    rpc SendMessage(SendSMSRequest) returns (SendSMSResponse);
    rpc ResendMessage(ResendSMSRequest) returns (ResendSMSResponse);
    rpc GetStatus(GetSMSStatusRequest) returns (GetSMSStatusResponse);
}

message SendSMSRequest {
    string phone   = 1;  // Номер телефона получателя (формат E.164)
    string message = 2;  // Текст SMS-сообщения
}

message SendSMSResponse {
    string message_id = 1;  // Идентификатор отправленного сообщения
}

message ResendSMSRequest {
    string message_id = 1;  // Идентификатор сообщения для повторной отправки
}

message ResendSMSResponse {
    string message_id = 1;  // Идентификатор нового сообщения
}

message GetSMSStatusRequest {
    string message_id = 1;  // Идентификатор сообщения
}

message GetSMSStatusResponse {
    SMSStatus status = 1;
}

enum SMSStatus {
    SMS_STATUS_UNSPECIFIED = 0;
    SMS_STATUS_PENDING     = 1;  // Ожидает отправки
    SMS_STATUS_SENT        = 2;  // Отправлено оператору
    SMS_STATUS_DELIVERED   = 3;  // Доставлено получателю
    SMS_STATUS_FAILED      = 4;  // Ошибка доставки
}

Коды ошибок

Код Описание
INVALID_ARGUMENT Не указан телефон или текст сообщения
NOT_FOUND Сообщение с указанным message_id не найдено
INTERNAL Ошибка SMS-провайдера
UNAVAILABLE Сервис недоступен

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

Параметр Описание
DATABASE_URL DSN для подключения к PostgreSQL
GRPC_PORT Порт gRPC-сервера
SQS_QUEUE_URL URL очереди AWS SQS
AWS_REGION Регион AWS
AWS_ACCESS_KEY_ID Ключ доступа AWS
AWS_SECRET_ACCESS_KEY Секретный ключ AWS
TELEGRAM_BOT_TOKEN Токен Telegram-бота

Sumsub Integration

Назначение

Сервис интеграции с платформой KYC-верификации Sumsub. Предоставляет gRPC-интерфейс для получения токена доступа к виджету верификации, а также принимает входящие вебхуки от Sumsub через встроенный HTTP-сервер на Fiber.

Архитектура

┌────────────────────────────────────────────────┐
│              gRPC Server                       │
│  GetAccessToken  — токен для виджета           │
│  GetApplicantInformation — данные заявителя    │
│  GetDocumentImage — изображение документа      │
└────────────────────┬───────────────────────────┘
┌────────────────────▼───────────────────────────┐
│              Sumsub API Client                 │
│  Обращение к Sumsub REST API                  │
└────────────────────────────────────────────────┘

┌────────────────────────────────────────────────┐
│              Fiber HTTP Server                 │
│  POST /webhook — приём событий от Sumsub       │
│  (верификация подписи HMAC-SHA256)             │
└────────────────────┬───────────────────────────┘
┌────────────────────▼───────────────────────────┐
│              Webhook Handler                   │
│  Обработка TerminalStatusEvent                 │
│  Обновление статуса KYC в БД                   │
└────────────────────────────────────────────────┘

Процесс KYC-верификации

┌──────────────┐         ┌──────────────┐         ┌───────────────┐
│   Клиент     │         │   UnivexID   │         │    Sumsub     │
│  (браузер)   │         │   Service    │         │  Integration  │
└──────┬───────┘         └──────┬───────┘         └───────┬───────┘
       │                        │                         │
       │  Запрос начать KYC     │                         │
       │───────────────────────>│                         │
       │                        │  GetAccessToken(userId) │
       │                        │────────────────────────>│
       │                        │                         │
       │                        │  {access_token}         │
       │                        │<────────────────────────│
       │  {access_token}        │                         │
       │<───────────────────────│                         │
       │                        │                         │
       │  Инициализация         │                         │
       │  Sumsub Widget         │                         │
       │  с токеном             │                         │
       │                        │                         │
       │  Прохождение           │                         │
       │  верификации           │                         │
       │────────────────────────────────────────────────> │
       │                        │                         │
       │             POST /webhook (TerminalStatusEvent)  │
       │                        │<────────────────────────│

База данных

1 миграция PostgreSQL: таблица applicants хранит данные заявителей Sumsub, привязанные к user_id.

gRPC-контракт

Прото-файл: proto/sumsub_integration.proto

service SumsubIntegration {
    rpc GetAccessToken(GetAccessTokenRequest) returns (GetAccessTokenResponse);
    rpc GetApplicantInformation(GetApplicantInformationRequest) returns (GetApplicantInformationResponse);
    rpc GetDocumentImage(GetDocumentImageRequest) returns (GetDocumentImageResponse);
}

message GetAccessTokenRequest {
    string user_id  = 1;  // Идентификатор пользователя в системе Univex
    string level    = 2;  // Уровень верификации Sumsub (напр. "basic-kyc-level")
}

message GetAccessTokenResponse {
    string access_token = 1;  // Токен доступа для виджета Sumsub
    int64  expires_at   = 2;  // Unix timestamp истечения токена
}

message GetApplicantInformationRequest {
    string user_id = 1;  // Идентификатор пользователя в системе Univex
}

message GetApplicantInformationResponse {
    string applicant_id  = 1;  // Идентификатор заявки в Sumsub
    string first_name    = 2;  // Имя из документа
    string last_name     = 3;  // Фамилия из документа
    string date_of_birth = 4;  // Дата рождения (ISO 8601)
    string country       = 5;  // Код страны (ISO 3166-1 alpha-2)
    string kyc_status    = 6;  // Текущий статус верификации
}

message GetDocumentImageRequest {
    string user_id       = 1;  // Идентификатор пользователя в системе Univex
    string inspection_id = 2;  // Идентификатор инспекции в Sumsub
    string image_id      = 3;  // Идентификатор изображения
}

message GetDocumentImageResponse {
    bytes  image_data    = 1;  // Бинарные данные изображения
    string content_type  = 2;  // MIME-тип (напр. "image/jpeg")
}

Webhook: TerminalStatusEvent

При получении события от Sumsub сервис:

  1. Верифицирует HMAC-SHA256 подпись запроса.
  2. Десериализует событие TerminalStatusEvent.
  3. Обновляет статус KYC заявителя в БД.
  4. При необходимости — инициирует следующие шаги (уведомление UnivexID).
message TerminalStatusEvent {
    string applicant_id   = 1;  // Идентификатор заявки в Sumsub
    string user_id        = 2;  // Идентификатор пользователя в Univex
    string first_name     = 3;  // Имя из документа
    string last_name      = 4;  // Фамилия из документа
    string date_of_birth  = 5;  // Дата рождения (ISO 8601)
    string country        = 6;  // Код страны (ISO 3166-1 alpha-2)
    KYCResult result      = 7;  // Результат верификации
    string reject_reason  = 8;  // Причина отказа (если result = REJECTED)
}

enum KYCResult {
    KYC_RESULT_UNSPECIFIED = 0;
    KYC_RESULT_APPROVED    = 1;
    KYC_RESULT_REJECTED    = 2;
    KYC_RESULT_PENDING     = 3;
}

Коды ошибок gRPC

Код Описание
INVALID_ARGUMENT Не указан user_id или уровень верификации
NOT_FOUND Заявитель не найден
INTERNAL Ошибка при обращении к Sumsub API
UNAVAILABLE Sumsub API недоступен

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

Параметр Описание
DATABASE_URL DSN для подключения к PostgreSQL
GRPC_PORT Порт gRPC-сервера
HTTP_PORT Порт Fiber HTTP-сервера (webhook)
SUMSUB_APP_TOKEN API-токен Sumsub
SUMSUB_SECRET_KEY Секретный ключ для подписи запросов и верификации webhook
SUMSUB_BASE_URL Базовый URL Sumsub API

Сводная таблица вспомогательных сервисов

Сервис Транспорт БД Внешние зависимости RPC / Эндпоинты
email gRPC + SQS Worker PostgreSQL (2 миграции) SMTP, AWS SQS SendMessage
sms-gate gRPC + SQS Worker PostgreSQL AWS SNS, AWS SQS, Telegram Bot SendMessage, ResendMessage, GetStatus
sumsub-integration gRPC + Fiber webhook PostgreSQL (1 миграция) Sumsub API GetAccessToken, GetApplicantInformation, GetDocumentImage