Реализация электронной подписи документов на сайте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация электронной подписи документов на сайте
Сложная
~1-2 недели
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Реализация электронного подписания документов

Электронная подпись в веб-приложении решает юридически значимые задачи: подписание договоров, согласие с офертой, подтверждение транзакций. Различают несколько типов: простая ЭП (ПЭП — логин/пароль/SMS-код), усиленная неквалифицированная (НЭП — криптография, токены), квалифицированная (КЭП — только через удостоверяющий центр, имеет высшую юрсилу).

Простая ЭП: SMS-подписание договора

Наиболее распространённый подход для B2C: пользователь получает код по SMS, вводит его, PDF-договор фиксируется с отметкой времени, IP, fingerprint. Юридически значима как простая ЭП при наличии соглашения об использовании ЭП.

// Модель документа с аудитом подписания
class Document extends Model
{
    protected $casts = [
        'signing_metadata' => 'array',
        'signed_at'        => 'datetime',
    ];
}

// Сервис подписания
class DocumentSigningService
{
    public function initiateSignin(Document $document, User $user): void
    {
        // Генерация и отправка кода
        $code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);

        Cache::put("signing_code:{$document->id}:{$user->id}", bcrypt($code), now()->addMinutes(15));

        $user->notify(new DocumentSigningCodeNotification($code, $document));
    }

    public function confirmSigning(Document $document, User $user, string $code, Request $request): void
    {
        $cached = Cache::get("signing_code:{$document->id}:{$user->id}");

        if (!$cached || !Hash::check($code, $cached)) {
            throw new InvalidSigningCodeException('Неверный или истёкший код подтверждения');
        }

        // Создать хэш текущей версии документа
        $documentHash = hash('sha256', Storage::disk('s3')->get($document->path));

        $document->update([
            'status'           => 'signed',
            'signed_at'        => now(),
            'signed_by'        => $user->id,
            'document_hash'    => $documentHash,
            'signing_metadata' => [
                'ip'            => $request->ip(),
                'user_agent'    => $request->userAgent(),
                'fingerprint'   => $request->header('X-Client-Fingerprint'),
                'method'        => 'sms_code',
                'phone_last4'   => substr($user->phone, -4),
                'code_sent_at'  => Cache::get("signing_code_sent_at:{$document->id}:{$user->id}"),
                'signed_at_iso' => now()->toIso8601String(),
                'timezone'      => $request->header('X-Timezone', 'UTC'),
            ],
        ]);

        // Зафиксировать подпись в аудит-логе
        AuditLog::create([
            'action'      => 'document.signed',
            'user_id'     => $user->id,
            'document_id' => $document->id,
            'metadata'    => $document->signing_metadata,
        ]);

        Cache::forget("signing_code:{$document->id}:{$user->id}");

        // Отправить подписанную копию на email
        $user->notify(new DocumentSignedNotification($document));
    }
}

Визуальная подпись: canvas + API

import SignatureCanvas from 'react-signature-canvas';
import { useRef, useState } from 'react';

function DocumentSigner({ documentId }: { documentId: number }) {
  const sigCanvas = useRef<SignatureCanvas>(null);
  const [step, setStep] = useState<'draw' | 'confirm' | 'sms'>('draw');
  const [smsCode, setSmsCode] = useState('');

  const handleDrawComplete = async () => {
    if (sigCanvas.current?.isEmpty()) return;

    const signatureData = sigCanvas.current!.toDataURL('image/png');

    // Сохранить изображение подписи, перейти к SMS-подтверждению
    await api.post(`/documents/${documentId}/initiate`, { signature_image: signatureData });
    setStep('sms');
  };

  const handleSmsConfirm = async () => {
    await api.post(`/documents/${documentId}/confirm`, { code: smsCode });
    setStep('confirm');
  };

  return (
    <div>
      {step === 'draw' && (
        <>
          <p>Нарисуйте вашу подпись:</p>
          <div style={{ border: '1px solid #e5e7eb', borderRadius: 8 }}>
            <SignatureCanvas
              ref={sigCanvas}
              penColor="#1a1a1a"
              canvasProps={{ width: 500, height: 200, className: 'signature-canvas' }}
            />
          </div>
          <button onClick={() => sigCanvas.current?.clear()}>Очистить</button>
          <button onClick={handleDrawComplete}>Далее</button>
        </>
      )}

      {step === 'sms' && (
        <>
          <p>Введите код из SMS для подтверждения подписи:</p>
          <input
            type="text" inputMode="numeric"
            maxLength={6} value={smsCode}
            onChange={e => setSmsCode(e.target.value)}
          />
          <button onClick={handleSmsConfirm}>Подписать</button>
        </>
      )}

      {step === 'confirm' && (
        <p>Документ успешно подписан. Копия отправлена на ваш email.</p>
      )}
    </div>
  );
}

Встраивание подписи в PDF

use Smalot\PdfParser\Parser;
use setasign\Fpdi\Fpdi;

class SignedPdfService
{
    public function addSignatureStamp(string $pdfPath, array $signing): string
    {
        $pdf = new Fpdi();
        $pageCount = $pdf->setSourceFile($pdfPath);

        for ($i = 1; $i <= $pageCount; $i++) {
            $templateId = $pdf->importPage($i);
            $size = $pdf->getTemplateSize($templateId);

            $pdf->AddPage($size['orientation'], [$size['width'], $size['height']]);
            $pdf->useTemplate($templateId);

            // На последней странице добавить штамп подписи
            if ($i === $pageCount) {
                $pdf->SetFont('DejaVu', '', 8);
                $pdf->SetTextColor(0, 100, 0);

                $stamp = implode("\n", [
                    "Документ подписан электронной подписью",
                    "Подписант: {$signing['user_name']}",
                    "Email: {$signing['user_email']}",
                    "Дата: {$signing['signed_at']}",
                    "SHA-256: " . substr($signing['document_hash'], 0, 16) . '...',
                    "IP: {$signing['ip']}",
                ]);

                $pdf->MultiCell(0, 4, $stamp, 'D', 'L');
            }
        }

        $signedPath = str_replace('.pdf', '_signed.pdf', $pdfPath);
        $pdf->Output('F', $signedPath);

        return $signedPath;
    }
}

КЭП через СБИС / КриптоПро

Для юридически значимых договоров с максимальной силой (B2B, госзаказ) используется квалифицированная ЭП через удостоверяющий центр:

// Интеграция со СБИС API (подписание на стороне УЦ)
class SbisSigningService
{
    public function sign(string $documentBase64, int $signatoryId): string
    {
        $response = Http::withToken($this->getToken())
            ->post('https://online.sbis.ru/service/sbis.Signature.Sign', [
                'jsonrpc' => '2.0',
                'method'  => 'СБИС.ПодписатьДокумент',
                'params'  => [
                    'Документ'     => $documentBase64,
                    'Подписывающий' => $signatoryId,
                ],
            ]);

        return $response->json('result.Подпись');
    }
}

Хранение и проверка

// Верификация подписи — проверить, что документ не изменён после подписания
public function verify(Document $document): bool
{
    $currentHash = hash('sha256', Storage::disk('s3')->get($document->path));
    return hash_equals($document->document_hash, $currentHash);
}

Подписанные документы хранят с неизменяемыми правами (S3 Object Lock) для предотвращения фальсификации.

Срок реализации

Задача Срок
Простая ЭП (SMS-подтверждение + аудит) 3–4 дня
Canvas подпись + встраивание в PDF +2–3 дня
Интеграция СБИС или КриптоПро КЭП 5–7 дней
Полная система с хранилищем и верификацией 7–10 дней