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

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

Матеріал з K2 ERP Wiki

document = document_repository.get_by_id(db, document_id)

Як користувач системи, 

== 29.; Логування та аудит ==
 )
GET /api/v1/signature/dashboard?date_from=2026-05-01&date_to=2026-05-31

 v
 def get_adapter(self, provider_code: str) -> SignatureProviderAdapter:
 )

 signature_session_repository.create(
 "file_hash_sha256": stored_file.sha256,
</pre>

POST /api/v1/signature/documents
[[Категорія:K2 ERP]]
 provider_code=provider_code,

{| class="wikitable"

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

!; | Він бачить статистику по всіх провайдерах.; Колір
 "external_signer_id": "CLIENT-001",
 return {"status": "ok"}

{| class="wikitable"
!; |-
| supported_formats
| jsonb
| PDF, XML, P7S, ASIC.; |-
| Перевірка підпису
| Результат, підписант, сертифікат.; |-
| entity_type
| varchar
| document, request, session, verification, provider.; |-
| AC-13
| Hash не збігається.; | Запропонувати інший провайдер.; |-
| IITAdapter
| інтеграційні функціональні можливості з бібліотеками ІІТ або локальним агентом.; GET /api/v1/signature/providers

!; HTML
7.; | Так
|-
| DOCX
| Документи Word.; характеристика
 "expires_at": response.get("expires_at"),
 v
 "file_name": "contract_123.pdf",
=== 7.4.; Варіант 4.; Ручне завантаження підпису ===

!; |}

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

</div>
{| class="wikitable"
!; характеристика

<pre>
POST /api/v1/signature/callback/{provider_code}
[[Категорія:API]]
AC-15 - document_id uuid - CallbackValidationError Документ стає VERIFIED.; * Офіційна сторінка підписання документів на порталі Дія.; | Опційно
JSON - Audit Event платформа зберігає файл і запускає перевірку.; |}
request.status = "VERIFY_ERROR"
; Тип помилки callback_id = callback_service.get_callback_id(provider_code, payload) POST /api/v1/signature/documents/{document_id}/verify adapter = provider_router.get_adapter(provider_code)

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

pass
provider_code=provider_code,

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

async def create_signature_request(command: "CreateSignatureRequestCommand", db: "Session") -> "SignatureRequest": sha256(file_bytes)

Створення сесії, підписання, тест.; # Чи потрібен локальний агент для файлового КЕП?; | Verification + signer matching.; |- AC-16 DRAFT, DISABLED, CANCELLED.; характеристика

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

Статус стає VERIFIED.; |- AC-2 class="wikitable"
def __init__(self, adapters: dict [str, SignatureProviderAdapter]):
id uuid - priority integer - Document Version - file_id uuid Файл.; характеристика

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

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

payload={"signature_request_id": str(request.id)},
"idempotency_key": command.idempotency_key,
},
; супровід в MVP
"certificate_info": result.certificate_info,

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

K2 ERP / Dashboard / Archive

11.; |}

 document = document_repository.get_by_external_id(

!; |-
| credentials_encrypted
| jsonb
| Зашифровані credentials.; | style="background:#ffcc80;" | Помаранчевий
|-
| Помилка авторизації
| AUTH_ERROR
| Невірні credentials.; Підписант
</syntaxhighlight>
{| class="wikitable"
|-
| AC-1
| Адміністратор створює провайдера Дія.Підпис.; Критерій
if existing:
)

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

provider_session_id varchar style="background:#fff9c4;" | Важливий
ІІТ / користувач системи ЦСК-1 IIT_CSK DLL / COM / SO / Java / JS / локальний агент - Document Version Service Опційно
CAdES style="background:#fff9c4;" | Жовтий
Очікує підпису WAITING_SIGNATURE - AC-7 Провайдер не підтримує роботу формат.; signature_file_id=signature_file.file_id,

!; |-
| XML-підпис
| XML_SIGN
| Підпис XML-документа.; Очікуваний результат
 external_document_id=command.external_document_id,
!; @router.post("/api/v1/signature/callback/{provider_code}")

* додати rate limiting;
* додати alerting;
* додати dead letter queue;
* додати backup файлів;
* додати моніторинг провайдерів;
* додати secure secret storage.; |-
| file_size
| integer
| Розмір.; |}

async def start_signature_session(signature_request_id: str, db: "Session") -> None:

{| class="wikitable"
!; data={
!; Код
== 2.; Область сфера застосування ==
!; !; | Очікує підпису, вибір провайдера.; Коментар
K2 ERP / CRM / Website
Provider Adapters
Як розробник K2 ERP, 
|-
| id
| uuid
| ID заявки.; Код
!; №
== 12.; Статуси провайдера ==
{| class="wikitable"

from abc import ABC, abstractmethod

 },
=== 17.1.; Список провайдерів ===
async def upload_manual_signature(document_id: str, file: "UploadedFile", user: "User", db: "Session"):

 if not await adapter.verify_callback(request, payload):
4.; | SIGN_ERROR або retry.; | XAdES.; | Вони підсвічуються помаранчевим.; signature_validator.validate_document_for_signing(document, command)

* офіційно затверджений сервіс КЕП Дії.; |-
| version_number
| integer
| Номер версії.; |-
| supported_formats
| jsonb
| PDF, XML, p7s, ASIC тощо.; |-
| AC-4
| Провайдер недоступний.; |-
| provider_id
| uuid
| Провайдер.; |-
| AC-3
| Адміністратор створює провайдера файлового КЕП.; * Внутрішня документація K2 ERP.; | style="background:#c8e6c9;" | Зелений
|-
| Тестовий режим
| TEST_MODE
| Провайдер доступний тільки в тесті.; користувач системи використовує файловий КЕП.; |-
| entity_id
| uuid
| ID сутності.; !; |}

!; |-
| file_type
| varchar
| signature, signed_container, signed_pdf.; | style="background:#bbdefb;" | Блакитний
|-
| Очікує вибору провайдера
| WAITING_PROVIDER
| Підписант ще не обрав спосіб підпису.; характеристика

=== Етап 6.; Callback / polling ===
{| class="wikitable"
<div style="border-left: 6px solid #f57c00; background: #fff3e0; padding: 12px 16px; margin: 16px 0;">
[[Категорія:Python]]

</syntaxhighlight>
 db=db,
 db.commit()

<syntaxhighlight lang="python">

 async def cancel_session(self, session_id: str) -> dict:
!; | style="background:#ef9a9a;" | Червоний
|-
| Сертифікат відкликаний
| CERT_REVOKED
| Сертифікат відкликаний.; |-
| result
| varchar
| VALID, INVALID, HASH_MISMATCH.; |-
| signature_request_id
| uuid
| Заявка.; |-
| base_url
| varchar
| URL API.; |-
| mime_type
| varchar
| MIME type.; |-
| base_url
| varchar
| URL API, якщо є собою.; |-
| provider_code
| Провайдер підпису.; |-
| document_type
| varchar
| CONTRACT, ACT, REPORT.; |-
| current_version_id
| uuid
| Поточна реліз.; | Signature format detector і окремі verifier-и.; async def get_session_status(self, session_id: str) -> dict:
 "signature_request_id": request.id,
!; Signature Storage зберігає файл підпису / контейнер.; |-
| signer_id
| uuid
| Підписант.; |-
| Рекомендація
| Використовувати локальний агент або браузерний компонент, а не передавати ключ на backend.; # Чи потрібен експорт журналу в Excel?; Обраний adapter створює сесію або локальну операцію підпису.; {| class="wikitable"
 db=db,
|-
| Активний
| ACTIVE
| Провайдер доступний для підписання.; |-
| переважні аспекти
| Приватний ключ не покидає комп'ютер користувача.; !; | style="background:#c8e6c9;" | Високий
|-
| Файловий КЕП
| FILE_KEY
| Локальне підписання
| Ключ типу Key-6.dat або інший файловий контейнер.; Python Signature Service створює версію документа.; |-
| ManualUploadAdapter
| Ручне завантаження p7s / контейнера.; |-
| provider_session_id
| ID сесії провайдера.; |-
| document_name
| varchar
| Назва.; Вибір адаптера
</syntaxhighlight>
 db.commit()
 |
 |-- DiiaSignAdapter
 |-- SmartIDAdapter
 |-- FileKeyAdapter
 |-- IITAdapter
 |-- TaxCSKAdapter
 |-- ManualUploadAdapter
 |
 "raw_request": payload,
 except Exception as exc:
 |
 | 4.; |-
| style="background:#fff9c4;" | Жовтий
| #fff9c4
| Очікування дії користувача.; характеристика
!; |-
| provider_code
| varchar
| Провайдер.;=== 10.1.; користувач системи обирає провайдера ===

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

!; Поле

 return {"status": "already_processed"}
=== 10.2.; K2 ERP функціонує з єдиним API ===
 db=db,
 request.status = "VERIFIED"
!; # Чи є собою офіційно затверджений API-доступ до Дія.Підпис?; |-
| created_at
| timestamp
| Дата створення.; Де застосовується
=== 17.4.; Отримання доступних провайдерів для документа ===
<div style="border-left: 6px solid #2e7d32; background: #e8f5e9; padding: 12px 16px; margin: 16px 0;">
 "signature_file_id": str(signature_file.id),
|-
| id
| uuid
| ID версії.; Стан

=== 10.3.; Адміністратор керує провайдерами ===
[[Категорія:Інтеграції]]
 "raw_result": result.raw,
!; |}

from fastapi import APIRouter, Request, HTTPException

 "document_id": str(document.id),
{| class="wikitable"
!; |-
| code
| varchar
| DIIA_SIGN, PRIVAT24_SMARTID, IIT_CSK тощо.; |-
| name
| varchar
| Назва.; |-
| ProviderUnavailableError
| Провайдер недоступний.; "k2_entity": "contract",
 "qr_payload": response.get("qr_payload"),
=== 17.11.; Перевірка підпису ===
щоб не реалізовувати окрему логіку для кожного сервісного центру.; |-
| Verification Result
| Результат перевірки.; |-
| provider_name
| varchar
| КНЕДП / провайдер сертифіката.;=== 27.3.; Проблемні документи ===

* реалізувати DiiaSignAdapter;
* реалізувати SmartIDAdapter;
* реалізувати ManualUploadAdapter;
* реалізувати FileKey/IITAdapter;
* реалізувати mock adapters.; |-
| version_number
| Номер версії.; |-
| file_name
| varchar
| Назва файлу.; Код
<syntaxhighlight lang="python">

=== 27.2.; Приклад dashboard по провайдерах ===
<div style="border-left: 6px solid #c62828; background: #ffebee; padding: 12px 16px; margin: 16px 0;">
 )
 signature_file = signature_file_repository.create(

 "raw_response": response,
== 5.; Підтримувані формати ==

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

Як керівник, 

 pass
=== 10.4.; Керівник бачить контроль ===
!; !; Критерій

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

 )

=== 24.3. Callback controller ===
До MVP не входить:
=== 17.12. Dashboard ===
 document_version = document_version_repository.get_by_id(db, request.document_version_id)
|-
| id
| uuid
| ID сесії.; |-
| file_id
| uuid
| Файл у сховищі.; Тип

* реалізувати dashboard API;
* реалізувати списки проблемних документів;
* реалізувати статистику по провайдерах;
* реалізувати експорт журналу.; Валідація, hash, версії, політики
 finally:
5.; |-
| SmartIDAdapter
| інтеграційні функціональні можливості з Приват24 / SmartID.; GET /api/v1/signature/documents/{document_id}/signed-file
 "signature_type": command.signature_type,
{{DISPLAYTITLE:Технічне завдання: Уніфіковане накладання електронного підпису різних сервісних центрів України для Python}}
|-
| AC-8
| користувач системи обирає Дія.Підпис.; Підписано

2.; |-
| style="background:#eeeeee;" | Сірий
| #eeeeee
| Чернетка, вимкнено, скасовано або архів.; !; |-
| Document
| Документ, який потрібно підписати.; |-
| callback_url
| varchar
| Callback URL.; # Чи потрібна інтеграційні функціональні можливості з K2 ERP задачами?; * наявність документа;
* наявність актуальної версії документа;
* наявність file_id;
* доступність файлу у сховищі;
* розмір файлу;
* MIME type;
* hash документа;
* тип документа;
* тип підписанта;
* доступні провайдери для документа;
* чи підтримує роботу провайдер формат документа;
* чи підтримує роботу провайдер потрібний тип підпису;
* чи не був документ змінений після створення заявки;
* чи не підписаний документ цим підписантом раніше;
* чи є собою idempotency_key;
* чи надає можливість бізнес-процес підписання.; Провайдер
== 9.; Основні сутності ==
<div style="border-left: 6px solid #1565c0; background: #e3f2fd; padding: 12px 16px; margin: 16px 0;">

 if provider_code not in self.adapters:
</div>
{| class="wikitable"

!; "status": "ACTIVE",
 allowed_providers = policy_engine.get_allowed_providers(

23.9. signature_events

if callback_repository.exists(callback_id):
signature_queue.enqueue(
"tax_id": "1234567890"

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

Окремо варто відзначити який надає K2 ERP / CRM / документообігу один уніфікований API; додатково реалізовано ПриватБанк SmartID, файловий КЕП, ІІТ, КЕП ДПС і інші КНЕДП.; |-

AC-11 Підпис отримано.; Параметр MANUAL_REVIEW.; Приклад
stored_file = await file_storage.save(file)
- Обов'язкова умова Автоматична перевірка підпису після завантаження.; )

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

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

31. MVP

35.; Джерела

API провайдера недоступний Idempotent callback.; Колір
  • реалізувати SignatureProviderAdapter;
  • реалізувати ProviderRouter;
  • реалізувати PolicyEngine;
  • реалізувати SignatureRequestService.; |-
raw_result jsonb Повний результат.; Поле ; Колір


}

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

10.; Тип інтеграції

23.5. signature_requests

26.; Retry-логіка

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

10. User Story

"signed_at": result.signed_at,
  • єдиний Signature API;
  • довідник провайдерів;
  • Provider Router;
  • Signature Provider Interface;
  • супровід Дія.Підпис як адаптера, якщо є собою API-доступ;
  • супровід SmartID як адаптера, якщо є собою API-доступ;
  • супровід ручного завантаження p7s;
  • супровід файлового КЕП через локальний агент або ІІТ-адаптер;
  • версіонування документа;
  • hash документа;
  • створення заявки;
  • статуси;
  • callback endpoint;
  • polling worker;
  • збереження підпису;
  • перевірка підпису;
  • dashboard API;
  • журнал подій;
  • retry;
  • unit-тести;
  • mock adapters.; | style="background:#eeeeee;" | Сірий
request = signature_request_repository.create(
; №
  1. Які провайдери мають бути в MVP?; |-
raw_response jsonb - signed_at timestamp style="background:#c8e6c9;" | Зелений
Невалідний INVALID - AC-6 }
"callback_context": {
"signature_request_id": request.id,
- document_version_id uuid реліз.; Дія системи
task_name="verify_uploaded_signature",
provider = provider_repository.get_by_code(db, command.provider_code)

щоб контролювати доступні сценарії підписання.; !; Поле POST /api/v1/signature/providers/{provider_code}/check-connection

AC-12 - DiiaSignAdapter Вони підсвічуються червоним.; |}
db.commit()
provider = request.provider

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

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


document_file_id=document_version.file_id,
XML із XAdES.; |} платформа надає можливість створити заявку.; |- KeyReadError style="background:#fff9c4;" | Важливий
Ручне завантаження підпису MANUAL_UPLOAD Upload p7s / container Fallback-сценарій.; Параметр

 return self.adapters [provider_code]
== 1.; Мета ==
=== 24.5.; Перевірка підпису ===
!; | style="background:#ef9a9a;" | Червоний
|-
| Невідомий формат
| UNKNOWN_FORMAT
| Формат підпису не розпізнано.; характеристика

=== 30.3.; Підписання ===
 elif result.code in ["SIGNER_MISMATCH", "UNKNOWN_FORMAT"]:
 "deep_link": response.get("deep_link"),
|-
| external_document_id
| ID документа в K2 ERP.; | style="background:#c8e6c9;" | Високий
|-
| ПриватБанк SmartID / Приват24
| PRIVAT24_SMARTID
| API / polling / callback / ручне завантаження
| Хмарний КЕП ПриватБанку.; |-
| priority
| integer
| Пріоритет.; | Статус стає VERIFY_ERROR.; характеристика
 async def get_signature_result(self, session_id: str) -> dict:
!; користувач системи підтверджує підпис.; Статус
Уніфікований компонент застосовується для:

=== 30.4.; Перевірка ===

!; )

!; Dashboard показує результат.; |-
| Polling Worker
| Перевіряє статуси сесій.; |-
| payload
| jsonb
| Технічні інформаційні дані.; KPI

6.; | style="background:#ef9a9a;" | Червоний
|-
| Hash не збігається
| HASH_MISMATCH
| Підпис не відповідає версії документа.; |-
| certificate_info
| jsonb
| інформаційні дані сертифіката.; |-
| Локальне підписання
| Тип агента, результат, без пароля ключа.; Код
=== 23.2. signature_integrations ===
!; |-
| UnsupportedFormatError
| Провайдер не підтримує роботу формат.; '''Критично істотно:''' платформа повинна зберігати, який саме тип підпису було створено: detached, enveloped, container, PDF, XML або hash-sign.; |}

 "document_number": "123",

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

 raise HTTPException(status_code=401, detail="Invalid callback")
!; return signature_file

{| class="wikitable"

=== 17.6.; Отримання статусу заявки ===
!; |-
| Ризики
| Не можна зберігати пароль до ключа на сервері.; №
!; | INVALIDATED.; |-
| Різні формати підписів
| Не всі формати однаково перевіряються.; характеристика

<syntaxhighlight lang="python">

 "provider_id": provider_repository.get_by_code(db, "MANUAL_UPLOAD").id,

== 15. Unified Signature Provider Interface ==
 "file_id": "file-001",
 @abstractmethod
|-
| document_version_id
| ID версії.; | Опційно
|-
| P7S
| Файл підпису.; |-
| Signer
| Підписант.; Ризик

!; |-
| status
| varchar
| ACTIVE, DISABLED, UNAVAILABLE.; характеристика

{| class="wikitable"
!; Призначення
|-
| ProviderNotAllowedError
| Провайдер не дозволений для документа.; |-
| created_at
| timestamp
| Дата.; Провайдер

{| class="wikitable"

* timeout;
* HTTP 429;
* HTTP 500;
* HTTP 502;
* HTTP 503;
* HTTP 504;
* тимчасової недоступності провайдера;
* тимчасової помилки polling;
* тимчасової помилки отримання результату;
* тимчасової помилки перевірки;
* повторного callback з тим самим callback_id.; |-
| file_hash_sha256
| SHA-256 hash.; |-
| Документ змінено
| Підпис має змогу бути накладений на стару версію.; |-
| Недоліки
| Менше автоматизації.;== 4.; Типи підписання ==

[[Категорія:Приват24]]
class SignatureProviderRouter:

!; |}

!; Значення
|-
| Дія.Підпис
| 18
| 92
| 90
| 2
| style="background:#c8e6c9;" | Норма
|-
| Приват24 SmartID
| 12
| 64
| 63
| 1
| style="background:#c8e6c9;" | Норма
|-
| Файловий КЕП
| 6
| 40
| 37
| 3
| style="background:#ffcc80;" | Контроль
|-
| ІІТ / ЦСК
| 4
| 52
| 51
| 1
| style="background:#c8e6c9;" | Норма
|-
| Ручне завантаження
| 3
| 12
| 10
| 2
| style="background:#f3e5f5;" | Ручна перевірка
|}

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

 callback_repository.create_raw_event(
 request.error_message = str(exc)

28.; Безпека

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

expected_signer_id=request.signer_id,

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

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

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

  • HTTPS для всіх endpoint-ів;
  • зберігання секретів у secret storage;
  • шифрування credentials;
  • заборону зберігання пароля до файлового КЕП;
  • заборону передачі приватного ключа на backend, якщо застосовується локальний агент;
  • перевірку callback signature;
  • ідемпотентність callback;
  • контроль версій документа;
  • контроль hash;
  • рольову модель;
  • маскування персональних даних у логах;
  • журнал усіх дій;
  • контроль доступу до файлів;
  • окремі права на ручну перевірку;
  • окремі права на зміну провайдера;
  • окремі права на повторне підписання.; |}
expected_hash=document_version.file_hash_sha256,
style="background:#ef9a9a;" | Критично
Помилки перевірки Невалідні підписи.; характеристика
 request.status = "WAITING_SIGNATURE"

!; |-
| integration_type
| varchar
| API, LOCAL_AGENT, FILE_KEY, MANUAL_UPLOAD.; Провайдер
== 33.; Ризики ==
!; |-
| AC-17
| Підпис не відповідає документу.; |-
| created_at
| timestamp
| Дата перевірки.; |-
| Signature Storage
| Зберігає підписані файли.; |-
| SignerMismatchError
| Підписант не відповідає очікуваному.; |-
| source
| varchar
| API, LOCAL_AGENT, MANUAL_UPLOAD.; |-
| credentials_encrypted
| jsonb
| Зашифровані credentials.; |}

 raise BusinessError("Document cannot accept signature in current status")

 "provider_name": result.provider_name,

"result": result.code,
- file_hash_sha256 varchar style="background:#bbdefb;" | Блакитний
Тимчасово недоступний UNAVAILABLE - Audit Logger Логує всі дії.; Результат - new_status varchar - event_type varchar Провайдер доступний у списку.; | Dashboard, список документів, картка документа.; | style="background:#fff9c4;" | Важливий
Інші КНЕДП OTHER_QTSP Через ІІТ / файловий ключ / API class="wikitable" Відхилити callback.; Поле

class SignatureProviderAdapter(ABC):

response = await adapter.create_signature_session(payload)

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

Кожна реліз документа повинна мати: {{SEO

; характеристика - file_size - idempotency_key SHA-256 hash.; Очікуваний результат style="background:#fff9c4;" | Увага
Підписано - Особливості style="background:#bbdefb;" | Блакитний
Підпис отримано SIGNED - AC-9 } style="background:#f3e5f5;" | Контроль

30.2.; Документи

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

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

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

"source": "MANUAL_UPLOAD",
- idempotency_key varchar - Ручне завантаження Хто завантажив, файл, hash.; характеристика
  • реалізувати VerificationService;
  • реалізувати перевірку p7s;
  • реалізувати перевірку контейнерів;
  • реалізувати статуси перевірки;
  • реалізувати ручну перевірку.; Сутність
"full_name": "Іван Петренко",

23.6. signature_sessions

payload = await request.json()

30. Acceptance Criteria

=== 23.4. sign_document_versions ===
 pass
{| class="wikitable"
 "signature_format": signature_format_detector.detect(file.filename, stored_file.bytes),
 "signer_type": "CLIENT",
Verification Service повинен перевіряти:

=== 17.3.; Створення документа ===
!; | Показати інструкцію користувачу.; |-
| callback_url
| varchar
| Callback URL.; |-
| Signature Session
| Сесія підписання.; Тип

data={

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

23.3. sign_documents

if result.code == "VALID":
id uuid - Створення заявки - SignatureResultError - PDF-підпис PDF_SIGN Підпис у PDF або супровідний p7s.; характеристика - status varchar - LocalAgentUnavailableError - AC-10 - status varchar Статус.;== 19.; Валідація перед підписом == - supported_signature_types jsonb - Polling Старий статус, новий статус.; # Чи потрібні нагадування про прострочення?; "file_type": "signature",
  • створити FastAPI-проєкт;
  • налаштувати PostgreSQL;
  • створити моделі провайдерів, документів, заявок, сесій;
  • налаштувати Alembic;
  • реалізувати healthcheck.; signature_file = signature_file_repository.get_by_id(db, signature_file_id)
@abstractmethod
@abstractmethod
платформа повинна не допускати дублювання заявок і підписів.; # Чи потрібно підтримувати ІІТ DLL/SO напряму?; Пріоритет
- is_active boolean - signer_name varchar - AC-14 - Помаранчевий #ffcc80 - переважні аспекти - provider_name varchar - AC-21 }

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

"provider_code": "DIIA_SIGN",
- Provider Router Вибирає провайдера підпису.; )
Чернетка DRAFT Документ створений, але ще не готовий до підпису.; Поле
"k2_entity_id": "contract-001"
Валідний VALID - callback_event_id - Hash-підпис HASH_SIGN style="background:#ffcc80;" | Потрібна дія
Ручна перевірка style="background:#ef9a9a;" | Червоний
Сертифікат прострочений CERT_EXPIRED - Ручне рішення для бізнесу Хто ухвалив, коментар, дата.; Статус
)
@abstractmethod

</syntaxhighlight>

23.7. signature_files

)

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

self.adapters = adapters
db=db,

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

- Verification Service - Signature Integration конфігурація конкретного провайдера.; №
data={
; Параметр

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

style="background:#c8e6c9;" | Норма
Перевірено - AuthError Показати користувачу помилку.; |- document_date date style="background:#ef9a9a;" | Червоний
Не той підписант SIGNER_MISMATCH Підписант не відповідає очікуваному.; Помилки
"document_date": "2026-05-07",
AC-18 Керівник відкриває dashboard.;=== 30.6. Dashboard === - Signature Request - Статуси Запускається локальний або файловий сценарій.; |- Блакитний #bbdefb операційна дія виконується або тестовий режим.; характеристика
 request.document.status = "VERIFIED"
|-
| provider_code
| varchar
| DIIA_SIGN, PRIVAT24_SMARTID, IIT_CSK тощо.; |-
| deep_link
| text
| Deep link, якщо є собою.; # Чи потрібен довгостроковий архів підписаних документів?; |-
| Секрети потрапили в лог
| Ризик компрометації.; # Чи потрібно пакетне підписання?; | style="background:#ef9a9a;" | Червоний
|-
| Вимкнений
| DISABLED
| Провайдер вимкнений адміністратором.; Тип

3.; |-
| Реалізація
| Localhost API, WebSocket або protocol handler.; |-
| file_hash_sha256
| varchar
| Hash.; платформа отримує результат.; !; |-
| document_number
| varchar
| Номер.; Статус
{| class="wikitable"
== 14.; технічна архітектура рішення для бізнесу ==
!; Критерій

<pre>
=== Етап 8.; Dashboard та аудит ===
 signature_verification_repository.create(
!; | Так
|-
| XML
| Податкова, формування звітів, структуровані документи.; |-
| document_version_id
| реліз документа.; |}

{| class="wikitable"

!; |
 | 3.; |-
| supported_signature_types
| jsonb
| DETACHED, ENVELOPED, CONTAINER.; * Документація КНЕДП ДПС.; | Запропонувати інший провайдер.; | Прострочення, недоступність, відхилення.; |-
| Підходить для
| Робочих місць бухгалтерів, кадровиків, юристів.; |-
| Signature Policy Engine
| Визначає, які провайдери доступні для документа.; | style="background:#eeeeee;" | Сірий
|}

</syntaxhighlight>

користувач системи підписує документ поза системою та завантажує результат.; Тип підпису
|-
| id
| uuid
| ID інтеграції.; |-
| provider_id
| uuid
| Провайдер.; Тип
{| class="wikitable"
я хочу викликати один API підписання, 
|-
| id
| uuid
| ID документа.; |-
| Ризики
| Залежність від зовнішнього API.; Перевірено
Сервіс повинен забезпечити:

8.; характеристика

[[Категорія:КЕП]]
== 27.; Dashboard керівника ==
 |
 | 1.; * зміненого документа;
* невалідного callback;
* відхилення користувачем;
* невідповідності підписанта;
* невалідного пароля файлового ключа;
* фінального статусу VERIFIED;
* ручного рішення для бізнесу адміністратора.; Подія

!; | Опційно
|}

 "signer_id": command.signer_id,

 "status": "CREATING",

* перелік провайдерів підпису, які потрібно підтримати в MVP;
* офіційну документацію кожного API-провайдера;
* credentials для хмарних сервісів;
* тестові ключі або тестові акаунти;
* список форматів документів;
* вимоги до підпису: detached, embedded, container;
* правила перевірки підпису;
* список КНЕДП, які треба підтримати;
* вимоги до зберігання документів;
* вимоги до журналювання;
* вимоги до K2 ERP;
* вимоги до UI підписанта;
* вимоги до мобільного сценарію;
* вимоги до довгострокового архіву.; | Опційно
|-
| ZIP
| Пакет документів.; | Створюється сесія Дії.; | style="background:#ffcc80;" | Резервний
|}

{| class="wikitable"

 "expires_at": "2026-05-07T14:30:00+03:00"
 "external_document_id": "K2-DOC-2026-000123",
<pre>
!; | Створюється сесія SmartID.; K2 ERP отримує фінальний статус.; | style="background:#f3e5f5;" | Фіолетовий
|-
| Скасовано
| CANCELLED
| Заявку або документ скасовано.;

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

  • визначити провайдерів MVP;
  • отримати документацію Дія.Підпис;
  • отримати документацію SmartID;
  • визначити локальну бібліотеку для файлового КЕП;
  • визначити формати підпису;
  • визначити правила перевірки.; | style="background:#c8e6c9;" | Зелений
Відхилено користувачем DECLINED_BY_USER користувач системи відмовився від підписання.; Дата
request.status = "SIGN_ERROR"
)
callback_id=callback_id,
Провайдер доступний у списку.; Тип

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

; Як зменшити ; Очікуваний результат
payload = signature_mapper.to_provider_payload(request)
  • цілісність документа;
  • відповідність підпису конкретній версії документа;
  • валідність підпису;
  • валідність сертифіката;
  • підписанта;
  • дату та час підписання;
  • chain trust;
  • статус відкликання сертифіката, якщо доступно;
  • формат підпису;
  • відповідність очікуваному типу підписанта;
  • чи не змінювався документ після підпису.; | style="background:#ef9a9a;" | Червоний
Помилка перевірки VERIFY_ERROR Підпис не пройшов перевірку.; K2 ERP повинна працювати з єдиним інтерфейсом: створити заявку, отримати статус, отримати підпис, перевірити підпис, зберегти результат.; характеристика

Як адміністратор,

"document_version_id": document.current_version_id,
)

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

},

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

db=db,
"expires_at": command.expires_at,
- integration_type varchar API, LOCAL_AGENT, FILE_KEY, MANUAL_UPLOAD.; Очікуваний результат Помилка API, помилка перевірки.; характеристика
id uuid Документ стає VERIFY_ERROR.; Тип Заблокувати заявку.; Підпис має накладатися тільки на конкретну версію документа.; | Trust list / manual review.; |- created_at timestamp платформа пропонує іншого провайдера або блокує заявку.; №

}

; Тип

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

; характеристика

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

pass
MANUAL_REVIEW.; |}

1.; Колір

я хочу вмикати або вимикати провайдерів підпису,

"idempotency_key": "K2-DOC-2026-000123-sign-v1",
async def check_connection(self) -> dict:
; Колір Провайдер доступний для локального підпису.; |- status varchar }
db=db,
"signature_request_id": None,
; характеристика
Документів створено Загальна кількість документів.; характеристика

Приклад:

@abstractmethod
; Критерій
Дія.Підпис DIIA_SIGN API / QR / deep link / callback Хмарний сценарій через застосунок Дія.; Компонент - Локальний агент недоступний style="background:#fff9c4;" | Жовтий
Підписується SIGNING } - Невідповідність підписанта }
"document_type": "CONTRACT",

Див.; 36.; додатково

request = signature_request_repository.get_by_id(db, signature_request_id)
<pre>
|-
| AC-5
| Документ валідний.; | Вимкнути інтеграцію, повідомити адміністратора.; |-
| Підписаний контейнер
| CONTAINER
| Документ і підпис зберігаються в контейнері.; Очікуваний результат
 if command.provider_code not in allowed_providers:
|-
| переважні аспекти
| функціонує з багатьма КНЕДП.; |-
| qr_payload
| text
| QR payload, якщо є собою.; |-
| Помилка
| Код, повідомлення, без секретів.; | Так
|-
| ASIC
| Контейнер підпису.; | style="background:#ef9a9a;" | Критично
|-
| Прострочено
| Не підписано у строк.; Очікує
 document_type=document.document_type,

 "phone": "+380671112233",
== 8.; Уніфікований бізнес-процес ==
 v
 task_name="start_signature_session",
request = signature_request_repository.get_by_id(db, signature_request_id)
style="background:#c8e6c9;" | Зелений
Підпис перевірено VERIFIED - VerificationError - Особливості Запропонувати іншого провайдера або manual upload.; | style="background:#ffcc80;" | Помаранчевий
Прострочено EXPIRED }
result=result,

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

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

!; Signature Provider Router

* [[Python]]
* [[FastAPI]]
* [[K2 ERP]]
* [[КЕП]]
* [[Електронний підпис]]
* [[Дія.Підпис]]
* [[Приват24]]
* [[SmartID]]
* [[ІІТ]]
* [[Користувач ЦСК-1]]
* [[ДПС]]
* [[КНЕДП]]
* [[p7s]]
* [[CAdES]]
* [[XAdES]]
* [[ASIC]]
* [[Електронний документообіг]]
* [[API інтеграція]]

 if document.status not in ["READY_TO_SIGN", "WAITING_SIGNATURE", "SIGN_ERROR"]:
!; # Чи потрібно підписувати документи клієнтами?; |-
| style="background:#c8e6c9;" | Зелений
| #c8e6c9
| Успішно: підписано, перевірено, провайдер активний.; !; Поле
 |
 | 2.; |-
| Callback Controller
| Приймає callback від хмарних сервісів.; |-
| Комунікація
| Browser  Local Agent  Crypto Library  результат у K2 ERP.; |-
| certificate_settings
| jsonb
| конфігурація сертифікатів.; | Вона підсвічується фіолетовим.; | style="background:#c8e6c9;" | Норма
|-
| Помилки підпису
| Помилки провайдера або локального агента.; |-
| signer_id
| Підписант.; | Маскування, secure logging.; Критерій
<pre>
 request.status = "MANUAL_REVIEW"
GET /api/v1/signature/documents/{document_id}/available-providers
</pre>
 "file_mime_type": "application/pdf",
Retry заборонений для:
!; |-
| signature_request_id
| uuid
| Заявка.; |-
| old_status
| varchar
| Старий статус.; Збереження і перевірка
</div>
 return request
=== 27.1.; Основні KPI ===

{| class="wikitable"

=== Етап 9.; Production hardening ===
платформа повинна забезпечити:
[[Категорія:ІІТ]]
|-
| Unified Signature API
| Єдиний REST API для K2 ERP.; Документ

 verification_queue.enqueue(

* договорів;
* актів виконаних робіт;
* рахунків;
* заяв;
* кадрових документів;
* первинних бухгалтерських документів;
* податкових документів;
* документів ЕДО;
* заявок у CRM;
* документів K2 ERP;
* пакетів документів;
* XML-звітів;
* PDF-документів;
* підтвердження юридично значущих дій.; async def verify_signature(self, document: bytes, signature: bytes, options: dict) -> dict:
=== 23.1. signature_providers ===
!; Що зберігати

=== 14.1.; Загальна схема ===
</div>
 "provider_id": provider.id,

!; Тип
<syntaxhighlight lang="python">
 },
 def get_allowed_providers(self, document_type: str, signer_type: str) -> list [str]:
 "signer": {
 result = await verifier.verify(
!; |-
| style="background:#ef9a9a;" | Червоний
| #ef9a9a
| Помилка або негативний результат.; |-
| created_at
| timestamp
| Дата.; | Стара заявка стає INVALIDATED.; # Які формати документів підписуємо: PDF, XML, DOCX, ZIP?; |-
| FileKeyAdapter
| Підписання файловим КЕП.; |-
| Невідомий КНЕДП
| Сертифікат не розпізнано.; | Так
|-
| XAdES
| XML-підпис.; | VERIFY_ERROR.; |-
| AC-20
| є собою прострочені заявки.; |-
| is_active
| boolean
| Активність.; платформа розраховує hash документа.; |-
| Signature File
| Файл підпису або контейнер.; | style="background:#eeeeee;" | Сірий
|-
| Готовий до підпису
| READY_TO_SIGN
| Документ перевірено.; |-
| external_document_id
| varchar
| ID документа в K2 ERP.; Verification Service перевіряє підпис.; | style="background:#ef9a9a;" | Червоний
|-
| Ручна перевірка
| MANUAL_REVIEW
| Потрібне втручання адміністратора.;== 11.; Статуси документа ==

* реалізувати завантаження документа;
* реалізувати версіонування;
* реалізувати hash;
* реалізувати дедублікацію.; Поле
!; * Офіційна сторінка SmartID ПриватБанку.; з цієї причини потрібен адаптерний шар.; | style="background:#ffcc80;" | Помаранчевий
|-
| Помилка підписання
| SIGN_ERROR
| Помилка під час підписання.; else:

До MVP входить:

 "file_id": stored_file.id,
[[Категорія:Дія]]
</div>
|-
| переважні аспекти
| користувач системи не передає файл ключа в систему.; * Офіційна сторінка Дія.Підпис.; Причина
=== 15.3. Signature Policy Engine ===
class SignaturePolicyEngine:
 "signer_identifier": result.signer_identifier,
== 34.; Відкриті питання ==
!; | Статус стає SIGNED.; * Документація ІІТ / користувач системи ЦСК-1.; K2 ERP створює документ.; |-
| signature_format
| varchar
| P7S, ASIC, CAdES, XAdES, PDF.; |-
| created_at
| Дата створення.; | document.pdf + document.pdf.p7s
|-
| Вкладений підпис
| ENVELOPED
| Підпис вбудований у документ або XML.; |-
| is_active
| boolean
| Активність.; | Статус стає MANUAL_REVIEW.; Поле
|-
| PDF
| Найчастіший формат договорів, актів, рахунків.; Критерій
|-
| Відокремлений підпис
| DETACHED
| Підпис зберігається окремо від документа.; |-
| mime_type
| Тип файлу.; |-
| signature_type
| varchar
| DETACHED, ENVELOPED, CONTAINER.; |-
| Створення сесії
| provider_session_id, статус.; Формат

 },
=== 23.8. signature_verifications ===
щоб підписати документ зручним для мене способом.; |-
| file_hash_sha256
| Hash документа.; | Hash і document version lock.; |-
| document_id
| uuid
| Документ.; | style="background:#c8e6c9;" | Високий
|-
| КНЕДП ДПС
| TAX_CSK
| Файловий ключ / ІІТ / локальне підписання
| Часто застосовується для податкових документів.; Єдиний API підписання
 v
!; |-
| raw_request
| jsonb
| Запит.; Створюється signature_request.; |-
| style="background:#f3e5f5;" | Фіолетовий
| #f3e5f5
| Ручна перевірка або нестандартний сценарій.; Очікуваний результат

 result = await adapter.parse_callback(payload)

9.; |-
| Dashboard API
| інформаційні дані для контролю.; |-
| signer_identifier
| varchar
| РНОКПП / ЄДРПОУ, якщо доступно.; Параметр

* реалізувати callback endpoint;
* реалізувати polling worker;
* реалізувати raw event storage;
* реалізувати idempotency.; |-
| expires_at
| timestamp
| Строк дії.; | signed.pdf або pdf.p7s.; Поле

<pre>
 db.commit()
!; |}

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

 payload=payload,

я хочу вибрати спосіб підписання: Дія, Приват24 SmartID, файловий КЕП або інший КНЕДП, 
 signer_type=command.signer.signer_type,
 return ["DIIA_SIGN", "PRIVAT24_SMARTID", "MANUAL_UPLOAD"]

{| class="wikitable"

 if document_type == "TAX_REPORT":
 return ["IIT_CSK", "TAX_CSK", "FILE_KEY"]

 if signer_type == "CLIENT":
=== 17.9.; Завантаження файлу підпису ===
 if signer_type == "EMPLOYEE":
 return ["FILE_KEY", "IIT_CSK", "PRIVAT24_SMARTID"]

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

== 16.; |-

source varchar - created_by - signature_request_id uuid Заявка.; характеристика

Критично істотно: бізнес-система не повинна напряму залежати від конкретного сервісного центру підпису.; | style="background:#ffcc80;" | Помаранчевий

Ручна перевірка MANUAL_REVIEW Потрібне втручання адміністратора.; характеристика
=== 15.1.; Загальний інтерфейс провайдера ===
<div style="border-left: 6px solid #c62828; background: #ffebee; padding: 12px 16px; margin: 16px 0;">
{
!; # Чи є собою офіційно затверджений API-доступ до SmartID?; |}

!; Код
12.; |-
| Provider Adapter
| Програмний адаптер конкретного провайдера.; |-
| Вибір провайдера
| Хто обрав, який провайдер.; |-
| DocumentChangedError
| Документ змінився після заявки.; 
 adapter = provider_router.get_adapter(provider.code)
</pre>
router = APIRouter()
async def signature_callback(provider_code: str, request: Request):
 payload={
!;
- settings jsonb } signature_processor.process_provider_result( idempotency_key=command.idempotency_key, істотно: різні провайдери мають різні способи роботи: API, callback, polling, QR/deep link, файловий ключ, локальний агент, DLL/SO-бібліотека, JavaScript-бібліотека або ручне завантаження p7s.; # Чи потрібно підписувати документи співробітниками?; | style="background:#f3e5f5;" | Фіолетовий
raise BusinessError("Provider is not allowed for this document")
}

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

"provider_session_id": response.get("session_id"),
"email": "client@example.com",

id uuid ID перевірки.; Дія
; конфігурація провайдера == try: "signer_name": result.signer_name, POST /api/v1/signature/documents/{document_id}/upload-signature }, Signature Storage + Verification Service pass verifier = verification_service_factory.get_verifier(signature_file.signature_format)
- Callback - Callback дублюється - AC-19 є собою помилки підписання.; Поле "signature_type": "DETACHED", data={ я хочу бачити dashboard підписання,

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

Signature Provider Інструкція, fallback, healthcheck агента.; | платформа показує статус UNAVAILABLE.; |- expires_at timestamp Строк дії.; Поле <syntaxhighlight lang="python"> }, pass
Створення документа Тип, номер, hash, реліз.; Тип async def verify_signature(signature_request_id: str, signature_file_id: str, db: "Session") -> None: raise ValueError(f"Unsupported signature provider: {provider_code}") Критично істотно: якщо документ змінено після створення заявки, попередню заявку потрібно перевести в INVALIDATED.; |- provider_id uuid Провайдер.; характеристика платформа повинна логувати:

15.2. Provider Router

"document_name": "Договір поставки №123", existing = signature_request_repository.get_by_idempotency_key( Python Unified Signature Service v return existing
; * єдиний API для всіх видів підписання; * підтримку декількох провайдерів підпису; * створення заявки на підписання; * підготовку документа до підпису; * розрахунок hash документа; * підписання PDF, XML, JSON, DOCX, ZIP або довільного файлу; * підтримку відокремленого підпису; * підтримку вкладеного підпису; * підтримку підписаного контейнера; * отримання результату підписання; * перевірку підпису; * перевірку цілісності документа; * перевірку підписанта; * перевірку сертифіката; * збереження файлів підпису; * збереження підписаних документів; * журналювання всіх подій; * dashboard контролю; * fallback-сценарії для ручного підписання.; Ключ
Підходить для ASIC / p7s container.; | style="background:#e3f2fd;" | відомості
Очікують підпису Активні заявки.; характеристика
 "document_id": document.id,
 == 24.; Приклад Python-логіки ==