Перейти до вмісту

Уніфіковане накладання електронного підпису різних сервісних центрів України

Матеріал з K2 ERP Wiki Ukraine — База знань з автоматизації та санкцій в Україні


SEO title: Технічне завдання: Уніфіковане накладання електронного підпису різних сервісних центрів України для Python SEO description: Технічне завдання на реалізацію уніфікованого Python-модуля електронного підпису для різних сервісних центрів України: Дія.Підпис, ПриватБанк SmartID, файловий КЕП, ІІТ, ДПС, хмарний КЕП, p7s, ASIC, CAdES, XAdES, перевірка підпису, callback, polling, dashboard та журналювання. SEO keywords: Python, КЕП, електронний підпис, Дія.Підпис, SmartID, Приват24, ІІТ, ДПС, ЦСК, КНЕДП, p7s, ASIC, CAdES, XAdES, FastAPI, K2 ERP, електронний документообіг Alternative to:



Головна ідея: розробити єдиний Python-сервіс підписання, який надає K2 ERP / CRM / документообігу один уніфікований API для накладання електронного підпису через різних провайдерів України: Дія.Підпис, ПриватБанк SmartID, файловий КЕП, ІІТ, КЕП ДПС та інші КНЕДП.

Критично важливо: бізнес-система не повинна напряму залежати від конкретного сервісного центру підпису. K2 ERP повинна працювати з єдиним інтерфейсом: створити заявку, отримати статус, отримати підпис, перевірити підпис, зберегти результат.

Важливо: різні провайдери мають різні способи роботи: API, callback, polling, QR/deep link, файловий ключ, локальний агент, DLL/SO-бібліотека, JavaScript-бібліотека або ручне завантаження p7s. Тому потрібен адаптерний шар.

Технічний стек: Python 3.11+, FastAPI, PostgreSQL, SQLAlchemy, Alembic, httpx, Pydantic, Celery/RQ/APScheduler, Redis, Docker, S3-compatible file storage, локальні crypto adapters.

Управлінський результат: керівник і відповідальні особи повинні бачити, через який провайдер підписано документ, хто підписав, коли, чи пройшла перевірка, які документи очікують підпису, які прострочені, які мають помилки або потребують ручної перевірки.

1. Мета

Метою задачі є створення уніфікованого Python-сервісу електронного підпису для роботи з різними сервісними центрами та провайдерами КЕП України.

Сервіс повинен забезпечити:

  • єдиний API для всіх видів підписання;
  • підтримку декількох провайдерів підпису;
  • створення заявки на підписання;
  • підготовку документа до підпису;
  • розрахунок hash документа;
  • підписання PDF, XML, JSON, DOCX, ZIP або довільного файлу;
  • підтримку відокремленого підпису;
  • підтримку вкладеного підпису;
  • підтримку підписаного контейнера;
  • отримання результату підписання;
  • перевірку підпису;
  • перевірку цілісності документа;
  • перевірку підписанта;
  • перевірку сертифіката;
  • збереження файлів підпису;
  • збереження підписаних документів;
  • журналювання всіх подій;
  • dashboard контролю;
  • fallback-сценарії для ручного підписання.

2. Область застосування

Уніфікований модуль використовується для:

  • договорів;
  • актів виконаних робіт;
  • рахунків;
  • заяв;
  • кадрових документів;
  • первинних бухгалтерських документів;
  • податкових документів;
  • документів ЕДО;
  • заявок у CRM;
  • документів K2 ERP;
  • пакетів документів;
  • XML-звітів;
  • PDF-документів;
  • підтвердження юридично значущих дій.

3. Підтримувані провайдери підпису

Провайдер Код Тип інтеграції Коментар Пріоритет
Дія.Підпис DIIA_SIGN API / QR / deep link / callback Хмарний сценарій через застосунок Дія. Високий
ПриватБанк SmartID / Приват24 PRIVAT24_SMARTID API / polling / callback / ручне завантаження Хмарний КЕП ПриватБанку. Високий
Файловий КЕП FILE_KEY Локальне підписання Ключ типу Key-6.dat або інший файловий контейнер. Важливий
ІІТ / Користувач ЦСК-1 IIT_CSK DLL / COM / SO / Java / JS / локальний агент Універсальний локальний crypto-provider для українських КЕП. Високий
КНЕДП ДПС TAX_CSK Файловий ключ / ІІТ / локальне підписання Часто використовується для податкових документів. Важливий
Інші КНЕДП OTHER_QTSP Через ІІТ / файловий ключ / API Підключаються через адаптер. Важливий
Ручне завантаження підпису MANUAL_UPLOAD Upload p7s / container Fallback-сценарій. Резервний

4. Типи підписання

Тип підпису Код Опис Приклад
Відокремлений підпис DETACHED Підпис зберігається окремо від документа. document.pdf + document.pdf.p7s
Вкладений підпис ENVELOPED Підпис вбудований у документ або XML. XML із XAdES.
Підписаний контейнер CONTAINER Документ і підпис зберігаються в контейнері. ASIC / p7s container.
PDF-підпис PDF_SIGN Підпис у PDF або супровідний p7s. signed.pdf або pdf.p7s.
XML-підпис XML_SIGN Підпис XML-документа. XAdES.
Hash-підпис HASH_SIGN Провайдер підписує hash, а не сам файл. SHA-256 hash.

Критично важливо: система повинна зберігати, який саме тип підпису було створено: detached, enveloped, container, PDF, XML або hash-sign. Без цього неможливо правильно перевіряти й використовувати результат.

5. Підтримувані формати

Формат Опис Підтримка в MVP
PDF Найчастіший формат договорів, актів, рахунків. Так
XML Податкова, звітність, структуровані документи. Так
DOCX Документи Word. Опційно
JSON Технічні документи або структуровані payload. Опційно
ZIP Пакет документів. Опційно
P7S Файл підпису. Так
ASIC Контейнер підпису. Опційно
CAdES Формат CMS-підпису. Так
XAdES XML-підпис. Опційно

6. Передумови

Для реалізації задачі необхідно отримати:

  • перелік провайдерів підпису, які потрібно підтримати в MVP;
  • офіційну документацію кожного API-провайдера;
  • credentials для хмарних сервісів;
  • тестові ключі або тестові акаунти;
  • список форматів документів;
  • вимоги до підпису: detached, embedded, container;
  • правила перевірки підпису;
  • список КНЕДП, які треба підтримати;
  • вимоги до зберігання документів;
  • вимоги до журналювання;
  • вимоги до K2 ERP;
  • вимоги до UI підписанта;
  • вимоги до мобільного сценарію;
  • вимоги до довгострокового архіву.

7. Варіанти реалізації

7.1. Варіант 1. Хмарний підпис через API

Використовується для Дія.Підпис, SmartID та інших хмарних КЕП.

Параметр Опис
Переваги Користувач не передає файл ключа в систему.
Особливості Потрібні сесія підписання, callback або polling.
Ризики Залежність від зовнішнього API.
Статуси WAITING_SIGNATURE, SIGNING, SIGNED, VERIFIED.

7.2. Варіант 2. Локальне підписання файловим ключем

Користувач використовує файловий КЕП.

Параметр Опис
Переваги Працює з багатьма КНЕДП.
Особливості Потрібна локальна crypto-бібліотека або агент.
Ризики Не можна зберігати пароль до ключа на сервері.
Рекомендація Використовувати локальний агент або браузерний компонент, а не передавати ключ на backend.

7.3. Варіант 3. Підписання через локальний агент

На ПК користувача встановлюється агент підпису.

Параметр Опис
Підходить для Робочих місць бухгалтерів, кадровиків, юристів.
Переваги Приватний ключ не покидає комп'ютер користувача.
Комунікація Browser → Local Agent → Crypto Library → результат у K2 ERP.
Реалізація Localhost API, WebSocket або protocol handler.

7.4. Варіант 4. Ручне завантаження підпису

Користувач підписує документ поза системою та завантажує результат.

Параметр Опис
Підходить для Fallback або MVP без API.
Переваги Простий резервний сценарій.
Недоліки Менше автоматизації.
Обов'язкова умова Автоматична перевірка підпису після завантаження.

8. Уніфікований бізнес-процес

1. K2 ERP створює документ.
2. Python Signature Service створює версію документа.
3. Система розраховує hash документа.
4. Користувач або правило обирає провайдера підпису.
5. Створюється signature_request.
6. Обраний adapter створює сесію або локальну операцію підпису.
7. Користувач підтверджує підпис.
8. Система отримує результат.
9. Signature Storage зберігає файл підпису / контейнер.
10. Verification Service перевіряє підпис.
11. K2 ERP отримує фінальний статус.
12. Dashboard показує результат.

9. Основні сутності

Сутність Опис
Signature Provider Провайдер підпису: Дія, SmartID, ІІТ, файловий КЕП.
Signature Integration Налаштування конкретного провайдера.
Document Документ, який потрібно підписати.
Document Version Версія документа, яка передається на підпис.
Signature Request Заявка на підписання.
Signature Session Сесія підписання.
Signer Підписант.
Signature File Файл підпису або контейнер.
Verification Result Результат перевірки.
Provider Adapter Програмний адаптер конкретного провайдера.
Audit Event Подія журналу.

10. User Story

10.1. Користувач обирає провайдера

Як користувач, я хочу вибрати спосіб підписання: Дія, Приват24 SmartID, файловий КЕП або інший КНЕДП, щоб підписати документ зручним для мене способом.

10.2. K2 ERP працює з єдиним API

Як розробник K2 ERP, я хочу викликати один API підписання, щоб не реалізовувати окрему логіку для кожного сервісного центру.

10.3. Адміністратор керує провайдерами

Як адміністратор, я хочу вмикати або вимикати провайдерів підпису, щоб контролювати доступні сценарії підписання.

10.4. Керівник бачить контроль

Як керівник, я хочу бачити dashboard підписання, щоб контролювати прострочені, помилкові та непідписані документи.

11. Статуси документа

Статус Код Опис Колір
Чернетка DRAFT Документ створений, але ще не готовий до підпису. Сірий
Готовий до підпису READY_TO_SIGN Документ перевірено. Блакитний
Очікує вибору провайдера WAITING_PROVIDER Підписант ще не обрав спосіб підпису. Жовтий
Очікує підпису WAITING_SIGNATURE Створена заявка на підпис. Жовтий
Підписується SIGNING Виконується процес підписання. Блакитний
Підпис отримано SIGNED Підпис або контейнер отримано. Зелений
Підпис перевірено VERIFIED Підпис валідний. Зелений
Відхилено користувачем DECLINED_BY_USER Користувач відмовився від підписання. Помаранчевий
Прострочено EXPIRED Строк підписання минув. Помаранчевий
Помилка підписання SIGN_ERROR Помилка під час підписання. Червоний
Помилка перевірки VERIFY_ERROR Підпис не пройшов перевірку. Червоний
Ручна перевірка MANUAL_REVIEW Потрібне втручання адміністратора. Фіолетовий
Скасовано CANCELLED Заявку або документ скасовано. Сірий

12. Статуси провайдера

Статус Код Опис Колір
Активний ACTIVE Провайдер доступний для підписання. Зелений
Тестовий режим TEST_MODE Провайдер доступний тільки в тесті. Блакитний
Тимчасово недоступний UNAVAILABLE API або локальний агент недоступний. Помаранчевий
Помилка авторизації AUTH_ERROR Невірні credentials. Червоний
Вимкнений DISABLED Провайдер вимкнений адміністратором. Сірий

13. Єдина логіка кольорів

Колір HTML Значення Де використовується
Зелений #c8e6c9 Успішно: підписано, перевірено, провайдер активний. Dashboard, список документів, картка документа.
Блакитний #bbdefb Операція виконується або тестовий режим. Створення сесії, підписання, тест.
Жовтий #fff9c4 Очікування дії користувача. Очікує підпису, вибір провайдера.
Помаранчевий #ffcc80 Потрібна дія або є ризик. Прострочення, недоступність, відхилення.
Червоний #ef9a9a Помилка або негативний результат. Помилка API, помилка перевірки.
Фіолетовий #f3e5f5 Ручна перевірка або нестандартний сценарій. MANUAL_REVIEW.
Сірий #eeeeee Чернетка, вимкнено, скасовано або архів. DRAFT, DISABLED, CANCELLED.

14. Архітектура рішення

14.1. Загальна схема

K2 ERP / CRM / Website
        |
        | 1. Єдиний API підписання
        v
Python Unified Signature Service
        |
        | 2. Валідація, hash, версії, політики
        v
Signature Provider Router
        |
        | 3. Вибір адаптера
        v
Provider Adapters
        |
        |-- DiiaSignAdapter
        |-- SmartIDAdapter
        |-- FileKeyAdapter
        |-- IITAdapter
        |-- TaxCSKAdapter
        |-- ManualUploadAdapter
        |
        v
Signature Storage + Verification Service
        |
        | 4. Збереження і перевірка
        v
K2 ERP / Dashboard / Archive

14.2. Основні компоненти

Компонент Опис
Unified Signature API Єдиний REST API для K2 ERP.
Provider Router Вибирає провайдера підпису.
Signature Policy Engine Визначає, які провайдери доступні для документа.
Document Version Service Контролює версії та hash документа.
DiiaSignAdapter Інтеграція з Дія.Підпис.
SmartIDAdapter Інтеграція з Приват24 / SmartID.
FileKeyAdapter Підписання файловим КЕП.
IITAdapter Інтеграція з бібліотеками ІІТ або локальним агентом.
ManualUploadAdapter Ручне завантаження p7s / контейнера.
Signature Storage Зберігає підписані файли.
Verification Service Перевіряє підпис.
Callback Controller Приймає callback від хмарних сервісів.
Polling Worker Перевіряє статуси сесій.
Audit Logger Логує всі дії.
Dashboard API Дані для контролю.

15. Unified Signature Provider Interface

15.1. Загальний інтерфейс провайдера

from abc import ABC, abstractmethod


class SignatureProviderAdapter(ABC):
    @abstractmethod
    async def check_connection(self) -> dict:
        pass

    @abstractmethod
    async def create_signature_session(self, request: dict) -> dict:
        pass

    @abstractmethod
    async def get_session_status(self, session_id: str) -> dict:
        pass

    @abstractmethod
    async def get_signature_result(self, session_id: str) -> dict:
        pass

    @abstractmethod
    async def cancel_session(self, session_id: str) -> dict:
        pass

    @abstractmethod
    async def verify_signature(self, document: bytes, signature: bytes, options: dict) -> dict:
        pass

15.2. Provider Router

class SignatureProviderRouter:
    def __init__(self, adapters: dict[str, SignatureProviderAdapter]):
        self.adapters = adapters

    def get_adapter(self, provider_code: str) -> SignatureProviderAdapter:
        if provider_code not in self.adapters:
            raise ValueError(f"Unsupported signature provider: {provider_code}")
        return self.adapters[provider_code]

15.3. Signature Policy Engine

class SignaturePolicyEngine:
    def get_allowed_providers(self, document_type: str, signer_type: str) -> list[str]:
        if document_type == "TAX_REPORT":
            return ["IIT_CSK", "TAX_CSK", "FILE_KEY"]

        if signer_type == "CLIENT":
            return ["DIIA_SIGN", "PRIVAT24_SMARTID", "MANUAL_UPLOAD"]

        if signer_type == "EMPLOYEE":
            return ["FILE_KEY", "IIT_CSK", "PRIVAT24_SMARTID"]

        return ["DIIA_SIGN", "PRIVAT24_SMARTID", "FILE_KEY", "MANUAL_UPLOAD"]

16. Налаштування провайдера

Поле Тип Опис
provider_code varchar DIIA_SIGN, PRIVAT24_SMARTID, IIT_CSK тощо.
provider_name varchar Назва провайдера.
integration_type varchar API, LOCAL_AGENT, FILE_KEY, MANUAL_UPLOAD.
base_url varchar URL API, якщо є.
callback_url varchar Callback URL.
credentials_encrypted jsonb Зашифровані credentials.
certificate_settings jsonb Налаштування сертифікатів.
supported_formats jsonb PDF, XML, p7s, ASIC тощо.
supported_signature_types jsonb DETACHED, ENVELOPED, CONTAINER.
is_active boolean Активність.
priority integer Пріоритет у списку.

17. API Python-сервісу

17.1. Список провайдерів

GET /api/v1/signature/providers

17.2. Перевірка провайдера

POST /api/v1/signature/providers/{provider_code}/check-connection

17.3. Створення документа

POST /api/v1/signature/documents

17.4. Отримання доступних провайдерів для документа

GET /api/v1/signature/documents/{document_id}/available-providers

17.5. Створення заявки на підпис

POST /api/v1/signature/documents/{document_id}/signature-requests

17.6. Отримання статусу заявки

GET /api/v1/signature/signature-requests/{request_id}/status

17.7. Callback від провайдера

POST /api/v1/signature/callback/{provider_code}

17.8. Ручне завантаження підпису

POST /api/v1/signature/documents/{document_id}/upload-signature

17.9. Завантаження файлу підпису

GET /api/v1/signature/documents/{document_id}/signature-file

17.10. Завантаження підписаного документа

GET /api/v1/signature/documents/{document_id}/signed-file

17.11. Перевірка підпису

POST /api/v1/signature/documents/{document_id}/verify

17.12. Dashboard

GET /api/v1/signature/dashboard?date_from=2026-05-01&date_to=2026-05-31

18. Приклад запиту на створення заявки

{
  "external_document_id": "K2-DOC-2026-000123",
  "idempotency_key": "K2-DOC-2026-000123-sign-v1",
  "provider_code": "DIIA_SIGN",
  "signature_type": "DETACHED",
  "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",
    "signer_type": "CLIENT",
    "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"
}

19. Валідація перед підписом

Перед створенням заявки система повинна перевірити:

  • наявність документа;
  • наявність актуальної версії документа;
  • наявність file_id;
  • доступність файлу у сховищі;
  • розмір файлу;
  • MIME type;
  • hash документа;
  • тип документа;
  • тип підписанта;
  • доступні провайдери для документа;
  • чи підтримує провайдер формат документа;
  • чи підтримує провайдер потрібний тип підпису;
  • чи не був документ змінений після створення заявки;
  • чи не підписаний документ цим підписантом раніше;
  • чи є idempotency_key;
  • чи дозволяє бізнес-процес підписання.

Критично важливо: якщо документ змінено після створення заявки, попередню заявку потрібно перевести в INVALIDATED. Підпис має накладатися тільки на конкретну версію документа.

20. Hash і версії документа

Кожна версія документа повинна мати:

Поле Опис
document_version_id ID версії.
file_hash_sha256 SHA-256 hash.
file_size Розмір файлу.
mime_type Тип файлу.
version_number Номер версії.
created_at Дата створення.
created_by Хто створив версію.

Приклад:

sha256(file_bytes)

21. Перевірка підпису

Verification Service повинен перевіряти:

  • цілісність документа;
  • відповідність підпису конкретній версії документа;
  • валідність підпису;
  • валідність сертифіката;
  • підписанта;
  • дату та час підписання;
  • chain trust;
  • статус відкликання сертифіката, якщо доступно;
  • формат підпису;
  • відповідність очікуваному типу підписанта;
  • чи не змінювався документ після підпису.

Можливі результати:

Результат Код Опис Колір
Валідний VALID Підпис успішно перевірений. Зелений
Невалідний INVALID Підпис не пройшов перевірку. Червоний
Hash не збігається HASH_MISMATCH Підпис не відповідає версії документа. Червоний
Не той підписант SIGNER_MISMATCH Підписант не відповідає очікуваному. Червоний
Сертифікат прострочений CERT_EXPIRED Сертифікат недійсний. Червоний
Сертифікат відкликаний CERT_REVOKED Сертифікат відкликаний. Червоний
Невідомий формат UNKNOWN_FORMAT Формат підпису не розпізнано. Помаранчевий
Ручна перевірка MANUAL_REVIEW Потрібне втручання адміністратора. Фіолетовий

22. Дедублікація

Система повинна не допускати дублювання заявок і підписів.

Ключ Призначення
external_document_id ID документа в K2 ERP.
document_version_id Версія документа.
signer_id Підписант.
provider_code Провайдер підпису.
idempotency_key Унікальний ключ заявки.
provider_session_id ID сесії провайдера.
callback_event_id ID callback-події.
file_hash_sha256 Hash документа.

23. Модель даних

23.1. signature_providers

Поле Тип Опис
id uuid ID провайдера.
code varchar DIIA_SIGN, PRIVAT24_SMARTID, IIT_CSK тощо.
name varchar Назва.
integration_type varchar API, LOCAL_AGENT, FILE_KEY, MANUAL_UPLOAD.
status varchar ACTIVE, DISABLED, UNAVAILABLE.
supported_formats jsonb PDF, XML, P7S, ASIC.
supported_signature_types jsonb DETACHED, ENVELOPED, CONTAINER.
priority integer Пріоритет.
is_active boolean Активність.

23.2. signature_integrations

Поле Тип Опис
id uuid ID інтеграції.
provider_id uuid Провайдер.
base_url varchar URL API.
callback_url varchar Callback URL.
credentials_encrypted jsonb Зашифровані credentials.
settings jsonb Технічні налаштування.
is_active boolean Активність.

23.3. sign_documents

Поле Тип Опис
id uuid ID документа.
external_document_id varchar ID документа в K2 ERP.
document_type varchar CONTRACT, ACT, REPORT.
document_name varchar Назва.
document_number varchar Номер.
document_date date Дата.
current_version_id uuid Поточна версія.
status varchar Статус.

23.4. sign_document_versions

Поле Тип Опис
id uuid ID версії.
document_id uuid Документ.
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 Дата створення.

23.5. signature_requests

Поле Тип Опис
id uuid ID заявки.
document_id uuid Документ.
document_version_id uuid Версія.
provider_id uuid Провайдер.
signer_id uuid Підписант.
signature_type varchar DETACHED, ENVELOPED, CONTAINER.
idempotency_key varchar Ключ дедублікації.
status varchar Статус.
expires_at timestamp Строк дії.
created_at timestamp Дата створення.

23.6. signature_sessions

Поле Тип Опис
id uuid ID сесії.
signature_request_id uuid Заявка.
provider_session_id varchar ID сесії провайдера.
status varchar Статус.
qr_payload text QR payload, якщо є.
deep_link text Deep link, якщо є.
raw_request jsonb Запит.
raw_response jsonb Відповідь.
expires_at timestamp Строк дії.

23.7. signature_files

Поле Тип Опис
id uuid ID файлу підпису.
signature_request_id uuid Заявка.
provider_id uuid Провайдер.
file_id uuid Файл у сховищі.
file_type varchar signature, signed_container, signed_pdf.
signature_format varchar P7S, ASIC, CAdES, XAdES, PDF.
file_hash_sha256 varchar Hash файлу.
source varchar API, LOCAL_AGENT, MANUAL_UPLOAD.
created_at timestamp Дата.

23.8. signature_verifications

Поле Тип Опис
id uuid ID перевірки.
signature_request_id uuid Заявка.
result varchar VALID, INVALID, HASH_MISMATCH.
signer_name varchar ПІБ підписанта.
signer_identifier varchar РНОКПП / ЄДРПОУ, якщо доступно.
provider_name varchar КНЕДП / провайдер сертифіката.
signed_at timestamp Час підписання.
certificate_info jsonb Дані сертифіката.
raw_result jsonb Повний результат.
created_at timestamp Дата перевірки.

23.9. signature_events

Поле Тип Опис
id uuid ID події.
entity_type varchar document, request, session, verification, provider.
entity_id uuid ID сутності.
provider_code varchar Провайдер.
event_type varchar Тип події.
old_status varchar Старий статус.
new_status varchar Новий статус.
source varchar K2_ERP, PYTHON_SERVICE, PROVIDER, USER.
payload jsonb Технічні дані.
created_at timestamp Дата.

24. Приклад Python-логіки

24.1. Створення заявки

async 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,
    )

    allowed_providers = policy_engine.get_allowed_providers(
        document_type=document.document_type,
        signer_type=command.signer.signer_type,
    )

    if command.provider_code not in allowed_providers:
        raise BusinessError("Provider is not allowed for this document")

    signature_validator.validate_document_for_signing(document, command)

    provider = provider_repository.get_by_code(db, command.provider_code)

    request = signature_request_repository.create(
        db=db,
        data={
            "document_id": document.id,
            "document_version_id": document.current_version_id,
            "provider_id": provider.id,
            "signer_id": command.signer_id,
            "signature_type": command.signature_type,
            "idempotency_key": command.idempotency_key,
            "status": "CREATING",
            "expires_at": command.expires_at,
        },
    )

    signature_queue.enqueue(
        task_name="start_signature_session",
        payload={"signature_request_id": str(request.id)},
    )

    db.commit()
    return request

24.2. Запуск сесії через router

async def start_signature_session(signature_request_id: str, db: "Session") -> None:
    request = signature_request_repository.get_by_id(db, signature_request_id)
    provider = request.provider
    adapter = provider_router.get_adapter(provider.code)

    try:
        payload = signature_mapper.to_provider_payload(request)

        response = await adapter.create_signature_session(payload)

        signature_session_repository.create(
            db=db,
            data={
                "signature_request_id": request.id,
                "provider_session_id": response.get("session_id"),
                "status": "ACTIVE",
                "qr_payload": response.get("qr_payload"),
                "deep_link": response.get("deep_link"),
                "raw_request": payload,
                "raw_response": response,
                "expires_at": response.get("expires_at"),
            },
        )

        request.status = "WAITING_SIGNATURE"

    except Exception as exc:
        request.status = "SIGN_ERROR"
        request.error_message = str(exc)

    finally:
        db.commit()

24.3. Callback controller

from fastapi import APIRouter, Request, HTTPException

router = APIRouter()


@router.post("/api/v1/signature/callback/{provider_code}")
async def signature_callback(provider_code: str, request: Request):
    payload = await request.json()

    adapter = provider_router.get_adapter(provider_code)

    if not await adapter.verify_callback(request, payload):
        raise HTTPException(status_code=401, detail="Invalid callback")

    callback_id = callback_service.get_callback_id(provider_code, payload)

    if callback_repository.exists(callback_id):
        return {"status": "already_processed"}

    callback_repository.create_raw_event(
        provider_code=provider_code,
        callback_id=callback_id,
        payload=payload,
    )

    result = await adapter.parse_callback(payload)

    signature_processor.process_provider_result(
        provider_code=provider_code,
        result=result,
    )

    return {"status": "ok"}

24.4. Ручне завантаження підпису

async def upload_manual_signature(document_id: str, file: "UploadedFile", user: "User", db: "Session"):
    document = document_repository.get_by_id(db, document_id)

    if document.status not in ["READY_TO_SIGN", "WAITING_SIGNATURE", "SIGN_ERROR"]:
        raise BusinessError("Document cannot accept signature in current status")

    stored_file = await file_storage.save(file)

    signature_file = signature_file_repository.create(
        db=db,
        data={
            "signature_request_id": None,
            "provider_id": provider_repository.get_by_code(db, "MANUAL_UPLOAD").id,
            "file_id": stored_file.id,
            "file_type": "signature",
            "signature_format": signature_format_detector.detect(file.filename, stored_file.bytes),
            "file_hash_sha256": stored_file.sha256,
            "source": "MANUAL_UPLOAD",
        },
    )

    verification_queue.enqueue(
        task_name="verify_uploaded_signature",
        payload={
            "document_id": str(document.id),
            "signature_file_id": str(signature_file.id),
        },
    )

    db.commit()
    return signature_file

24.5. Перевірка підпису

async 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)

    verifier = verification_service_factory.get_verifier(signature_file.signature_format)

    result = await verifier.verify(
        document_file_id=document_version.file_id,
        signature_file_id=signature_file.file_id,
        expected_hash=document_version.file_hash_sha256,
        expected_signer_id=request.signer_id,
    )

    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,
            "provider_name": result.provider_name,
            "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"
    elif result.code in ["SIGNER_MISMATCH", "UNKNOWN_FORMAT"]:
        request.status = "MANUAL_REVIEW"
    else:
        request.status = "VERIFY_ERROR"

    db.commit()

25. Обробка помилок

Тип помилки Опис Дія системи
ProviderNotAllowedError Провайдер не дозволений для документа. Заблокувати заявку.
ProviderUnavailableError Провайдер недоступний. Запропонувати інший провайдер.
AuthError Невірні credentials провайдера. Вимкнути інтеграцію, повідомити адміністратора.
DocumentChangedError Документ змінився після заявки. INVALIDATED.
UnsupportedFormatError Провайдер не підтримує формат. Запропонувати інший провайдер.
SignatureResultError Не вдалося отримати підпис. SIGN_ERROR або retry.
CallbackValidationError Callback не пройшов перевірку. Відхилити callback.
LocalAgentUnavailableError Локальний агент недоступний. Показати інструкцію користувачу.
KeyReadError Не вдалося прочитати файловий ключ. Показати користувачу помилку.
VerificationError Підпис не пройшов перевірку. VERIFY_ERROR.
SignerMismatchError Підписант не відповідає очікуваному. MANUAL_REVIEW.

26. Retry-логіка

Retry дозволений для:

  • timeout;
  • HTTP 429;
  • HTTP 500;
  • HTTP 502;
  • HTTP 503;
  • HTTP 504;
  • тимчасової недоступності провайдера;
  • тимчасової помилки polling;
  • тимчасової помилки отримання результату;
  • тимчасової помилки перевірки;
  • повторного callback з тим самим callback_id.

Retry заборонений для:

  • зміненого документа;
  • невалідного callback;
  • відхилення користувачем;
  • невідповідності підписанта;
  • невалідного пароля файлового ключа;
  • фінального статусу VERIFIED;
  • ручного рішення адміністратора.

27. Dashboard керівника

27.1. Основні KPI

KPI Опис Колір
Документів створено Загальна кількість документів. Інформація
Очікують підпису Активні заявки. Увага
Підписано Підпис отримано. Норма
Перевірено Підпис валідний. Норма
Помилки підпису Помилки провайдера або локального агента. Критично
Помилки перевірки Невалідні підписи. Критично
Прострочено Не підписано у строк. Потрібна дія
Ручна перевірка Потрібне втручання. Контроль

27.2. Приклад dashboard по провайдерах

Провайдер Очікує Підписано Перевірено Помилки Стан
Дія.Підпис 18 92 90 2 Норма
Приват24 SmartID 12 64 63 1 Норма
Файловий КЕП 6 40 37 3 Контроль
ІІТ / ЦСК 4 52 51 1 Норма
Ручне завантаження 3 12 10 2 Ручна перевірка

27.3. Проблемні документи

Дата Документ Провайдер Підписант Статус Причина Дія
07.05.2026 Договір №123 Дія.Підпис Іван Петренко Прострочено Не завершено підписання Створити нову заявку
07.05.2026 Акт №45 SmartID Олена Сидоренко Помилка перевірки Hash не збігається Ручна перевірка
07.05.2026 Звіт XML Файловий КЕП Бухгалтер Помилка Невірний пароль ключа Повторити підписання

28. Безпека

Система повинна забезпечити:

  • HTTPS для всіх endpoint-ів;
  • зберігання секретів у secret storage;
  • шифрування credentials;
  • заборону зберігання пароля до файлового КЕП;
  • заборону передачі приватного ключа на backend, якщо використовується локальний агент;
  • перевірку callback signature;
  • ідемпотентність callback;
  • контроль версій документа;
  • контроль hash;
  • рольову модель;
  • маскування персональних даних у логах;
  • журнал усіх дій;
  • контроль доступу до файлів;
  • окремі права на ручну перевірку;
  • окремі права на зміну провайдера;
  • окремі права на повторне підписання.

29. Логування та аудит

Система повинна логувати:

Подія Що зберігати
Створення документа Тип, номер, hash, версія.
Вибір провайдера Хто обрав, який провайдер.
Створення заявки Підписант, строк, provider_code.
Створення сесії provider_session_id, статус.
Callback provider_code, callback_id, raw payload.
Polling Старий статус, новий статус.
Локальне підписання Тип агента, результат, без пароля ключа.
Ручне завантаження Хто завантажив, файл, hash.
Перевірка підпису Результат, підписант, сертифікат.
Помилка Код, повідомлення, без секретів.
Ручне рішення Хто ухвалив, коментар, дата.

30. Acceptance Criteria

30.1. Провайдери

Критерій Очікуваний результат
AC-1 Адміністратор створює провайдера Дія.Підпис. Провайдер доступний у списку.
AC-2 Адміністратор створює провайдера SmartID. Провайдер доступний у списку.
AC-3 Адміністратор створює провайдера файлового КЕП. Провайдер доступний для локального підпису.
AC-4 Провайдер недоступний. Система показує статус UNAVAILABLE.

30.2. Документи

Критерій Очікуваний результат
AC-5 Документ валідний. Система дозволяє створити заявку.
AC-6 Документ змінено після заявки. Стара заявка стає INVALIDATED.
AC-7 Провайдер не підтримує формат. Система пропонує іншого провайдера або блокує заявку.

30.3. Підписання

Критерій Очікуваний результат
AC-8 Користувач обирає Дія.Підпис. Створюється сесія Дії.
AC-9 Користувач обирає SmartID. Створюється сесія SmartID.
AC-10 Користувач обирає файловий КЕП. Запускається локальний або файловий сценарій.
AC-11 Підпис отримано. Статус стає SIGNED.

30.4. Перевірка

Критерій Очікуваний результат
AC-12 Підпис валідний. Статус стає VERIFIED.
AC-13 Hash не збігається. Статус стає VERIFY_ERROR.
AC-14 Підписант не збігається. Статус стає MANUAL_REVIEW.

30.5. Ручне завантаження

Критерій Очікуваний результат
AC-15 Користувач завантажує p7s. Система зберігає файл і запускає перевірку.
AC-16 Підпис відповідає документу. Документ стає VERIFIED.
AC-17 Підпис не відповідає документу. Документ стає VERIFY_ERROR.

30.6. Dashboard

Критерій Очікуваний результат
AC-18 Керівник відкриває dashboard. Він бачить статистику по всіх провайдерах.
AC-19 Є помилки підписання. Вони підсвічуються червоним.
AC-20 Є прострочені заявки. Вони підсвічуються помаранчевим.
AC-21 Є ручна перевірка. Вона підсвічується фіолетовим.

31. MVP

До MVP входить:

  • єдиний Signature API;
  • довідник провайдерів;
  • Provider Router;
  • Signature Provider Interface;
  • підтримка Дія.Підпис як адаптера, якщо є API-доступ;
  • підтримка SmartID як адаптера, якщо є API-доступ;
  • підтримка ручного завантаження p7s;
  • підтримка файлового КЕП через локальний агент або ІІТ-адаптер;
  • версіонування документа;
  • hash документа;
  • створення заявки;
  • статуси;
  • callback endpoint;
  • polling worker;
  • збереження підпису;
  • перевірка підпису;
  • dashboard API;
  • журнал подій;
  • retry;
  • unit-тести;
  • mock adapters.

До MVP не входить:

  • повна підтримка всіх КНЕДП України;
  • повна підтримка всіх форматів ASIC / XAdES;
  • складний UI локального агента;
  • архів довгострокового зберігання за окремим регламентом;
  • автоматичне юридичне трактування підпису;
  • повна інтеграція з усіма ЕДО-системами;
  • власний КНЕДП.

32. Етапи реалізації

Етап 1. Аналіз провайдерів

  • визначити провайдерів MVP;
  • отримати документацію Дія.Підпис;
  • отримати документацію SmartID;
  • визначити локальну бібліотеку для файлового КЕП;
  • визначити формати підпису;
  • визначити правила перевірки.

Етап 2. Базовий Python-сервіс

  • створити FastAPI-проєкт;
  • налаштувати PostgreSQL;
  • створити моделі провайдерів, документів, заявок, сесій;
  • налаштувати Alembic;
  • реалізувати healthcheck.

Етап 3. Уніфікований інтерфейс

  • реалізувати SignatureProviderAdapter;
  • реалізувати ProviderRouter;
  • реалізувати PolicyEngine;
  • реалізувати SignatureRequestService.

Етап 4. Адаптери

  • реалізувати DiiaSignAdapter;
  • реалізувати SmartIDAdapter;
  • реалізувати ManualUploadAdapter;
  • реалізувати FileKey/IITAdapter;
  • реалізувати mock adapters.

Етап 5. Документи та hash

  • реалізувати завантаження документа;
  • реалізувати версіонування;
  • реалізувати hash;
  • реалізувати дедублікацію.

Етап 6. Callback / polling

  • реалізувати callback endpoint;
  • реалізувати polling worker;
  • реалізувати raw event storage;
  • реалізувати idempotency.

Етап 7. Перевірка підпису

  • реалізувати VerificationService;
  • реалізувати перевірку p7s;
  • реалізувати перевірку контейнерів;
  • реалізувати статуси перевірки;
  • реалізувати ручну перевірку.

Етап 8. Dashboard та аудит

  • реалізувати dashboard API;
  • реалізувати списки проблемних документів;
  • реалізувати статистику по провайдерах;
  • реалізувати експорт журналу.

Етап 9. Production hardening

  • додати rate limiting;
  • додати alerting;
  • додати dead letter queue;
  • додати backup файлів;
  • додати моніторинг провайдерів;
  • додати secure secret storage.

33. Ризики

Ризик Опис Як зменшити
API провайдера недоступний Хмарний підпис не працює. Запропонувати іншого провайдера або manual upload.
Різні формати підписів Не всі формати однаково перевіряються. Signature format detector і окремі verifier-и.
Документ змінено Підпис може бути накладений на стару версію. Hash і document version lock.
Callback дублюється Може повторно змінити статус. Idempotent callback.
Локальний агент недоступний Користувач не може підписати файловим КЕП. Інструкція, fallback, healthcheck агента.
Невідповідність підписанта Підписала інша особа. Verification + signer matching.
Невідомий КНЕДП Сертифікат не розпізнано. Trust list / manual review.
Секрети потрапили в лог Ризик компрометації. Маскування, secure logging.

34. Відкриті питання

  1. Які провайдери мають бути в MVP?
  2. Чи є офіційний API-доступ до Дія.Підпис?
  3. Чи є офіційний API-доступ до SmartID?
  4. Чи потрібен локальний агент для файлового КЕП?
  5. Чи потрібно підтримувати ІІТ DLL/SO напряму?
  6. Які формати документів підписуємо: PDF, XML, DOCX, ZIP?
  7. Який тип підпису потрібен: detached, container, embedded?
  8. Чи потрібно пакетне підписання?
  9. Чи потрібно підписувати документи клієнтами?
  10. Чи потрібно підписувати документи співробітниками?
  11. Чи потрібно перевіряти РНОКПП / ЄДРПОУ підписанта?
  12. Чи потрібен довгостроковий архів підписаних документів?
  13. Чи потрібна інтеграція з K2 ERP задачами?
  14. Чи потрібні нагадування про прострочення?
  15. Чи потрібен mobile UI?
  16. Чи потрібен експорт журналу в Excel?

35. Джерела

  • Офіційний сервіс КЕП Дії.
  • Офіційна сторінка підписання документів на порталі Дія.
  • Офіційна сторінка Дія.Підпис.
  • Офіційна сторінка SmartID ПриватБанку.
  • Інструкції ПриватБанку щодо створення SmartID.
  • Документація ІІТ / Користувач ЦСК-1.
  • Документація КНЕДП ДПС.
  • Офіційна партнерська документація конкретних провайдерів.
  • Внутрішня документація K2 ERP.

36. Див. також