Přeskočit na obsah

E-podpis — case-sensitive email match v cron syncu propadá podpisy

import { Aside } from ‘@astrojs/starlight/components’;

Cron pro periodickou synchronizaci stavu e-podpisu běží každých ~5 minut a sleduje smlouvy ve stavu SENT_TO_CLIENT. Logy konzistentně vrací:

[E-sign Cron] Checked 6/7, updated 0

I když klient v UI poskytovatele e-podpisu podepsal (envelope.status = completed, recipient status = signed, signedAt = 2026-06-03T07:07:24), aplikace ji nepřevedla do FULLY_SIGNED. Důsledek: status workflow se zasekl, navazující kroky (auto-vystavení zálohové faktury, notifikace, nárůst commission) se nikdy nespustily. Klienti volají s dotazem „kde je má zálohovka”.

Recipient match v cron handleru porovnával email jako case-sensitive string:

// BUG
const clientRecipient = recipients.find(r => r.email === contract.client.email);

Poskytovatel e-podpisu normalizuje email na lowercase (RFC 5321 local-part je technicky case-sensitive, ale v praxi 99 % MTA/služeb lowercaseuje). V CRM se ale email uložil tak, jak ho zadal obchodník — s velkými písmeny v local-partu (např. Klient.User@example.com). Po odeslání API vrátilo klient.user@example.com, === selhalo, clientRecipient byl undefined, clientSigned = false, žádná změna stavu.

Bug byl latentní — postihnul jen klienty, kteří mají v emailu velká písmena. U 1/7 smluv v daný den.

Normalizovat email na obou stranách porovnání. Stačí lower-case, ale lépe i .trim():

const clientEmail = (contract.client.email || "").trim().toLowerCase();
const ownerEmail = (companyEmail || "").trim().toLowerCase();
const clientRecipient = recipients.find(
r => (r.email || "").trim().toLowerCase() === clientEmail
);
const ownerRecipient = recipients.find(
r => (r.email || "").trim().toLowerCase() === ownerEmail
);

Fix musí být na všech místech, kde se recipient párujte:

  • periodický cron (GET /api/contracts/cron/e-sign-sync)
  • manuální resync endpoint (POST /api/contracts/[id]/e-sign-sync)
  • webhook handler (callback z poskytovatele)
  • onsite/offline branch (signed dokument uploadnut ručně)

Pokud byste fixli jen cron, manuální „Synchronizovat” tlačítko v UI by stále padalo.

Backfill (uvíznuté smlouvy)

Sekce “Backfill (uvíznuté smlouvy)”

Po nasazení fixu nestačí čekat na další cron tick — uvíznuté smlouvy už jsou ve stavu, který cron nepřechytí (nebo přechytí, ale match teď konečně sedí). Doporučený postup:

  1. Najít všechny smlouvy s digiSignId IS NOT NULL AND status IN ('SENT_TO_CLIENT', 'CLIENT_SIGNED')
  2. Pro každou stáhnout recipients z API a porovnat case-insensitive
  3. Pokud clientSigned && ownerSigned → nastavit status = FULLY_SIGNED, vyplnit clientSignedAt / ownerSignedAt z API
  4. Manuálně dohnat finanční navazující kroky — auto-trigger na status změnu vystaví ZF jen v API route, ne při raw DB updatu. Buď je třeba zavolat ručně service createAndSendDepositInvoice(), nebo přechod znovu protlačit přes oficiální endpoint.
  • Vždy lower-case + trim při srovnávání emailů proti externí službě. Nepředpokládej, že druhá strana zachovává case.
  • Validace na zápisu: při ukládání emailu klienta normalizovat (nebo aspoň přidat unique index na lower(email)). Tím se zbavíš duplicit typu User@example.com vs user@example.com.
  • Logy musí být akční: Checked N/N, updated 0 v živé produkci s aktivními pending smlouvami je podezřelé. Když by log obsahoval i match status per smlouvu (např. clientSigned=false, found 0/2 expected recipients), bug by se odhalil dřív.
  • Alert na latency: smlouva SENT_TO_CLIENT starší než X dní bez postupu by měla notifikovat obchodníka. Pokud klient skutečně neportál, je to lead pro follow-up; pokud podepsal, jde o tenhle bug.
  • Monitoring „očekávaný outcome”: pokud máš v jiné tabulce ZF, jejíž počet má korelovat s počtem FULLY_SIGNED smluv, sleduj poměr. Tento bug se projevil prudkým propadem ZF/FULLY_SIGNED ratio.
  • Webhook handler (z DigiSignu) má stejnou past — viz webhook-name-match-attaches-lead-to-wrong-contact (stejný princip: případově citlivá identifikace entity z externí služby).
  • DigiSign signing flow — viz digisign-envelope-signing-flow.
Přidal aiarchitekt.cz · 5. 6. 2026 2:00
Provozuje aiarchitekt.cz