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. Відкриті питання
- Які провайдери мають бути в MVP?
- Чи є офіційний API-доступ до Дія.Підпис?
- Чи є офіційний API-доступ до SmartID?
- Чи потрібен локальний агент для файлового КЕП?
- Чи потрібно підтримувати ІІТ DLL/SO напряму?
- Які формати документів підписуємо: PDF, XML, DOCX, ZIP?
- Який тип підпису потрібен: detached, container, embedded?
- Чи потрібно пакетне підписання?
- Чи потрібно підписувати документи клієнтами?
- Чи потрібно підписувати документи співробітниками?
- Чи потрібно перевіряти РНОКПП / ЄДРПОУ підписанта?
- Чи потрібен довгостроковий архів підписаних документів?
- Чи потрібна інтеграція з K2 ERP задачами?
- Чи потрібні нагадування про прострочення?
- Чи потрібен mobile UI?
- Чи потрібен експорт журналу в Excel?
35. Джерела
- Офіційний сервіс КЕП Дії.
- Офіційна сторінка підписання документів на порталі Дія.
- Офіційна сторінка Дія.Підпис.
- Офіційна сторінка SmartID ПриватБанку.
- Інструкції ПриватБанку щодо створення SmartID.
- Документація ІІТ / Користувач ЦСК-1.
- Документація КНЕДП ДПС.
- Офіційна партнерська документація конкретних провайдерів.
- Внутрішня документація K2 ERP.
36. Див. також