diff --git a/internal/lkemulator/server.go b/internal/lkemulator/server.go index 67e1149..eb6b434 100644 --- a/internal/lkemulator/server.go +++ b/internal/lkemulator/server.go @@ -37,10 +37,44 @@ type Server struct { mux *http.ServeMux } +// templateFuncs — функции, доступные внутри шаблонов эмулятора +// (русификация статусов). +var templateFuncs = template.FuncMap{ + "ruState": ruState, +} + +// ruState — те же значения, что и в lkgateway.russianState. Дублирование +// допустимое: lkemulator — отдельный пакет и не зависит от lkgateway. +func ruState(s string) string { + switch s { + case "draft": + return "Черновик" + case "validated": + return "Валидирована" + case "submitted_to_nsd": + return "Отправлена в НРД" + case "awaiting_decision": + return "Ожидает решение" + case "confirmed": + return "Подтверждена" + case "awaiting_sub16": + return "Ожидает SUB16" + case "done": + return "Завершена" + case "rejected": + return "Отклонена" + case "timed_out": + return "Таймаут SLA" + case "manual_approval": + return "На ручном разборе" + } + return s +} + // NewServer собирает Server. func NewServer(cfg ServerConfig) (*Server, error) { parse := func(content string) (*template.Template, error) { - return template.ParseFS(tplFS, "web/templates/layout.html", "web/templates/"+content) + return template.New("layout.html").Funcs(templateFuncs).ParseFS(tplFS, "web/templates/layout.html", "web/templates/"+content) } home, err := parse("home.html") if err != nil { diff --git a/internal/lkemulator/web/templates/claim.html b/internal/lkemulator/web/templates/claim.html index 926a1f6..1dfcba9 100644 --- a/internal/lkemulator/web/templates/claim.html +++ b/internal/lkemulator/web/templates/claim.html @@ -1,6 +1,6 @@ {{define "content"}}
{{slice .Claim.ID 0 8}} · {{.Claim.Status}}{{slice .Claim.ID 0 8}} · {{ruState .Claim.Status}}{{.Claim.LastCallback.ReasonCode}} {{.Claim.LastCallback.ReasonText}}{{.TransferringDepositoryINN}}{{.ReceivingDepositoryINN}}{{slice .Claim.ID 0 8}} · {{.Claim.Status}}{{slice .Claim.ID 0 8}} · {{ruState .Claim.Status}}| Создана | {{.Claim.CreatedAt.Format "02.01.2006 15:04:05"}} | |||
| Обновлена | {{.Claim.UpdatedAt.Format "02.01.2006 15:04:05"}} | |||
| {{.State}} | +{{ruState .State}} | {{.EnteredAt.Format "15:04:05.000"}} | {{if .LeftAt}}{{.LeftAt.Format "15:04:05.000"}}{{else}}сейчас{{end}} | {{.Reason}} | @@ -74,7 +74,7 @@ {{range .Claim.M2MDecision.Securities}}
{{.ReferenceID}} |
- {{.Outcome}} | +{{ruOutcome .Outcome}} | {{range .RejectCodes}}{{.}} {{end}} |
| Статус | {{.Claim.LastCallback.NewStatus}} | ||
| Статус | {{ruState .Claim.LastCallback.NewStatus}} | ||
| Код причины | {{.Claim.LastCallback.ReasonCode}} {{.Claim.LastCallback.ReasonText}} | {{len .Securities}} | {{.TransferringDepositoryINN}} |
{{.ReceivingDepositoryINN}} |
- {{.Status}} | +{{ruState .Status}} | детали → | {{end}} diff --git a/internal/lkgateway/web/templates/admin_help.html b/internal/lkgateway/web/templates/admin_help.html new file mode 100644 index 0000000..d37fbdf --- /dev/null +++ b/internal/lkgateway/web/templates/admin_help.html @@ -0,0 +1,33 @@ +{{define "content"}} +
| Сценарий | СКЗИ | Цена (ориентир) |
|---|---|---|
| Проверка XMLDSig входящих от НРД и брокеров | КриптоПро CSP «Сервер» | ~30-50к ₽ (один раз) |
| Подпись пакетов в НРД (резервный канал WS ONYX) | КриптоПро CSP «Сервер» | включено |
| Подпись действий оператора в admin-ui | Рутокен ЭЦП 2.0 + лицензия CSP «Рабочее место» | ~3-5к ₽ железо + ~2-3к ₽ лицензия |
| Проверка XMLDSig заявлений от ЛК | КриптоПро CSP «Сервер» | включено |
Если используется Интеграционный шлюз НРД (ИШ), он сам подписывает пакеты — наша серверная подпись нужна только для резервного канала ONYX и подписи действий оператора. Можно начать с минимума: только Рутокен оператора и отложить серверную лицензию.
+Скачать дистрибутив с www.cryptopro.ru (Linux x86_64).
sudo rpm -i cprocsp-rdr-gui-gtk-64-5.0.*.rpm \ + cprocsp-rdr-64-5.0.*.rpm \ + cprocsp-ca-certs-64-5.0.*.rpm \ + lsb-cprocsp-base-5.0.*.rpm \ + lsb-cprocsp-rdr-64-5.0.*.rpm+
Активация лицензии:
+sudo /opt/cprocsp/sbin/amd64/cpconfig -license -set XXXX-XXXXX-XXXXX-XXXXX-XXXXX+
Проверка:
+/opt/cprocsp/sbin/amd64/cpconfig -license -view +/opt/cprocsp/bin/amd64/csptest -keyset -enum -unique+
sudo dpkg -i cprocsp-rdr-gui-gtk-64_5.0.*_amd64.deb \ + cprocsp-rdr-64_5.0.*_amd64.deb \ + lsb-cprocsp-base_5.0.*_all.deb \ + lsb-cprocsp-rdr-64_5.0.*_amd64.deb +sudo apt-get install -f +sudo /opt/cprocsp/sbin/amd64/cpconfig -license -set XXXX-XXXXX-XXXXX-XXXXX-XXXXX+
Путь к библиотеке после установки:
+/opt/cprocsp/lib/amd64/libcppkcs11.so+
Эта же библиотека работает и с CSP-ключами (контейнеры на диске или в реестре), и с Рутокен ЭЦП 2.0 (подключённым по USB или в виде smart-card reader).
+На странице «Настройка» в карточке «Криптография» укажите:
+cryptopro/run/bj/crypto.sock (для legacy crypto-service на Java — на M2+ переходим на Go-клиент напрямую через PKCS#11)/opt/cprocsp/lib/amd64/libcppkcs11.soПодключите Рутокен в USB. Драйверы КриптоПро CSP уже включают поддержку Рутокен:
+# увидеть подключённые токены +/opt/cprocsp/bin/amd64/csptest -card -enum + +# увидеть ключевые контейнеры на токене +/opt/cprocsp/bin/amd64/csptest -keyset -enum -unique+
Для подписи действий оператора в admin-ui:
+cryptopro и указать слот Рутокен.# сертификат корневого УЦ (если ещё нет в системе) +/opt/cprocsp/bin/amd64/certmgr -inst -store mroot -file /path/to/root-ca.cer + +# сертификат подписанта (контейнер на токене) +/opt/cprocsp/bin/amd64/certmgr -inst -store uMy -cont '\\.\HDIMAGE\my-keys' \ + -file /path/to/operator.cer + +# проверить установленные сертификаты +/opt/cprocsp/bin/amd64/certmgr -list -store uMy+
Через CLI КриптоПро (быстрая проверка что криптография работает):
+# подписать произвольный файл +/opt/cprocsp/bin/amd64/cryptcp -signf -dn 'CN=Иванов И.И.' \ + -det -strict /tmp/test.txt + +# проверить подпись +/opt/cprocsp/bin/amd64/cryptcp -vsignf -det /tmp/test.txt /tmp/test.txt.sgn+
Через нашу систему — раздел Настройка → кнопка «Запустить тестовую заявку». На странице «Заявка» появится результат и расшифровка проверки подписи.
+www.cryptopro.ru/products/cspwww.cryptopro.ru/forum2/default.aspx?g=topics&f=43support@cryptopro.rudev.rutoken.ru/display/PUB/Rutoken+EDSПри проблемах с лицензией сначала проверьте cpconfig -license -view — лицензия должна быть валидна и не просрочена. Срок действия КриптоПро лицензии — обычно 1 год.
PostgreSQL 16 (или PostgreSQL Pro Certified). Хранит сделки, журнал событий, справочники и данные, поступающие из внешних систем через ETL.
+DSN указывается в разделе Настройка → PostgreSQL. Формат:
+postgres://USER:PASSWORD@HOST:PORT/DBNAME?sslmode=disable+
Локальный дев-стенд:
+postgres://bj:bj_dev@127.0.0.1:5432/bj?sslmode=disable+
При сохранении выполняется pgx.Connect и Ping. Если БД недоступна, форма покажет ошибку.
| Схема | Назначение | Кто пишет | Кто читает |
|---|---|---|---|
fansy_staging |
+ Промежуточные таблицы для ETL Fansy | +Команда Fansy (роль fansy_etl) |
+ Наша процедура перелива в fansy |
+
fansy |
+ Рабочие данные: клиенты, документы, ИИС, депо-счета, портфели, справочники ЦБ и участников | +Наша процедура перелива (после валидации) | +Наша система (роль bj_reader): m2m-core при формировании заявок |
+
m2m_core |
+ Сделки M2M, журнал событий FSM | +bj-server | +bj-server, admin-ui | +
Команда Fansy через инкрементный UPSERT в staging-таблицы:
+| Таблица staging | SLA свежести | Что в ней |
|---|---|---|
clients | ≤ 5 минут | Анкеты инвесторов (ФИО, дата рождения, ИНН) |
client_documents | ≤ 5 минут | Документы (паспорт, ИНН и др.) |
iia_contracts | ≤ 5 минут | Договоры ИИС (тип T12/T03, номер, дата, ИНН брокера) |
depo_accounts | ≤ 5 минут | Депо-счета и разделы у разных депозитариев |
portfolios | ≤ 1 минута | Остатки ЦБ (целое/дробное количество, статус обособления) |
securities / participants | раз в сутки | Справочники ЦБ и участников MOST |
Полный data-dictionary: docs/fansy-contract/v1/data-dictionary.md. Требования к ETL: docs/fansy-contract/v1/etl-requirements.md.
Только в схему m2m_core:
deals — корневая запись сделки. Уникальность по guid = идемпотентность приёма заявок.deal_events — журнал FSM-событий (event sourcing).Старт сделки происходит при POST /api/v1/back_office/claims/. FSM проходит этапы:
draft → validated → submitted_to_nsd → awaiting_decision → + confirmed → awaiting_sub16 → done + ↘ rejected + ↘ timed_out + ↘ manual_approval+
| Роль | Права | Создаёт |
|---|---|---|
fansy_etl | SELECT/INSERT/UPDATE на fansy_staging.* | миграция 000__roles.sql |
bj_reader | SELECT на fansy.* | миграция 000__roles.sql |
bj_migrator | Владелец схем, DDL-права | миграция 000__roles.sql |
Пароли проставляются администратором БД через ALTER ROLE, в репозиторий не попадают.
Файлы лежат в migrations/fansy-store/ и migrations/m2m-core/. Применить локально:
cd /home/fontvielle/Bridge-and-Join-s +for f in migrations/fansy-store/*.sql migrations/m2m-core/*.sql; do + podman exec -i bj-postgres psql -U bj -d bj -v ON_ERROR_STOP=1 < "$f" +done+
Порядок:
+fansy-store/000__roles.sql — ролиfansy-store/001__schemas.sql — схемы и грантыfansy-store/002__working.sql — рабочая схемаfansy-store/003__staging.sql — stagingfansy-store/004__seed_participants.sql — справочник участников (НРД, БКС, Ренессанс, Альфа-Банк)m2m-core/001__deals.sql — сделки M2Mm2m-core/002__stages.sql — jsonb-колонка истории FSM-- Состояние сделок за последний час +SELECT state, count(*) FROM m2m_core.deals +WHERE created_at > now() - interval '1 hour' +GROUP BY state; + +-- Журнал событий по сделке +SELECT created_at, type, actor, payload +FROM m2m_core.deal_events +WHERE deal_id = '...' +ORDER BY created_at; + +-- Свежесть данных Fansy +SELECT 'portfolios' AS t, max(loaded_at) FROM fansy_staging.portfolios +UNION ALL SELECT 'clients', max(loaded_at) FROM fansy_staging.clients;+
Bridge-and-Join-s реализует контракт ESIA Finance V1 на стороне back-office. ЛК клиента отправляет нам заявку, мы возвращаем подтверждение и потом колбэк со статусом. Полная спецификация: docs/lk-contract/v1/openapi.yaml.
HTTP Basic. Учётные записи и пароли согласуются с командой ЛК.
+curl -u "lk-user:lk-password" \ + -H "Content-Type: application/json" \ + http://10.10.10.22:8080/api/v1/back_office/claims/+
На M2 (дев-стенд) аутентификация отключена; включится на M3 — параллельно с подключением реального ЛК.
+POST /api/v1/back_office/claims/
Тело — JSON, кодировка UTF-8. Минимально нужно: анкета инвестора, ИНН передающего и принимающего депозитария, информация о стоимости, массив ценных бумаг с количеством и счетами, подписанный XML заявления (base64).
+curl -X POST http://10.10.10.22:8080/api/v1/back_office/claims/ \ + -H "Content-Type: application/json" \ + -d @docs/lk-contract/v1/examples/claim-request.json+
Ответ 201 Created:
{
+ "id": "c02a1d5e-c2af-4799-bab4-953f133c5133",
+ "status": "submitted_to_nsd",
+ "created_at": "2026-03-02T14:30:45Z",
+ "success": true
+}
+GET /api/v1/back_office/claims/{id}
curl http://10.10.10.22:8080/api/v1/back_office/claims/c02a1d5e-c2af-4799-bab4-953f133c5133+
Возвращает полную карточку с историей FSM, ответом НРД и решением принимающей стороны (когда оно пришло).
+GET /api/v1/back_office/claims с query-фильтрами: status, investor_id, created_from, created_to, limit, offset.
curl "http://10.10.10.22:8080/api/v1/back_office/claims?status=confirmed&limit=20"+
Когда сделка меняет статус (подтверждена принимающей стороной, отклонена, или произошёл таймаут), bj-server делает PATCH {LK_BASE}/api/v1/back_office/claims/{id}:
{
+ "claim_id": "c02a1d5e-c2af-4799-bab4-953f133c5133",
+ "new_status": "confirmed",
+ "updated_at": "2026-03-02T14:38:12Z",
+ "nsd_response": {
+ "guid": "...",
+ "status_code": "INFO",
+ "responses": [{"reference_id": "M2M2026...", "code": "01", "text": "..."}]
+ }
+}
+ Адрес ЛК указывается в Настройка → Callback в ЛК или через переменную BJ_LK_CALLBACK_URL.
Любая ошибка возвращается в формате, идентичном ESIA Finance V1:
+{
+ "error": true,
+ "status": 422,
+ "code": "invalid_signature",
+ "title": "Подпись заявления не прошла проверку",
+ "meta": {
+ "message": "Сертификат подписанта недействителен или цепочка доверия не построена.",
+ "errors": [{"field": "signed_document", "message": "..."}]
+ }
+}
+На дев-стенде доступен lk-emulator на порту 8083 — имитация ЛК. Он сам регистрирует свой URL в bj-server как callback-приёмник.
+http://10.10.10.22:8083/ — журнал моих заявок (автообновление 3 сек)http://10.10.10.22:8083/new — форма «подать заявку» с предустановленными инвесторами из seed-данныхЛК должен подписать заявление XMLDSig (ГОСТ или RSA) и положить в поле signed_document (base64). Мы проверяем подпись через crypto-service — см. инструкцию по КриптоПро.
На M2 проверка подписи отключена (stub). На M3-M4 включится после подключения СКЗИ.
+Как Bridge-and-Join-s взаимодействует с тремя главными внешними сторонами: НРД (через ИШ), команда Fansy (ETL в БД), уведомления операторам.
+Основной канал отправки M2M-сообщений в НРД. ИШ сам подписывает пакеты ЭДО, нам криптография в этом канале не нужна.
+Профили (см. Настройка → Интеграционный шлюз НРД):
+| Профиль | Среда | Криптография |
|---|---|---|
guest-gost | Гостевой контур (без проверок) | ГОСТ Р 34.10-2012 |
guest-rsa | Гостевой контур | RSA |
test3-gost | Тестовый контур TEST3 | ГОСТ |
test3-rsa | TEST3 | RSA |
prod-gost | Продуктивный | ГОСТ |
prod-rsa | Продуктивный | RSA |
Что указать в Настройка → ИШ:
+test3-gost)http://localhost:8080 если ИШ установлен на той же ВМTEST3_GOST_CONTAINERБез настроенного ИШ система работает в mock-режиме: bj-server эмитирует синтетический Decision через 3 секунды для каждой заявки. Это удобно для дев-демо и не требует подключения к НРД.
+Документация по подключению: DOC/instr_podkl_stend_v3.pdf, DOC/Ссылки для доступа в тестовые контуры.pdf.
Команда Fansy на своей стороне настраивает ETL, который пишет в схему fansy_staging.* нашей БД. Мы переливаем оттуда в fansy.* после валидации.
Что от вас как заказчика нужно:
+docs/fansy-contract/v1/ (тег fansy-contract-v1).fansy_etl и передать команде Fansy. Пароль выдать через защищённый канал.pg_hba.conf на стороне PostgreSQL (только TLS, sslmode=verify-full).Полный контракт: docs/fansy-contract/v1/etl-requirements.md. Семантика полей: docs/fansy-contract/v1/data-dictionary.md.
В M3-M4 будет раздел Настройка → Уведомления. Архитектура — провайдеры-плагины с единым интерфейсом Notifier { Send(ctx, recipient, template, data) }:
| Провайдер | Назначение | Что вводить |
|---|---|---|
| SMTP | E-mail (внутренний или внешний сервер) | хост, порт, логин/пароль, from-адрес |
| Yandex Messenger (Yandex 360) | Корпоративный мессенджер. У заказчика уже есть готовый бот — дотягиваем его | API-token, webhook, chat-id / user-id |
| Telegram | Опционально для отдельных операторов | bot-token, chat-id |
| Mattermost / Rocket.Chat | Корпоративные мессенджеры (если используются) | webhook URL |
| WebSocket в admin-ui | Мгновенный push если оператор открыл вкладку | встроено, без настроек |
Логика маршрутизации: критичный этап (ручное согласование, >80% SLA, отказ НРД) → параллельно e-mail + Messenger + WS-push. Обычные события — только e-mail. Маршрутизация по ролям настраивается в UI.
+| Внешняя сторона | Что согласовать |
|---|---|
| НРД (Национальный расчётный депозитарий) | Тестовые сертификаты GUEST/TEST3, дистрибутив ИШ, доступ к личному кабинету УЦ НРД |
| Команда ЛК (ESIA Finance) | Базовый URL ЛК, Basic-auth учётные данные, очерёдность подключения (сначала эмулятор, потом реальный ЛК) |
| Команда Fansy | Контракт docs/fansy-contract/v1/, SLA, окна обслуживания, IP-allowlist |
| КриптоПро | Серийный номер лицензии CSP, актуальный дистрибутив, поддержка support@cryptopro.ru |
| Брокеры-контрагенты MOST | БКС (ИНН 5406121446), Ренессанс (7709258228), Альфа-Банк (7728168971) — уже в seed |
{{slice .ID 0 8}}