feat(lk-contract): OpenAPI контракт lk-gateway по ESIA Finance API V1

- docs/lk-contract/v1/openapi.yaml — OpenAPI 3.0: POST/GET/PATCH /api/v1/back_office/claims, схемы Claim/CreateClaimRequest/StatusCallback/ErrorResponse
- docs/lk-contract/v1/examples/claim-request.json — заявка с 3 ЦБ, ИИС T03
- docs/lk-contract/v1/examples/claim-response.json — ответ на создание
- docs/lk-contract/v1/examples/callback-confirmed.json — callback подтверждения
- docs/lk-contract/v1/examples/callback-rejected.json — callback отказа
- docs/lk-contract/v1/examples/error-422.json — ошибка валидации
- docs/lk-contract/v1/changelog.md — v1.0.0

Контракт предлагается команде реального ЛК как точка синхронизации.
В lk-emulator (отдельный PR) контракт реализуется как «как-будто-ЛК»
для проверки сквозного потока.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
fontvielle
2026-05-14 00:47:18 +03:00
parent 93bcbca12c
commit a040f8b07d
9 changed files with 911 additions and 13 deletions
+52 -12
View File
@@ -1,18 +1,58 @@
# docs/lk-contract/v1 — контракт с ЛК клиента (ESIA Finance)
# docs/lk-contract/v1 — контракт с ЛК клиента (ESIA Finance API V1)
ЛК клиента работает на платформе **ESIA Finance**, контракт описан
в `DOC/API ЛК ЕСИА.pdf` (`/api/v1/back_office/...`, Basic HTTP, JSON,
ЛК клиента работает на платформе **ESIA Finance**, контракт описан в
`DOC/API ЛК ЕСИА.pdf` (`/api/v1/back_office/...`, Basic HTTP, JSON,
UTF-8).
На этапе M1 в `lk-emulator` мы воспроизводим этот контракт для запуска
сквозного потока. Реальный ЛК подключится по тому же контракту, без
правок на нашей стороне.
На этапе M1 в `lk-emulator` (отдельный PR) мы реализуем этот контракт
как «как-будто-ЛК» для запуска сквозного потока. Реальный ЛК
подключится по тому же контракту без правок на нашей стороне.
В этом каталоге будут:
## Состав каталога
- `openapi.yaml` наш OpenAPI-контракт `lk-gateway`, согласованный
с командой ЛК.
- `examples/` — примеры заявлений и ответов.
- `changelog.md` — версионирование контракта.
- **`openapi.yaml`** — OpenAPI 3.0 контракт lk-gateway. Описывает
четыре операции: создание, чтение, callback статуса и список заявок.
Модель `Claim` включает все поля, нужные m2m-core для формирования
`M2MTransferRequest`.
- **`examples/`**:
- `claim-request.json` — пример заявки на перевод (3 ЦБ, ИИС T03).
- `claim-response.json` — пример ответа на создание.
- `callback-confirmed.json` — callback подтверждения (status_code
INFO, 3 коды 01).
- `callback-rejected.json` — callback отказа (status_code ERROR).
- `error-422.json` — ошибка валидации подписи.
- **`changelog.md`** — версионирование контракта.
Реализация — задача M1.
## Что входит в модель заявки
- Идентификация инвестора (UUID в ЛК, ФИО, документ).
- Реквизиты передающего и принимающего депозитариев (ИНН).
- Информация об учёте стоимости (`cost_info: yes | no`).
- Опциональный блок ИИС (тип T12/T03, номер договора, дата, ИНН брокера).
- Массив ценных бумаг (1..N), каждая с:
- `security_code` (НРД-код, 12 символов),
- идентификацией (`isin` или развёрнутый `security_info`),
- количеством (целое `whole` или дробное `fractional` до 16 знаков),
- списком счетов депо (`settlement_accounts[]`).
- Подписанный XML заявления (base64) и формат подписи
(XMLDSig-GOST или XMLDSig-RSA).
## Что входит в callback статуса
- `claim_id`, `new_status`, `updated_at`.
- Для `rejected`/`timed_out`: код и текст причины из ответа НРД.
- Полное `nsd_response` (опц., для аудита).
## Порядок согласования
1. Передать команде ЛК ссылку на эту папку (тег `lk-contract-v1`).
2. Обсудить базовый URL, авторизацию (Basic, через VPN), окна.
3. Запустить `lk-emulator` на нашей стороне как опорную реализацию.
4. После приёмки — поднимать реальную интеграцию.
## Принципы
- OpenAPI 3.0, валидный по spectral / openapi-cli.
- Operation IDs в snake_case.
- Описания на русском, имена полей на английском.
- Enum'ы значений M2M — буквально как в XSD НРД (T12/T03, BOND/SHAR/MFUN, ...).
+34
View File
@@ -0,0 +1,34 @@
# Changelog контракта lk-gateway
## v1.0.0 (2026-05-14)
Первая опубликованная версия контракта. Соответствует ESIA Finance
API V1 (`DOC/API ЛК ЕСИА.pdf`).
Поддерживаемые операции:
- `POST /api/v1/back_office/claims/` — создание заявки.
- `GET /api/v1/back_office/claims` — список с фильтрами.
- `GET /api/v1/back_office/claims/{id}` — деталь.
- `PATCH /api/v1/back_office/claims/{id}` — callback статуса.
Модели:
- `Claim` — заявка с массивом `securities[]` (1..N ЦБ).
- `CreateClaimRequest` — входное тело создания.
- `StatusCallback` — обновление статуса с `nsd_response` для аудита.
- `ErrorResponse` — формат идентичен ESIA Finance V1.
Совместимость:
- HTTP Basic-auth.
- UTF-8, JSON.
- Поля enum — буквально как в XSD M2M (T12/T03, BOND/SHAR/MFUN,
ORDN/PREF/UKWN, INFO/ERROR).
## Принципы версионирования
- Несовместимые изменения — `v2/`, `v3/` (новая папка, отдельный
changelog).
- Совместимые добавления — minor-версия в этом файле.
- Документация исправлений — patch-версия в этом файле.
@@ -0,0 +1,26 @@
{
"claim_id": "c02a1d5e-c2af-4799-bab4-953f133c5133",
"new_status": "confirmed",
"updated_at": "2026-03-02T14:38:12Z",
"nsd_response": {
"guid": "c02a1d5e-c2af-4799-bab4-953f133c5133",
"status_code": "INFO",
"responses": [
{
"reference_id": "M2M2026030200001",
"code": "01",
"text": "Запрос на перевод принят и подтверждён принимающей стороной."
},
{
"reference_id": "M2M2026030200002",
"code": "01",
"text": "Запрос на перевод принят и подтверждён принимающей стороной."
},
{
"reference_id": "M2M2026030200003",
"code": "01",
"text": "Запрос на перевод принят и подтверждён принимающей стороной."
}
]
}
}
@@ -0,0 +1,17 @@
{
"claim_id": "c02a1d5e-c2af-4799-bab4-953f133c5133",
"new_status": "rejected",
"reason_code": "07",
"reason_text": "Не найдена сделка с таким GUID на стороне принимающего депозитария.",
"updated_at": "2026-03-02T14:40:00Z",
"nsd_response": {
"guid": "c02a1d5e-c2af-4799-bab4-953f133c5133",
"status_code": "ERROR",
"responses": [
{
"code": "07",
"text": "Не найдена сделка с таким GUID."
}
]
}
}
@@ -0,0 +1,104 @@
{
"investor": {
"id": "11111111-1111-1111-1111-111111111111",
"last_name": "Иванов",
"first_name": "Иван",
"middle_name": "Иванович",
"document": {
"document_type": "21",
"series": "4512",
"number": "654321"
}
},
"transferring_depository_inn": "0702345678",
"receiving_depository_inn": "0710987654",
"cost_info": {
"yes": {
"code": "MC0010300032"
}
},
"iia_agreement": {
"agreement_type": "T03",
"agreement_number": "ИИС78/2024",
"agreement_date": "2026-01-15",
"broker_inn": "0707083893"
},
"securities": [
{
"security_code": "MM0766162534",
"security_details": {
"isin": "RU0007661625"
},
"quantity": {
"whole": 1500
},
"settlement_accounts": [
{
"settlement_requisites_inn": "7702070139",
"settlement_location": {
"deponent_code": "DP789456",
"account_id": "31MC0021900000F01",
"section_id": "P001"
}
},
{
"settlement_requisites_inn": "7802031669",
"settlement_location": {
"deponent_code": "AA789451",
"account_id": "33MC0021900000F02",
"section_id": "F002"
}
}
]
},
{
"security_code": "MM0907654321",
"security_details": {
"isin": "RU0009029540"
},
"quantity": {
"whole": 300
},
"settlement_accounts": [
{
"settlement_requisites_inn": "7702070139",
"settlement_location": {
"deponent_code": "DP789456",
"account_id": "31MC0021900000F01",
"section_id": "P001"
}
}
]
},
{
"security_code": "MM2300100100",
"security_details": {
"security_info": {
"classification": "MFUN",
"category": "UKWN",
"identification_details": {
"fund_shares": {
"reg_number": "23-001",
"class": "A"
}
}
}
},
"quantity": {
"fractional": "2500.75"
},
"settlement_accounts": [
{
"settlement_requisites_inn": "7702070139",
"settlement_location": {
"deponent_code": "DP789456",
"account_id": "31MC0021900000F01",
"section_id": "P001"
}
}
]
}
],
"signed_document": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0id2luZG93cy0xMjUxIj8+...base64-XML...",
"signature_format": "XMLDSig-GOST"
}
@@ -0,0 +1,6 @@
{
"id": "c02a1d5e-c2af-4799-bab4-953f133c5133",
"status": "submitted",
"created_at": "2026-03-02T14:30:45Z",
"success": true
}
@@ -0,0 +1,15 @@
{
"error": true,
"status": 422,
"code": "invalid_signature",
"title": "Подпись заявления не прошла проверку",
"meta": {
"message": "Сертификат подписанта недействителен или цепочка доверия не построена.",
"errors": [
{
"field": "signed_document",
"message": "XMLDSig: certificate chain not trusted (signer CN = ИВАНОВ И.И.)."
}
]
}
}
+656
View File
@@ -0,0 +1,656 @@
openapi: 3.0.3
info:
title: lk-gateway API
version: 1.0.0
description: |
REST-контракт между сервисом `lk-gateway` (Bridge-and-Join-s) и ЛК
инвестора на платформе ESIA Finance. Версия V1 совместима с
официальным API ESIA Finance (`DOC/API ЛК ЕСИА.pdf`).
Контракт обслуживает жизненный цикл заявки M2M-перевода: создание,
получение, обновление статуса и список заявок.
Аутентификация — HTTP Basic. Кодировка — UTF-8. Тело запросов и
ответов — JSON.
servers:
- url: https://lk-gateway.bridge-and-joins.local
description: Production lk-gateway
- url: http://localhost:8080
description: Локальный эмулятор (lk-emulator)
security:
- basicAuth: []
paths:
/api/v1/back_office/claims/:
post:
operationId: create_claim
summary: Создать заявку на M2M-перевод
description: |
Принимает подписанное (XMLDSig) заявление инвестора. Сервис
проверяет подпись через crypto-service, валидирует данные,
создаёт сделку и инициирует отправку в НРД.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateClaimRequest'
examples:
full_claim:
summary: Заявка с тремя ЦБ, ИИС T03
externalValue: ./examples/claim-request.json
responses:
'201':
description: Заявка создана
content:
application/json:
schema:
$ref: '#/components/schemas/CreateClaimResponse'
'400':
description: Невалидные входные данные
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Не авторизован
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: Подпись неверна или данные не прошли бизнес-валидацию
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/v1/back_office/claims:
get:
operationId: list_claims
summary: Список заявок
description: Возвращает список заявок с фильтрацией по статусу, периоду и инвестору.
parameters:
- name: status
in: query
description: Фильтр по статусу.
required: false
schema:
$ref: '#/components/schemas/ClaimStatus'
- name: investor_id
in: query
description: UUID инвестора в ЛК.
required: false
schema:
type: string
format: uuid
- name: created_from
in: query
description: Нижняя граница периода создания (ISO 8601, UTC).
required: false
schema:
type: string
format: date-time
- name: created_to
in: query
description: Верхняя граница периода создания (ISO 8601, UTC).
required: false
schema:
type: string
format: date-time
- name: limit
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 200
default: 50
- name: offset
in: query
required: false
schema:
type: integer
minimum: 0
default: 0
responses:
'200':
description: Страница списка заявок
content:
application/json:
schema:
$ref: '#/components/schemas/ClaimsPage'
/api/v1/back_office/claims/{id}:
get:
operationId: get_claim
summary: Получить заявку и её статус
parameters:
- name: id
in: path
required: true
description: UUID заявки.
schema:
type: string
format: uuid
responses:
'200':
description: Заявка
content:
application/json:
schema:
$ref: '#/components/schemas/Claim'
'404':
description: Заявка не найдена
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
patch:
operationId: update_claim_status
summary: Callback обновления статуса (от lk-gateway к ЛК)
description: |
Используется лгатвей-ом для уведомления ЛК о смене статуса
сделки на стороне НРД. Подтверждение, отказ или таймаут.
parameters:
- name: id
in: path
required: true
description: UUID заявки.
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/StatusCallback'
examples:
confirmed:
summary: Подтверждение
externalValue: ./examples/callback-confirmed.json
rejected:
summary: Отказ
externalValue: ./examples/callback-rejected.json
responses:
'200':
description: Callback принят
content:
application/json:
schema:
$ref: '#/components/schemas/CallbackResponse'
'404':
description: Заявка не найдена
components:
securitySchemes:
basicAuth:
type: http
scheme: basic
schemas:
ClaimStatus:
type: string
description: |
Жизненный цикл заявки на M2M-перевод.
- `draft` — черновик, ещё не подписан.
- `signed` — подписан, но не отправлен.
- `submitted` — отправлен в НРД.
- `in_progress` — НРД принял, ждём решение от принимающей стороны.
- `confirmed` — подтверждён, перевод исполнен.
- `rejected` — отклонён.
- `timed_out` — превышен SLA, ручной разбор.
enum:
- draft
- signed
- submitted
- in_progress
- confirmed
- rejected
- timed_out
SignatureFormat:
type: string
description: Тип цифровой подписи заявления.
enum:
- XMLDSig-GOST
- XMLDSig-RSA
AgreementType:
type: string
description: |
Тип договора ИИС.
- `T12` — ИИС-1 или ИИС-2 (старый формат).
- `T03` — ИИС-3 (новый).
enum:
- T12
- T03
SecurityClassification:
type: string
description: Тип ценной бумаги.
enum:
- BOND
- SHAR
- MFUN
SecurityCategory:
type: string
description: Категория акций.
enum:
- ORDN
- PREF
- UKWN
Investor:
type: object
description: Анкета инвестора.
required:
- last_name
- first_name
- document
properties:
id:
type: string
format: uuid
description: UUID инвестора в ЛК (если уже известен).
last_name:
type: string
maxLength: 50
example: Иванов
first_name:
type: string
maxLength: 50
example: Иван
middle_name:
type: string
maxLength: 50
example: Иванович
document:
$ref: '#/components/schemas/IdentityDocument'
IdentityDocument:
type: object
description: Документ, удостоверяющий личность.
required:
- document_type
- number
properties:
document_type:
type: string
pattern: '^(0[1-7]|09|1[0-4]|2[1-37]|26|91)$'
description: Код документа по справочнику НРД.
example: '21'
series:
type: string
pattern: '^\S+$'
example: '4512'
number:
type: string
pattern: '^\S+$'
example: '654321'
Quantity:
type: object
description: Количество ценных бумаг — choice (ровно одно поле).
properties:
whole:
type: integer
format: int64
minimum: 1
example: 1500
fractional:
type: string
description: Десятичная строка с не более 16 знаками после точки.
pattern: '^[0-9]+(\.[0-9]{1,16})?$'
example: '2500.75'
FundShares:
type: object
required:
- reg_number
properties:
reg_number:
type: string
maxLength: 256
example: '23-001'
class:
type: string
maxLength: 120
example: A
IdentificationDetails:
type: object
description: Идентификация ЦБ — choice (ровно одно поле).
properties:
reg_number:
type: string
maxLength: 20
fund_shares:
$ref: '#/components/schemas/FundShares'
SecurityInfo:
type: object
description: Описание ЦБ при отсутствии ISIN.
required:
- classification
- category
- identification_details
properties:
classification:
$ref: '#/components/schemas/SecurityClassification'
category:
$ref: '#/components/schemas/SecurityCategory'
security_type:
type: string
maxLength: 256
security_series:
type: string
identification_details:
$ref: '#/components/schemas/IdentificationDetails'
SecurityDetails:
type: object
description: Идентификация ЦБ — choice (ровно одно поле).
properties:
isin:
type: string
pattern: '^[A-Z]{2}[A-Z0-9]{9}[0-9]$'
example: RU0007661625
security_info:
$ref: '#/components/schemas/SecurityInfo'
SettlementLocation:
type: object
required:
- deponent_code
- account_id
- section_id
properties:
deponent_code:
type: string
maxLength: 50
example: DP789456
account_id:
type: string
maxLength: 50
example: 31MC0021900000F01
section_id:
type: string
maxLength: 50
example: P001
SettlementAccount:
type: object
required:
- settlement_requisites_inn
- settlement_location
properties:
settlement_requisites_inn:
type: string
pattern: '^[0-9]{10}$'
example: '7702070139'
settlement_location:
$ref: '#/components/schemas/SettlementLocation'
ClaimSecurity:
type: object
required:
- security_code
- security_details
- quantity
- settlement_accounts
properties:
security_code:
type: string
pattern: '^[0-9A-Z_/-]{12}$'
example: MM0766162534
security_details:
$ref: '#/components/schemas/SecurityDetails'
quantity:
$ref: '#/components/schemas/Quantity'
settlement_accounts:
type: array
minItems: 1
items:
$ref: '#/components/schemas/SettlementAccount'
CostInfo:
type: object
description: |
Информация об учёте стоимости приобретения. Choice: либо
`yes` (с кодом депонента-источника), либо `no` (учёт не ведётся).
properties:
yes:
type: object
required: [code]
properties:
code:
type: string
pattern: '^[A-Z0-9]+$'
maxLength: 12
example: MC0010300032
no:
type: object
description: Пустой объект — учёт не ведётся.
IIAAgreement:
type: object
description: Реквизиты договора ИИС (нужно, если перевод идёт по ИИС).
required:
- agreement_type
- agreement_number
- agreement_date
- broker_inn
properties:
agreement_type:
$ref: '#/components/schemas/AgreementType'
agreement_number:
type: string
maxLength: 128
example: ИИС78/2024
agreement_date:
type: string
format: date
example: '2026-01-15'
broker_inn:
type: string
pattern: '^[0-9]{10}$'
example: '0707083893'
CreateClaimRequest:
type: object
required:
- investor
- transferring_depository_inn
- receiving_depository_inn
- cost_info
- securities
- signed_document
- signature_format
properties:
investor:
$ref: '#/components/schemas/Investor'
transferring_depository_inn:
type: string
pattern: '^[0-9]{10}$'
receiving_depository_inn:
type: string
pattern: '^[0-9]{10}$'
cost_info:
$ref: '#/components/schemas/CostInfo'
iia_agreement:
$ref: '#/components/schemas/IIAAgreement'
securities:
type: array
minItems: 1
items:
$ref: '#/components/schemas/ClaimSecurity'
signed_document:
type: string
format: byte
description: Подписанный XML заявления в base64.
signature_format:
$ref: '#/components/schemas/SignatureFormat'
CreateClaimResponse:
type: object
required: [id, status, created_at, success]
properties:
id:
type: string
format: uuid
status:
$ref: '#/components/schemas/ClaimStatus'
created_at:
type: string
format: date-time
success:
type: boolean
example: true
Claim:
type: object
description: Полная сущность заявки.
required:
- id
- status
- created_at
- updated_at
- investor
- transferring_depository_inn
- receiving_depository_inn
- securities
properties:
id:
type: string
format: uuid
status:
$ref: '#/components/schemas/ClaimStatus'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
investor:
$ref: '#/components/schemas/Investor'
transferring_depository_inn:
type: string
pattern: '^[0-9]{10}$'
receiving_depository_inn:
type: string
pattern: '^[0-9]{10}$'
cost_info:
$ref: '#/components/schemas/CostInfo'
iia_agreement:
$ref: '#/components/schemas/IIAAgreement'
securities:
type: array
items:
$ref: '#/components/schemas/ClaimSecurity'
last_callback:
$ref: '#/components/schemas/StatusCallback'
ClaimsPage:
type: object
required: [items, total, limit, offset]
properties:
items:
type: array
items:
$ref: '#/components/schemas/Claim'
total:
type: integer
minimum: 0
limit:
type: integer
offset:
type: integer
StatusCallback:
type: object
description: Callback обновления статуса от lk-gateway к ЛК.
required:
- claim_id
- new_status
- updated_at
properties:
claim_id:
type: string
format: uuid
new_status:
$ref: '#/components/schemas/ClaimStatus'
reason_code:
type: string
maxLength: 6
description: Код причины (для rejected/timed_out) из M2MTransferResponse или M2MTransferDecision.
example: '01'
reason_text:
type: string
maxLength: 1024
updated_at:
type: string
format: date-time
nsd_response:
type: object
description: Оригинал ответа НРД (необязательно, для аудита).
properties:
guid:
type: string
format: uuid
status_code:
type: string
enum: [INFO, ERROR]
responses:
type: array
items:
type: object
properties:
reference_id:
type: string
pattern: '^M2M[A-Z0-9]{13}$'
code:
type: string
text:
type: string
CallbackResponse:
type: object
required: [success]
properties:
success:
type: boolean
ErrorResponse:
type: object
description: Формат ошибки, идентичный API ESIA Finance V1.
required: [error, status]
properties:
error:
type: boolean
example: true
status:
type: integer
example: 422
code:
type: string
example: invalid_signature
title:
type: string
example: Подпись не прошла проверку
meta:
type: object
properties:
message:
type: string
errors:
type: array
items:
type: object
properties:
field:
type: string
message:
type: string
+1 -1
View File
@@ -13,7 +13,7 @@ PR-1 → PR-N. Каждая задача — самостоятельный ос
|----|------|--------|-----------|
| PR-1 | `PR-1-go-models-m2m.md` | выполнено | — |
| PR-2 | `PR-2-fansy-ddl.md` | выполнено | — (параллельно с PR-1) |
| PR-3 | `PR-3-lk-openapi.md` | готово к запуску | — (параллельно с PR-1) |
| PR-3 | `PR-3-lk-openapi.md` | выполнено | — (параллельно с PR-1) |
| PR-4 | `PR-4-m2m-core-skeleton.md` | готово к запуску | PR-1 |
| PR-5 | `PR-5-nsd-adapter-skeleton.md` | ждёт ИШ НРД и сертификаты | PR-1, PR-4 |
| PR-6 | `PR-6-crypto-service-skeleton.md` | ждёт КриптоПро JCP | PR-1 |