Накладення електронного підпису за допомогою Дія в Python
Головна ідея: розробити Python-сервіс, який дозволяє користувачам підписувати документи за допомогою Дія.Підпис із подальшим збереженням підписаного документа, файлу підпису, статусу підписання, журналу дій і результату перевірки підпису.
Критично важливо: система не повинна вважати документ підписаним тільки після відкриття QR-коду або переходу в застосунок Дія. Документ вважається підписаним лише після отримання підтвердженого результату підписання, збереження підпису та успішної перевірки цілісності.
Важливо: точні endpoint-и, формат callback, формат підписаного контейнера, параметри deep link / QR та правила взаємодії потрібно брати з офіційної документації Дії, яку надають після підключення партнера.
Технічний стек: Python 3.11+, FastAPI, PostgreSQL, SQLAlchemy, Alembic, httpx, Pydantic, Celery/RQ/APScheduler, Redis, Docker, S3-compatible file storage.
Управлінський результат: відповідальна особа повинна бачити, які документи очікують підпису, які підписані, які відхилені, які прострочені, які мають помилки callback, які потребують повтору або ручної перевірки.
1. Мета
Метою задачі є створення Python-сервісу для накладення електронного підпису за допомогою Дія.Підпис.
Сервіс повинен забезпечити:
- створення заявки на підписання документа;
- підготовку документа до підпису;
- розрахунок hash документа, якщо це вимагається інтеграцією;
- створення сесії підписання;
- генерацію QR-коду або deep link для переходу в застосунок Дія;
- відображення користувачу статусу підписання;
- отримання callback / webhook від Дії;
- отримання результату підписання;
- збереження підпису;
- збереження підписаного документа або контейнера;
- перевірку підпису;
- перевірку цілісності документа;
- оновлення статусу документа в K2 ERP або іншій системі;
- журналювання всіх подій;
- контроль помилок;
- dashboard для відповідальних осіб.
2. Область застосування
Інтеграція може використовуватись для:
- договорів;
- актів виконаних робіт;
- рахунків;
- заяв;
- анкет;
- кадрових документів;
- первинних документів;
- документів ЕДО;
- документів K2 ERP;
- документів, які формуються в CRM;
- авторизації користувача через Дія.Підпис;
- підтвердження дії користувача в системі.
3. Що таке Дія.Підпис у межах інтеграції
Дія.Підпис у межах цього ТЗ розглядається як зовнішній сервіс, який дозволяє користувачу підтвердити свою дію та накласти електронний підпис через застосунок Дія.
| Параметр | Опис |
|---|---|
| Тип сервісу | Електронний підпис через застосунок Дія. |
| Основний сценарій | Користувач відкриває QR/deep link, підтверджує підписання в застосунку, система отримує результат. |
| Результат | Підпис, підписаний контейнер або дані підписання згідно з API Дії. |
| Формат результату | Уточнюється за офіційною документацією Дії. Для багатьох КЕП-сценаріїв типовим є окремий файл підпису або контейнер. |
| Кінцева система | K2 ERP / CRM / документообіг / сайт / мобільний застосунок. |
Критично важливо: Дія.Підпис не повинен підміняти внутрішню систему зберігання документів. Python-сервіс повинен сам зберігати документ, підпис, статус, аудит і результат перевірки.
4. Передумови
Для реалізації задачі необхідно отримати:
- партнерський доступ до інтеграції Дії;
- офіційну документацію Дія.Підпис;
- тестове середовище, якщо доступне;
- client_id або аналогічний ідентифікатор партнера;
- client_secret або інший секрет доступу;
- сертифікати, якщо вони потрібні для взаємодії;
- endpoint-и API Дії;
- callback URL, який буде приймати результат;
- правила формування QR/deep link;
- правила формування запиту на підпис;
- допустимі формати документів;
- максимальний розмір документа;
- правила зберігання результату підписання;
- правила перевірки підпису;
- контакт технічної підтримки Дії.
Критично важливо: без офіційної документації партнера Дії не можна фіксувати production endpoint-и, назви параметрів і формат callback як остаточні.
5. Основні сценарії інтеграції
5.1. Підписання одного документа
Користувач відкриває документ у K2 ERP або на сайті, натискає кнопку «Підписати через Дія.Підпис».
Система:
- створює запис документа;
- створює сесію підписання;
- генерує QR/deep link;
- показує QR користувачу;
- очікує callback;
- отримує результат;
- зберігає підпис;
- перевіряє підпис;
- змінює статус документа на SIGNED.
5.2. Підписання пакета документів
Користувач підписує декілька документів за один бізнес-процес.
Система повинна:
- створити пакет документів;
- перевірити всі документи;
- передати пакет на підписання, якщо це підтримується;
- отримати результат по кожному документу;
- показати частково підписані або помилкові документи;
- не втратити статус окремого документа.
5.3. Підписання документа клієнтом
Клієнт отримує посилання на документ.
Система:
- відкриває сторінку підписання;
- показує коротку інформацію про документ;
- показує QR/deep link;
- клієнт підтверджує підписання в Дії;
- система отримує результат;
- документ стає підписаним клієнтом.
5.4. Підписання документа співробітником
Співробітник компанії підписує внутрішній документ.
Система:
- створює задачу на підпис;
- показує її у списку задач;
- контролює строк підписання;
- нагадує про прострочення;
- зберігає аудит дій.
5.5. Авторизація через Дія.Підпис
Дія.Підпис може використовуватись не тільки для документа, а й для авторизації або підтвердження дії.
Система:
- створює authorization session;
- показує QR/deep link;
- отримує підтвердження;
- ідентифікує користувача згідно з дозволеним обсягом даних;
- створює або оновлює сесію користувача.
6. Основні сутності
| Сутність | Опис |
|---|---|
| Signature Integration | Налаштування підключення до Дія.Підпис. |
| Document | Документ, який потрібно підписати. |
| Document Version | Версія документа, яка передана на підпис. |
| Signature Request | Заявка на підписання. |
| Signature Session | Сесія взаємодії з Дією. |
| Signer | Підписант. |
| Callback Event | Подія, отримана від Дії. |
| Signature File | Файл підпису або підписаний контейнер. |
| Verification Result | Результат перевірки підпису. |
| Audit Event | Подія журналу. |
7. User Story
7.1. Користувач підписує документ
Як користувач, я хочу натиснути кнопку «Підписати через Дія.Підпис», щоб підписати документ без завантаження ключів у систему.
7.2. Менеджер контролює підписання
Як менеджер, я хочу бачити статус підписання документа, щоб знати, чи клієнт підписав документ.
7.3. Адміністратор перевіряє помилки
Як адміністратор, я хочу бачити callback-и, помилки API та технічний журнал, щоб швидко знаходити причини невдалого підписання.
7.4. Керівник бачить dashboard
Як керівник, я хочу бачити кількість документів на підписі, підписаних, відхилених і прострочених, щоб контролювати документообіг.
8. Статуси документа
| Статус | Код | Опис | Колір |
|---|---|---|---|
| Чернетка | DRAFT | Документ створений, але ще не готовий до підпису. | Сірий |
| Готовий до підпису | READY_TO_SIGN | Документ перевірено і можна створювати заявку. | Блакитний |
| Очікує підпису | WAITING_SIGNATURE | Користувачу створено QR/deep link. | Жовтий |
| Підписується | SIGNING | Користувач відкрив процес підписання. | Блакитний |
| Підписано | SIGNED | Підпис успішно отримано і збережено. | Зелений |
| Підпис перевірено | VERIFIED | Підпис пройшов перевірку. | Зелений |
| Відхилено користувачем | DECLINED_BY_USER | Користувач не підтвердив підписання. | Помаранчевий |
| Прострочено | EXPIRED | Строк сесії підписання минув. | Помаранчевий |
| Помилка підписання | SIGN_ERROR | Помилка під час підписання. | Червоний |
| Помилка перевірки | VERIFY_ERROR | Підпис отримано, але перевірка не пройдена. | Червоний |
| Ручна перевірка | MANUAL_REVIEW | Потрібна перевірка адміністратором. | Фіолетовий |
9. Статуси сесії підписання
| Статус | Код | Опис | Колір |
|---|---|---|---|
| Створюється | CREATING | Система створює сесію підписання. | Блакитний |
| Активна | ACTIVE | QR/deep link доступний користувачу. | Жовтий |
| Очікує callback | WAITING_CALLBACK | Користувач перейшов у Дію, система очікує результат. | Жовтий |
| Завершена | COMPLETED | Сесія завершена успішно. | Зелений |
| Відхилена | DECLINED | Користувач відхилив дію. | Помаранчевий |
| Прострочена | EXPIRED | Сесія не завершена у строк. | Помаранчевий |
| Помилка | ERROR | Технічна помилка. | Червоний |
10. Єдина логіка кольорів
| Колір | HTML | Значення | Де використовується |
|---|---|---|---|
| Зелений | #c8e6c9 | Успішно: підписано, перевірено, завершено. | Dashboard, список документів, картка документа. |
| Блакитний | #bbdefb | Операція виконується. | Створення сесії, підписання. |
| Жовтий | #fff9c4 | Очікування дії користувача або callback. | Очікує підпису, активна сесія. |
| Помаранчевий | #ffcc80 | Потрібна дія або є ризик. | Відхилено, прострочено. |
| Червоний | #ef9a9a | Помилка або негативний результат. | Помилка підписання, помилка перевірки. |
| Фіолетовий | #f3e5f5 | Ручна перевірка або нестандартний сценарій. | MANUAL_REVIEW. |
| Сірий | #eeeeee | Чернетка або архів. | DRAFT, archived. |
11. Архітектура рішення
11.1. Загальна схема
K2 ERP / CRM / Website
|
| 1. Документ на підпис
v
Python Diia.Sign Integration Service
|
| 2. Валідація, hash, створення сесії
v
Diia Adapter
|
| 3. API Дії / QR / deep link
v
Застосунок Дія
|
| 4. Користувач підтверджує підписання
v
Callback URL Python-сервісу
|
| 5. Результат підписання
v
Signature Storage + Verification Service
|
| 6. Збереження, перевірка, статус
v
K2 ERP / Dashboard / Документообіг
11.2. Основні компоненти Python-сервісу
| Компонент | Опис |
|---|---|
| API Layer | REST API для створення заявок на підпис. |
| Document Service | Робота з документами та версіями. |
| Signature Request Service | Створення заявки на підписання. |
| Diia Client | Python-клієнт для API Дії. |
| QR / Deep Link Service | Генерація посилання або QR. |
| Callback Controller | Прийом callback від Дії. |
| Signature Storage | Зберігання підпису, контейнера, документа. |
| Verification Service | Перевірка підпису та цілісності. |
| Status Sync Service | Оновлення статусів у K2 ERP. |
| Audit Logger | Журнал подій, callback-ів, помилок. |
| Dashboard API | Дані для керівника та відповідальних осіб. |
12. Diia Client
12.1. Призначення
Diia Client — це Python-клас або пакет, який інкапсулює роботу з API Дія.Підпис.
12.2. Основні методи
class DiiaSignatureClient:
def check_connection(self) -> "ConnectionStatus":
pass
def authenticate(self) -> "AuthResult":
pass
def create_signature_session(self, payload: "CreateSignatureSessionPayload") -> "SignatureSessionResponse":
pass
def get_signature_session_status(self, session_id: str) -> "SignatureSessionStatusResponse":
pass
def get_signature_result(self, session_id: str) -> "SignatureResultResponse":
pass
def cancel_signature_session(self, session_id: str) -> "CancelSignatureSessionResponse":
pass
Важливо: назви методів у Python-клієнті є внутрішньою абстракцією. Реальні endpoint-и і payload потрібно взяти з офіційної документації Дії для партнера.
13. Конфігурація
from pydantic_settings import BaseSettings
class DiiaSignatureSettings(BaseSettings):
base_url: str
client_id: str
client_secret: str
callback_url: str
redirect_url: str | None = None
timeout_seconds: int = 30
retry_count: int = 3
retry_backoff_seconds: int = 5
verify_ssl: bool = True
session_ttl_minutes: int = 15
max_document_size_mb: int = 10
Приклад `.env`:
DIIA_SIGNATURE_BASE_URL=https://partner-api.example.diia DIIA_SIGNATURE_CLIENT_ID=******** DIIA_SIGNATURE_CLIENT_SECRET=******** DIIA_SIGNATURE_CALLBACK_URL=https://example.com/api/v1/diia-signature/callback DIIA_SIGNATURE_REDIRECT_URL=https://example.com/signature/result DIIA_SIGNATURE_TIMEOUT_SECONDS=30 DIIA_SIGNATURE_RETRY_COUNT=3 DIIA_SIGNATURE_SESSION_TTL_MINUTES=15 DIIA_SIGNATURE_MAX_DOCUMENT_SIZE_MB=10
Заборонено: зберігати client_secret, приватні ключі, токени, callback secrets або інші секрети у коді, Git-репозиторії, frontend-змінних або відкритих логах.
14. API Python-сервісу
14.1. Створення інтеграції
POST /api/v1/diia-signature/integrations
14.2. Перевірка підключення
POST /api/v1/diia-signature/integrations/{integration_id}/check-connection
14.3. Створення документа
POST /api/v1/diia-signature/documents
14.4. Створення заявки на підпис
POST /api/v1/diia-signature/documents/{document_id}/signature-requests
14.5. Отримання QR/deep link
GET /api/v1/diia-signature/signature-requests/{request_id}/link
14.6. Отримання статусу заявки
GET /api/v1/diia-signature/signature-requests/{request_id}/status
14.7. Callback від Дії
POST /api/v1/diia-signature/callback
14.8. Завантаження підписаного документа
GET /api/v1/diia-signature/documents/{document_id}/signed-file
14.9. Завантаження файлу підпису
GET /api/v1/diia-signature/documents/{document_id}/signature-file
14.10. Перевірка підпису
POST /api/v1/diia-signature/documents/{document_id}/verify
14.11. Dashboard
GET /api/v1/diia-signature/dashboard?date_from=2026-05-01&date_to=2026-05-31
15. Приклад запиту на створення заявки на підпис
{
"external_document_id": "K2-DOC-2026-000123",
"idempotency_key": "K2-DOC-2026-000123-sign-v1",
"document_type": "CONTRACT",
"document_name": "Договір поставки №123",
"document_number": "123",
"document_date": "2026-05-07",
"file_id": "file-001",
"file_name": "contract_123.pdf",
"file_mime_type": "application/pdf",
"signer": {
"external_signer_id": "CLIENT-001",
"full_name": "Іван Петренко",
"phone": "+380671112233",
"email": "client@example.com",
"tax_id": "1234567890"
},
"callback_context": {
"k2_entity": "contract",
"k2_entity_id": "contract-001"
},
"expires_at": "2026-05-07T14:30:00+03:00"
}
16. Валідація документа перед підписом
Перед створенням заявки система повинна перевірити:
- наявність external_document_id;
- наявність idempotency_key;
- наявність файлу документа;
- файл доступний у сховищі;
- файл не порожній;
- розмір файлу не перевищує ліміт;
- MIME type дозволений;
- документ не був змінений після створення заявки;
- hash документа збережений;
- підписант визначений;
- строк підписання не минув;
- документ ще не підписаний цим підписантом;
- бізнес-процес дозволяє підписання;
- користувач має право ініціювати підписання.
Критично важливо: якщо документ змінено після створення заявки на підпис, попередня заявка повинна бути скасована або переведена в статус INVALIDATED. Не можна підписувати старий hash для нової версії документа.
17. Hash документа і версії
Для кожного документа потрібно зберігати:
| Поле | Опис |
|---|---|
| document_version_id | ID версії документа. |
| file_hash_sha256 | Hash файлу. |
| file_size | Розмір файлу. |
| mime_type | MIME type. |
| created_at | Дата створення версії. |
| created_by | Хто створив версію. |
Приклад hash:
sha256(file_bytes)
18. Callback від Дії
Callback endpoint повинен:
- приймати тільки HTTPS-запити;
- перевіряти підпис або секрет callback, якщо передбачено API;
- перевіряти session_id;
- перевіряти request_id;
- перевіряти idempotency callback;
- зберігати raw payload;
- оновлювати статус сесії;
- зберігати файл підпису або посилання на результат;
- запускати перевірку підпису;
- повертати коректний HTTP status.
Критично важливо: callback повинен бути ідемпотентним. Якщо Дія або мережа повторно надішле той самий callback, система не повинна дублювати підпис або повторно змінювати фінальний статус некоректно.
19. Перевірка підпису
Після отримання результату підписання система повинна виконати перевірку.
Перевіряється:
- цілісність документа;
- відповідність підпису конкретній версії документа;
- валідність підпису;
- валідність сертифіката;
- дані підписанта;
- час підписання;
- статус відкликання сертифіката, якщо доступно;
- чи відповідає підписант очікуваному користувачу;
- чи не минув строк сесії;
- чи не змінювався документ після підпису.
Можливі результати:
| Результат | Код | Опис | Колір |
|---|---|---|---|
| Валідний | VALID | Підпис пройшов перевірку. | Зелений |
| Невалідний | INVALID | Підпис не пройшов перевірку. | Червоний |
| Не той документ | HASH_MISMATCH | Hash документа не збігається. | Червоний |
| Не той підписант | SIGNER_MISMATCH | Підписант не відповідає очікуваному. | Червоний |
| Потребує ручної перевірки | MANUAL_REVIEW | Неможливо автоматично визначити результат. | Фіолетовий |
20. Дедублікація
Система повинна не допускати дублювання заявок і підписів.
Ключі дедублікації:
| Ключ | Призначення |
|---|---|
| external_document_id | ID документа в K2 ERP. |
| document_version_id | Версія документа. |
| signer_id | Підписант. |
| idempotency_key | Унікальний ключ заявки. |
| diia_session_id | ID сесії в Дії. |
| callback_event_id | ID callback-події, якщо надається. |
| file_hash_sha256 | Hash документа. |
21. Черга обробки
21.1. Логіка черги
1. K2 ERP створює документ. 2. Користувач натискає «Підписати через Дія». 3. Python-сервіс перевіряє документ. 4. Створюється signature_request. 5. Створюється signature_session. 6. Користувачу показується QR/deep link. 7. Callback Controller приймає результат. 8. Signature Storage зберігає підпис. 9. Verification Service перевіряє підпис. 10. K2 ERP отримує фінальний статус.
21.2. Пріоритети задач
| Задача | Пріоритет | Коментар |
|---|---|---|
| Прийом callback | Критичний | Не можна втрачати результат підписання. |
| Збереження підпису | Критичний | Юридично значущий результат. |
| Перевірка підпису | Високий | Потрібна для фінального статусу. |
| Створення сесії | Високий | Основний сценарій користувача. |
| Оновлення dashboard | Середній | Контроль. |
| Нагадування про прострочення | Низький | Фоновий процес. |
22. Модель даних
22.1. diia_signature_integrations
| Поле | Тип | Опис |
|---|---|---|
| id | uuid | ID інтеграції. |
| provider | varchar | diia_signature. |
| name | varchar | Назва інтеграції. |
| base_url | varchar | URL API. |
| client_id | varchar | ID партнера. |
| client_secret_encrypted | text | Зашифрований секрет. |
| callback_url | varchar | Callback URL. |
| redirect_url | varchar | URL повернення користувача. |
| is_active | boolean | Активність. |
| created_at | timestamp | Дата створення. |
| updated_at | timestamp | Дата оновлення. |
22.2. sign_documents
| Поле | Тип | Опис |
|---|---|---|
| id | uuid | ID документа. |
| external_document_id | varchar | ID документа в K2 ERP. |
| document_type | varchar | CONTRACT, ACT, APPLICATION тощо. |
| document_name | varchar | Назва документа. |
| document_number | varchar | Номер документа. |
| document_date | date | Дата документа. |
| current_version_id | uuid | Поточна версія. |
| status | varchar | Статус документа. |
| created_at | timestamp | Дата створення. |
22.3. sign_document_versions
| Поле | Тип | Опис |
|---|---|---|
| id | uuid | ID версії. |
| document_id | uuid | ID документа. |
| file_id | uuid | Файл документа. |
| file_name | varchar | Назва файлу. |
| mime_type | varchar | MIME type. |
| file_size | integer | Розмір файлу. |
| file_hash_sha256 | varchar | Hash файлу. |
| version_number | integer | Номер версії. |
| created_at | timestamp | Дата створення. |
22.4. signature_requests
| Поле | Тип | Опис |
|---|---|---|
| id | uuid | ID заявки. |
| document_id | uuid | Документ. |
| document_version_id | uuid | Версія документа. |
| signer_id | uuid | Підписант. |
| idempotency_key | varchar | Ключ дедублікації. |
| status | varchar | Статус заявки. |
| expires_at | timestamp | Строк дії. |
| created_by | uuid | Хто створив заявку. |
| created_at | timestamp | Дата створення. |
22.5. signature_sessions
| Поле | Тип | Опис |
|---|---|---|
| id | uuid | ID сесії. |
| signature_request_id | uuid | Заявка. |
| diia_session_id | varchar | ID сесії в Дії. |
| qr_payload | text | Дані QR, якщо зберігаються. |
| deep_link | text | Deep link. |
| status | varchar | Статус сесії. |
| raw_request | jsonb | Запит до Дії. |
| raw_response | jsonb | Відповідь Дії. |
| created_at | timestamp | Дата створення. |
| expires_at | timestamp | Дата завершення. |
22.6. signature_files
| Поле | Тип | Опис |
|---|---|---|
| id | uuid | ID файлу підпису. |
| signature_request_id | uuid | Заявка. |
| file_id | uuid | Файл у сховищі. |
| file_type | varchar | signature, signed_container, signed_pdf. |
| file_hash_sha256 | varchar | Hash файлу. |
| created_at | timestamp | Дата створення. |
22.7. signature_verifications
| Поле | Тип | Опис |
|---|---|---|
| id | uuid | ID перевірки. |
| signature_request_id | uuid | Заявка. |
| result | varchar | VALID, INVALID, HASH_MISMATCH тощо. |
| signer_name | varchar | ПІБ підписанта з сертифіката. |
| signer_identifier | varchar | Ідентифікатор підписанта, якщо доступний. |
| signed_at | timestamp | Час підписання. |
| certificate_info | jsonb | Дані сертифіката. |
| raw_result | jsonb | Повний результат перевірки. |
| created_at | timestamp | Дата перевірки. |
22.8. signature_events
| Поле | Тип | Опис |
|---|---|---|
| id | uuid | ID події. |
| entity_type | varchar | document, request, session, callback, verification. |
| entity_id | uuid | ID сутності. |
| event_type | varchar | Тип події. |
| old_status | varchar | Попередній статус. |
| new_status | varchar | Новий статус. |
| source | varchar | K2_ERP, PYTHON_SERVICE, DIIA, USER. |
| payload | jsonb | Технічні дані. |
| created_at | timestamp | Дата події. |
23. Приклад Python-логіки
23.1. Створення заявки на підпис
from hashlib import sha256
from datetime import datetime, timezone
def create_signature_request(command: "CreateSignatureRequestCommand", db: "Session") -> "SignatureRequest":
existing = signature_request_repository.get_by_idempotency_key(
db=db,
idempotency_key=command.idempotency_key,
)
if existing:
return existing
document = document_repository.get_by_external_id(
db=db,
external_document_id=command.external_document_id,
)
signature_validator.validate_document_for_signing(document, command)
request = signature_request_repository.create(
db=db,
data={
"document_id": document.id,
"document_version_id": document.current_version_id,
"signer_id": command.signer_id,
"idempotency_key": command.idempotency_key,
"status": "CREATING",
"expires_at": command.expires_at,
},
)
signature_queue.enqueue(
task_name="create_diia_signature_session",
payload={"signature_request_id": str(request.id)},
)
audit_logger.log(
entity_type="signature_request",
entity_id=request.id,
event_type="SIGNATURE_REQUEST_CREATED",
new_status="CREATING",
payload={"external_document_id": command.external_document_id},
)
db.commit()
return request
23.2. Створення сесії Дія.Підпис
async def create_diia_signature_session(signature_request_id: str, db: "Session") -> None:
request = signature_request_repository.get_by_id(db, signature_request_id)
document_version = document_version_repository.get_by_id(db, request.document_version_id)
try:
payload = diia_mapper.to_signature_session_payload(
request=request,
document_version=document_version,
)
response = await diia_client.create_signature_session(payload)
session = signature_session_repository.create(
db=db,
data={
"signature_request_id": request.id,
"diia_session_id": response.session_id,
"qr_payload": response.qr_payload,
"deep_link": response.deep_link,
"status": "ACTIVE",
"raw_request": payload,
"raw_response": response.raw_payload,
"expires_at": response.expires_at,
},
)
request.status = "WAITING_SIGNATURE"
audit_logger.log(
entity_type="signature_session",
entity_id=session.id,
event_type="DIIA_SIGNATURE_SESSION_CREATED",
new_status="ACTIVE",
payload={"diia_session_id": response.session_id},
)
except Exception as exc:
request.status = "SIGN_ERROR"
audit_logger.log(
entity_type="signature_request",
entity_id=request.id,
event_type="DIIA_SIGNATURE_SESSION_ERROR",
new_status="SIGN_ERROR",
payload={"error": str(exc)},
)
finally:
db.commit()
23.3. Callback controller
from fastapi import APIRouter, Request, HTTPException
router = APIRouter()
@router.post("/api/v1/diia-signature/callback")
async def diia_signature_callback(request: Request):
payload = await request.json()
# Перевірка callback signature / secret залежить від офіційної документації Дії.
if not callback_security_service.is_valid(request, payload):
raise HTTPException(status_code=401, detail="Invalid callback signature")
callback_id = callback_service.get_callback_id(payload)
if callback_repository.exists(callback_id):
return {"status": "already_processed"}
callback_event = callback_repository.create_raw_event(payload)
signature_session = signature_session_repository.get_by_diia_session_id(
diia_session_id=payload["session_id"],
)
if not signature_session:
callback_event.status = "UNKNOWN_SESSION"
return {"status": "unknown_session"}
callback_processor.process_signature_result(
signature_session=signature_session,
payload=payload,
)
return {"status": "ok"}
23.4. Обробка результату підписання
def process_signature_result(signature_session: "SignatureSession", payload: dict) -> None:
request = signature_session.signature_request
if payload.get("status") == "declined":
signature_session.status = "DECLINED"
request.status = "DECLINED_BY_USER"
return
if payload.get("status") != "signed":
signature_session.status = "ERROR"
request.status = "SIGN_ERROR"
return
signature_file = signature_storage.save_signature_result(
signature_request_id=request.id,
payload=payload,
)
signature_session.status = "COMPLETED"
request.status = "SIGNED"
verification_queue.enqueue(
task_name="verify_signature",
payload={
"signature_request_id": str(request.id),
"signature_file_id": str(signature_file.id),
},
)
23.5. Перевірка підпису
def verify_signature(signature_request_id: str, signature_file_id: str, db: "Session") -> None:
request = signature_request_repository.get_by_id(db, signature_request_id)
document_version = document_version_repository.get_by_id(db, request.document_version_id)
signature_file = signature_file_repository.get_by_id(db, signature_file_id)
try:
result = signature_verifier.verify(
document_file_id=document_version.file_id,
signature_file_id=signature_file.file_id,
expected_hash=document_version.file_hash_sha256,
)
verification = signature_verification_repository.create(
db=db,
data={
"signature_request_id": request.id,
"result": result.code,
"signer_name": result.signer_name,
"signer_identifier": result.signer_identifier,
"signed_at": result.signed_at,
"certificate_info": result.certificate_info,
"raw_result": result.raw,
},
)
if result.code == "VALID":
request.status = "VERIFIED"
request.document.status = "VERIFIED"
else:
request.status = "VERIFY_ERROR"
except Exception as exc:
request.status = "MANUAL_REVIEW"
audit_logger.log(
entity_type="signature_request",
entity_id=request.id,
event_type="SIGNATURE_VERIFY_EXCEPTION",
new_status="MANUAL_REVIEW",
payload={"error": str(exc)},
)
finally:
db.commit()
24. Обробка помилок
| Тип помилки | Опис | Дія системи |
|---|---|---|
| ValidationError | Документ або підписант невалідний. | Не створювати сесію. |
| AuthError | Невірні credentials Дії. | Зупинити інтеграцію і повідомити адміністратора. |
| DocumentChangedError | Документ змінено після заявки. | Скасувати заявку або створити нову. |
| FileTooLargeError | Документ перевищує ліміт. | Показати користувачу помилку. |
| SessionExpiredError | Сесія підписання прострочена. | Статус EXPIRED, дозволити створити нову. |
| CallbackValidationError | Callback не пройшов перевірку. | Відхилити callback і записати подію. |
| SignatureResultError | Не вдалося отримати результат підпису. | Перевести в SIGN_ERROR або NEEDS_RETRY. |
| VerificationError | Підпис не пройшов перевірку. | Статус VERIFY_ERROR. |
| SignerMismatchError | Підписант не відповідає очікуваному. | Статус MANUAL_REVIEW або VERIFY_ERROR. |
| TimeoutError | API недоступне або timeout. | Retry, якщо безпечно. |
25. Retry-логіка
Retry дозволений для:
- timeout;
- HTTP 429;
- HTTP 500;
- HTTP 502;
- HTTP 503;
- HTTP 504;
- тимчасової помилки створення сесії;
- тимчасової помилки отримання статусу;
- тимчасової помилки перевірки підпису;
- повторного callback з тим самим callback_id.
Retry заборонений для:
- невалідного документа;
- документа, який змінився;
- простроченої сесії;
- відхилення користувачем;
- невірного callback signature;
- невідповідності підписанта;
- вже фінального статусу VERIFIED.
26. Dashboard керівника
26.1. Основні KPI
| KPI | Опис | Колір |
|---|---|---|
| Документів створено | Загальна кількість документів. | Інформація |
| Очікують підпису | Документи з активною сесією. | Увага |
| Підписано | Підпис отримано. | Норма |
| Перевірено | Підпис пройшов перевірку. | Норма |
| Відхилено | Користувач відмовився. | Потрібна дія |
| Прострочено | Сесія не завершена вчасно. | Потрібна дія |
| Помилки | Помилки підписання або callback. | Критично |
| Ручна перевірка | Потрібне втручання адміністратора. | Контроль |
26.2. Приклад dashboard
| Показник | Значення | Стан |
|---|---|---|
| Документів за день | 184 | Інформація |
| Очікують підпису | 32 | Увага |
| Підписано | 128 | Норма |
| Перевірено | 126 | Норма |
| Відхилено | 8 | Потрібна дія |
| Прострочено | 10 | Потрібна дія |
| Помилки callback | 3 | Критично |
| Ручна перевірка | 2 | Контроль |
26.3. Проблемні документи
| Дата | Документ | Підписант | Статус | Причина | Дія |
|---|---|---|---|---|---|
| 07.05.2026 | Договір №123 | Іван Петренко | Прострочено | Користувач не завершив підписання | Створити нову заявку |
| 07.05.2026 | Акт №45 | Олена Сидоренко | Помилка перевірки | Hash документа не збігається | Ручна перевірка |
| 07.05.2026 | Заява №77 | ТОВ «Альфа» | Ручна перевірка | Неможливо автоматично визначити підписанта | Перевірити сертифікат |
27. Безпека
Система повинна забезпечити:
- HTTPS для всіх endpoint-ів;
- перевірку SSL;
- зберігання секретів тільки в secret storage;
- шифрування файлів підпису;
- шифрування документів або контроль доступу до них;
- обмеження доступу до callback endpoint;
- перевірку callback signature / secret;
- ідемпотентність callback;
- журнал усіх дій;
- маскування персональних даних у логах;
- контроль доступу до документів;
- окремі права на створення заявки;
- окремі права на повторне підписання;
- окремі права на ручну перевірку;
- заборону підписання зміненої версії документа.
28. Логування та аудит
Система повинна логувати:
| Подія | Що зберігати |
|---|---|
| Створення документа | Тип, номер, версія, hash. |
| Створення заявки | Підписант, строк дії, ініціатор. |
| Створення сесії Дія | diia_session_id, статус, expires_at. |
| Відкриття QR/deep link | request_id, час. |
| Callback | callback_id, raw payload, статус перевірки. |
| Отримання підпису | file_id, hash підпису, час. |
| Перевірка підпису | результат, підписант, сертифікат. |
| Помилка | код, повідомлення, stack trace без секретів. |
| Ручна перевірка | хто перевірив, рішення, коментар. |
29. Acceptance Criteria
29.1. Інтеграція
| № | Критерій | Очікуваний результат |
|---|---|---|
| AC-1 | Адміністратор створює інтеграцію Дія.Підпис. | Інтеграція зберігається в системі. |
| AC-2 | Адміністратор перевіряє підключення. | Система повертає успішний або помилковий статус. |
| AC-3 | Credentials неправильні. | Система показує AuthError і не створює сесії. |
29.2. Документ
| № | Критерій | Очікуваний результат |
|---|---|---|
| AC-4 | Документ валідний. | Система створює заявку на підпис. |
| AC-5 | Документ перевищує ліміт розміру. | Заявка не створюється. |
| AC-6 | Документ змінено після створення заявки. | Попередня заявка стає INVALIDATED або скасовується. |
29.3. Підписання
| № | Критерій | Очікуваний результат |
|---|---|---|
| AC-7 | Користувач натискає «Підписати через Дія». | Система показує QR/deep link. |
| AC-8 | Користувач підписує документ. | Система отримує callback і зберігає підпис. |
| AC-9 | Користувач відхиляє підписання. | Статус стає DECLINED_BY_USER. |
| AC-10 | Сесія прострочена. | Статус стає EXPIRED. |
29.4. Перевірка
| № | Критерій | Очікуваний результат |
|---|---|---|
| AC-11 | Підпис валідний. | Статус стає VERIFIED. |
| AC-12 | Hash документа не збігається. | Статус стає VERIFY_ERROR. |
| AC-13 | Підписант не відповідає очікуваному. | Статус стає MANUAL_REVIEW або VERIFY_ERROR. |
29.5. Callback
| № | Критерій | Очікуваний результат |
|---|---|---|
| AC-14 | Callback має правильний підпис/секрет. | Система приймає callback. |
| AC-15 | Callback повторився. | Система не дублює результат. |
| AC-16 | Callback невалідний. | Система повертає помилку і записує подію. |
29.6. Dashboard
| № | Критерій | Очікуваний результат |
|---|---|---|
| AC-17 | Керівник відкриває dashboard. | Він бачить документи, підписи, помилки, прострочення. |
| AC-18 | Є помилки підписання. | Вони підсвічуються червоним. |
| AC-19 | Є прострочені заявки. | Вони підсвічуються помаранчевим. |
| AC-20 | Є документи на ручній перевірці. | Вони підсвічуються фіолетовим. |
30. MVP
До MVP входить:
- створення інтеграції Дія.Підпис;
- перевірка підключення;
- створення документа;
- збереження версії документа;
- розрахунок hash;
- створення заявки на підпис;
- створення сесії підписання;
- QR/deep link;
- callback endpoint;
- збереження результату підписання;
- базова перевірка підпису;
- статуси документа;
- журнал подій;
- dashboard API;
- retry для технічних помилок;
- ідемпотентність callback;
- unit-тести;
- mock Diia client.
До MVP не входить:
- масове підписання великого пакета документів;
- складний UI документообігу;
- власний кваліфікований надавач електронних довірчих послуг;
- повна юридична експертиза документів;
- інтеграція з усіма зовнішніми ЕДО-системами;
- автоматичне виправлення документів;
- архів довгострокового зберігання за окремими регламентами.
31. Етапи реалізації
Етап 1. Аналіз інтеграції Дія.Підпис
- отримати партнерську документацію;
- отримати тестові credentials;
- погодити callback URL;
- перевірити тестовий сценарій;
- визначити формат результату підписання;
- визначити правила перевірки підпису.
Етап 2. Базовий Python-сервіс
- створити FastAPI-проєкт;
- налаштувати PostgreSQL;
- створити моделі документів, заявок, сесій, підписів;
- налаштувати Alembic;
- реалізувати healthcheck.
Етап 3. Diia Client
- реалізувати authenticate;
- реалізувати create_signature_session;
- реалізувати get_signature_session_status;
- реалізувати get_signature_result;
- реалізувати обробку помилок.
Етап 4. Документи
- реалізувати завантаження документа;
- реалізувати версіонування;
- реалізувати hash;
- реалізувати валідацію;
- реалізувати дедублікацію.
Етап 5. Callback та підпис
- реалізувати callback endpoint;
- реалізувати перевірку callback;
- реалізувати збереження результату;
- реалізувати ідемпотентність;
- реалізувати raw event storage.
Етап 6. Перевірка підпису
- реалізувати Verification Service;
- реалізувати статуси перевірки;
- реалізувати ручну перевірку;
- реалізувати журнал перевірок.
Етап 7. Dashboard та аудит
- реалізувати dashboard API;
- реалізувати список проблемних документів;
- реалізувати фільтри;
- реалізувати експорт, якщо потрібно.
Етап 8. Production hardening
- додати rate limiting;
- додати alerting;
- додати dead letter queue;
- додати backup файлів;
- додати моніторинг callback;
- додати безпечне зберігання секретів.
32. Ризики
| Ризик | Опис | Як зменшити |
|---|---|---|
| Немає партнерського доступу | Без доступу неможливо реалізувати production інтеграцію. | Отримати доступ до старту розробки. |
| Документ змінено після заявки | Можна підписати неактуальну версію. | Версіонування і hash документа. |
| Callback втрачено | Система не дізнається про результат. | Callback retry, polling статусу, журнал raw events. |
| Дублювання callback | Може повторно змінити статус. | Ідемпотентність callback. |
| Невідомий формат підпису | Неможливо зберегти/перевірити результат. | Уточнити формат за документацією Дії. |
| Невідповідність підписанта | Документ підписала не та особа. | Перевірка даних сертифіката. |
| Помилка перевірки | Підпис отримано, але не підтверджено. | MANUAL_REVIEW і аудит. |
| Прострочені сесії | Користувач не завершив підписання. | TTL, нагадування, повторна заявка. |
33. Відкриті питання
- Чи вже є партнерський доступ до Дія.Підпис?
- Який точний формат результату підписання: p7s, ASIC, PDF з підписом або інший?
- Чи потрібно підписувати PDF, XML, DOCX або будь-який файл?
- Який максимальний розмір документа?
- Чи потрібне пакетне підписання?
- Чи потрібна авторизація через Дія.Підпис?
- Чи потрібно підписувати документи клієнтами, співробітниками або обома?
- Чи потрібно перевіряти РНОКПП / ЄДРПОУ підписанта?
- Чи потрібна інтеграція з K2 ERP?
- Чи потрібно зберігати підписані документи в архіві довгострокового зберігання?
- Чи потрібен UI для підписанта?
- Чи потрібні email/SMS-нагадування?
- Який строк дії сесії підписання?
- Який callback security mechanism надає Дія?
- Чи потрібна інтеграція з ЕДО-системами після підписання?
34. Джерела
- Офіційна сторінка інтеграції Дії.
- Офіційна сторінка Дія.Підпис для партнерів.
- Офіційний FAQ Дія.Підпис.
- Офіційний сервіс КЕП Дії.
- Партнерська API-документація Дії, яка надається після підключення.
- Документація K2 ERP щодо документів і бізнес-процесів.