Skip to content

Bankovní integrace

Fiktivní údaje

Všechny ukázky v této dokumentaci (příklady e-mailů, čísla účtů, jména, zůstatky) jsou smyšlené a slouží jen jako ilustrace formátu. Zadávejte vždy vlastní data.

Podporovaní poskytovatelé

Typ Poskytovatel Autentizace Interval kontroly Poznámky
API Fio Banka Fio API token (FIO_TOKEN) Každých 11 minut Jednodušší nastavení
API Erste / ČSAS OAuth2 (Authorization Code) Každých 11 minut Vyžaduje registraci na developer portálu
E-mail (IMAP) Bankovní e-mailová avíza IMAP e-mail + heslo / aplikační heslo Dle PAYMENT_CHECK_INTERVAL_MINUTES (výchozí 11 minut) Pro banky bez veřejného API; systémové parsery + vlastní regex providery
— Dummy Žádná — Pro testování

Aktivace poskytovatele

Aktivní poskytovatel se volí tlačítkem Aktivovat v záložce Integrace (POST /api/v1/admin/integrations/activate, tělo {"provider": "fio"}, prázdný řetězec = vypnout). Seznam povolených poskytovatelů se odvozuje z registru poskytovatelů (každý poskytovatel deklaruje svá konfigurační data), testovací dummy aktivovat nelze.

Při aktivaci se kontroluje úplnost nastavení (jen při aktivaci; již aktivní poskytovatel běží dál beze změny):

Poskytovatel Musí být nastaveno
fio FIO_URL, FIO_TOKEN
erste ERSTE_API_KEY, ERSTE_CLIENT_ID, ERSTE_CLIENT_SECRET (URL mají výchozí hodnotu)
email INCOMING_PAYMENT_NOTIFICATION_IMAP_HOST, INCOMING_PAYMENT_NOTIFICATION_EMAIL_ADDRESS

Při chybějících klíčích endpoint vrátí 400 s výčtem chybějících klíčů a nic nezapíše. Hodnoty se bere z prostředí i z databáze. Vypnutí ("") funguje vždy.

Nový poskytovatel se přidává novým souborem: RegisterProvider + RegisterProviderMeta (klíče, povinné klíče). Klíče poskytovatele musí být v registru konfigurace se skupinou payment a znovuinicializací platební služby — kontroluje to TestRegistryCompletenessPaymentProviders.

Fio Banka

Jednoduché napojení — stačí nastavit konfigurační klíče:

  1. V administraci nastavte:

    • PAYMENT_CHECK_SERVICE_ENABLED = true
    • PAYMENT_CHECK_SERVICE_NAME = fio
    • FIO_URL = https://fioapi.fio.cz/v1
    • FIO_TOKEN = váš API token z Fio internetového bankovnictví
  2. Cron úloha automaticky kontroluje transakce každých 11 minut.

Erste / Česká spořitelna (ČSAS)

Erste používá OAuth2 Authorization Code flow s krátkodobými přístupovými tokeny (5 min) a dlouhodobými obnovovacími tokeny (~180 dnů).

Předpoklady

  1. Zaregistrujte se na Erste Developer Portal
  2. Vytvořte aplikaci s přístupem k "Accounts" API
  3. Nastavte redirect URI:
    • Produkce: https://vaše-doména.cz/bank/erste/redirectFromBank
    • Vývoj: http://localhost:8090/bank/erste/redirectFromBank
  4. Poznamenejte si API Key, Client ID a Client Secret

Nastavení

  1. V administraci nastavte:

    • PAYMENT_CHECK_SERVICE_ENABLED = true
    • PAYMENT_CHECK_SERVICE_NAME = erste
    • ERSTE_API_KEY = váš API key (WEB-API-key)
    • ERSTE_CLIENT_ID = váš OAuth2 Client ID
    • ERSTE_CLIENT_SECRET = váš OAuth2 Client Secret
    • ERSTE_IDP_URL = URL IDP (výchozí je sandbox)
    • ERSTE_API_URL = URL Accounts API (výchozí je sandbox)
  2. Přejděte na záložku Integrace v administraci

  3. Klikněte na Připojit u karty Erste

  4. Budete přesměrováni na přihlašovací stránku banky. Přihlaste se a schvalte souhlas.

  5. Po přesměrování zpět vyberte bankovní účet pro párování transakcí.

  6. Hotovo! Cron úloha kontroluje transakce každých 11 minut.

Průběh OAuth2

Administrátor klikne "Připojit"
       |
       v
POST /api/v1/admin/bank/erste/connect
       |
       | Vrací auth_url
       v
Prohlížeč přesměruje na Erste IDP
(admin se přihlásí, schválí souhlas)
       |
       v
GET /bank/erste/redirectFromBank?code=...&state=...
       |
       | Ověří state (CSRF ochrana)
       | Vymění code za access_token + refresh_token
       | Uloží tokeny do kolekce oauth_tokens
       | Automaticky vybere účet, pokud existuje jen jeden
       v
Přesměrování na /admin?integration=erste&status=success

Správa tokenů

  • Access token vyprší každých 5 minut (300 s)
  • Refresh token vyprší za ~180 dnů (15 552 000 s)
  • Tokeny se obnovují na požádání před každým API voláním
  • Když refresh token vyprší → stav se změní na "expired" → administrátor musí znovu připojit
  • Všechny tokeny uloženy v kolekci oauth_tokens

Sandbox vs Produkce

Nastavení Sandbox Produkce
ERSTE_IDP_URL https://webapi.developers.erstegroup.com/api/csas/sandbox/v1/sandbox-idp https://webapi.developers.erstegroup.com/api/csas/production/v1/idp
ERSTE_API_URL https://webapi.developers.erstegroup.com/api/csas/public/sandbox/v3/accounts https://www.csas.cz/webapi/api/v3/accounts

Řešení problémů

Problém Řešení
"Token expired" Refresh token vypršel (~180 dnů). Klikněte "Znovu připojit" v záložce Integrace
"No account selected" Přejděte do záložky Integrace a vyberte bankovní účet
OAuth callback chyba Ověřte, že redirect URI přesně odpovídá tomu, co je registrováno na Erste Developer Portal

Jak párování funguje

Systém páruje platby podle:

  1. Variabilní symbol (VS) — unikátní číslo dobíjení nebo nákupu
  2. Specifický symbol (SS) — hash ID uživatele
  3. Částka — musí přesně odpovídat

U plateb za relaci musí částka odpovídat amount_due (kolik zbývá doplatit), ne celé ceně relace. Pokud uživatel část uhradil kreditem, QR kód i párování počítají jen se zbytkem.

Po spárování se automaticky:

  • Dokončí dobíjení → připíše se kredit
  • Aktivuje produkt → začne platit
  • Uhradí relace → do rozpisu se zapíše úhrada ve výši zbývající částky (kredit 300 + převod 518 → úhrada −518), amount_due klesne na 0 a final_price zůstane 818

Spárovaná platba: proč nemazat řádek platby

Řádek platby (bank_tx:*, manual, kredit) v rozpisu relace nikdy nemažte ručně v databázi. Smazání nespustí žádný přepočet, relace zůstane paid, ale při další úpravě rozpisu (např. ruční úpravě ceny) se přepočítá na nezaplacenou a uživateli vznikne fantomový dluh včetně upomínek. Opravy dělejte přes akce v administraci (ruční úprava ceny, označit jako zaplaceno).

Starší relace

Migrace 1791200000_session_amount_due automaticky přepočítá všechny existující relace z jejich rozpisu (final_price, discount, amount_due, paid) — ruční oprava databáze není potřeba. Již zaplacená relace se migrací nikdy nevrací na nezaplacenou.

Přehled bankovních transakcí

Všechny načtené bankovní transakce (Fio API, Erste API i e-mailová avíza) se ukládají do jednotné auditní tabulky bank transactions v administraci (Nastavení → Integrace → Bankovní transakce). Vidíte v ní, které platby se spárovaly s dobíjením / produktem / rezervací a které zůstaly nespárované.

Jak se transakce zapisují

  1. Orchestrátor (automatický cron, ruční „Kontrola plateb" nebo „Skenovat nyní") načte transakce z banky a všechny je zapíše jako unmatched
  2. Párovací engine (stejné sezení/dobíjení/produkty jako u API providerů) vyhledá spárovatelné transakce a upgraduje je na matched se záznamem, k čemu jsou připárované (matched_to_type + matched_to_id)
  3. Překrývající se načtení (Fio vrací celé dny, opakované běhy) nikdy nevytvoří duplicitní řádek — deduplikace probíhá vždy

Deduplikace a ochrana peněz

  • Dedup klíč = provider:external_id; když API external ID neobsahuje (některé banky), použije se fingerprint provider:datum:částka:vs:ss — stejná fyzická platba se tak vždy mapuje na stejný řádek
  • Unique index na dedup_key — duplikát se nikdy nevytvoří
  • Stav matched je finální: jednou spárovaná transakce se nikdy nevrátí na unmatched ani se nepřepáruje na jinou entitu. I když ji příští cron běh načte znovu jako „novou", stav zůstává matched

Tabulka v UI

Karta Bankovní transakce obsahuje sloupce Datum, Provider, Částka, VS, SS, Protistrana, Stav a „Připárováno k" s možností:

  • Filtrování — podle provideru (fio / erste / email), podle stavu (matched / unmatched) a textové vyhledávání (VS, SS, external ID, protistrana)
  • Řazení — kliknutím na hlavičku sloupce (Datum, Provider, Částka, VS, SS, Stav); výchozí řazení podle Datum (nejnovější nahoře)
  • Stránkování — 50 záznamů na stránku s navigací « 1 z N »
  • Detail — tlačítko Detail u řádku rozbalí podrobný pohled včetně raw_data (kompletní odpověď banky / rozparsované avízo)

API

  • GET /api/v1/admin/bank/transactions — seznam (admin only). Podporuje page, perPage (max 200), provider, status, search a sort (povolené sloupce: first_seen_at, matched_at, created, amount, status, provider, vs, ss, date, external_id; neplatné hodnoty se tiše ignorují). Seznam raw_data nevrací
  • GET /api/v1/admin/bank/transactions/{id} — detail (admin only), včetně raw_data

Kolekce bank_transactions má všechna pravidla přístupu prázdná (nil) — pod data se dostanete výhradně přes admin endpointy výše.

E-mailová (IMAP) platební avíza

Pro banky bez veřejného API (menší banky, stavební spořitelny) lze platby párovat z e-mailových avíz, která banka posílá na vaši schránku. Systém se připojí přes IMAP, načte e-maily, rozpozná banku podle odesílatele (a podle těla e-mailu), extrahuje variabilní symbol a částku a předá je stejnému párovacímu enginu jako API poskytovatelé.

Podporované e-mailové providery

Systémové parsery (registrované v kódu, v internal/bankemail/):

Banka Kód provideru Doména odesílatele Avízo
Fio banka fio fio.cz Přišel/odešel příjem na kontě
ČSOB csob csob.cz Moje info Avízo
ČSOB CEB csob_ceb csob.cz CEB Info: Zaúčtování platby
UniCredit Bank unicredit unicreditgroup.cz, unicredit.eu Informace o pohybu na účtu
Raiffeisenbank raiffeisenbank rb.cz Pohyb na účtě
MONETA Money Bank moneta moneta.cz Přišly/Odešly peníze
CREDITAS creditas creditas.cz Notifikace o změně na účtu
Air Bank airbank airbank.cz Zvýšení/Snížení zůstatku
Komerční banka kb kb.cz Oznámení o platbách
Česká spořitelna csas — Přišla/Odešla platba

Banky, které v tabulce nejsou, lze přidat bez změny kódu jako vlastní regex providery (BANK_EMAIL_REGEX_PROVIDERS) — viz sekci níže.

Živý přehled parserů v UI

Administrátor si může v Nastavení → Bankovní integrace → E-mail → Přehled parserů otevřít u každého parseru jeho routingová kritéria (domény odesílatele, podmínky na předmět a tělo, vč. regexů) a vzory polí pro extrakci plateb. Přehled se generuje přímo z kódu parserů (Info()/Criteria), takže se nikdy nemůže rozejít se skutečným routováním. Tabulka výše je jen rychlý přehled; autoritativní zdroj je UI.

Rozlišení csob vs csob_ceb (obě domény csob.cz): csob vyžaduje v předmětu „moje info" + „avízo" a v těle „parametry platby" a „čsob", csob_ceb vyžaduje v předmětu „ceb info" a v těle „zaúčtován" + „variabilní symbol" — podmínky se vzájemně vylučují, ke kolizi nedochází.

Kdy použít

Situace Doporučení
Banka má API (Fio, Erste/ČSAS) Použijte API poskytovatele
Banka API nemá, avíza chodí e-mailem Použijte e-mailový poskytovatel
Chcete ověřit, že avíza opravdu chodí Nastavte e-mail a použijte tlačítko Skenovat nyní

Předpoklady

  1. E-mailová schránka, která přijímá avíza a podporuje IMAP
  2. Přihlašovací údaje pro IMAP (heslo, případně aplikační heslo)
  3. Avíza mají ve zprávě variabilní symbol a částku

Přesný název složky

Název složky se nemusí shodovat s tím, co vidíte ve svém e-mailovém klientovi — klienti často zobrazují lokalizované nebo upravené názvy. Autoritativní názvy zná jen IMAP server. Použijte tlačítko Procházet složky v kartě bankovního e-mailu — vypíše přesné názvy přímo ze serveru a kliknutím na složku ji nastavíte.

Aktivace avíz u banky

Dřív než systém může avíza číst, musí je banka začít posílat na schránku, kterou systém sleduje. Postup se liší banka od banky a nastavuje se výhradně v internetovém bankovnictví dané banky (ne v Dverniku).

Česká spořitelna (csas)

Česká spořitelna zasílá avíza Přišla/Odešla platba na e-mail, který si vyberete v internetovém bankovnictví. Nastavení je dostupné pouze ve webové aplikaci — v mobilní aplikaci ho nejde aktivovat.

Postup:

  1. Přihlaste se do webové aplikace České spořitelny
  2. Vpravo nahoře klikněte na ikonu účtu → Nastavení
  3. V nabídce zvolte Informační zprávy
  4. V části Příchozí platby klikněte na Aktivovat
  5. Vyberte účet, ke kterému se má avízo týkat
  6. Vyberte e-mailovou adresu, na kterou se mají avíza zasílat, a doplňte ji
  7. Uložte změny

Avíza pak chodí z adresy ceskasporitelna@csas.cz s předmětem Přišla platba a v těle mají vypsaný variabilní symbol (VS), specifický symbol (SS) a částku — systém je rozpoznává automaticky (parser csas). Systém podporuje přeposlané avíza.

Nastavení

Konfigurační klíče (záložka Integrace → karta Bankovní e-mail):

Klíč Význam Výchozí
INCOMING_PAYMENT_NOTIFICATION_EMAIL_ADDRESS E-mailová adresa schránky —
INCOMING_PAYMENT_NOTIFICATION_EMAIL_PASSWORD Heslo / aplikační heslo k IMAPu —
INCOMING_PAYMENT_NOTIFICATION_IMAP_HOST IMAP server (např. imap.seznam.cz) —
INCOMING_PAYMENT_NOTIFICATION_IMAP_PORT IMAP port 993
INCOMING_PAYMENT_NOTIFICATION_IMAP_USE_TLS Použít TLS (Implicit TLS) true
INCOMING_PAYMENT_NOTIFICATION_IMAP_FOLDER Složka s avízy INBOX
INCOMING_PAYMENT_NOTIFICATION_REGEX_PROVIDERS Vlastní regex providery (JSON) —
PAYMENT_CHECK_INTERVAL_MINUTES Interval automatické kontroly — 0 = výchozí (každých 11 minut, */11), 1–59 = vlastní interval v minutách (*/N) 0

Postup:

  1. Vyplňte host, port, TLS, adresu a heslo
  2. Klikněte Otestovat připojení — ověří se TLS spojení i přihlášení
  3. Klikněte Procházet složky — vyberte přesnou složku ze serveru
  4. Klikněte Uložit
  5. Klikněte Skenovat nyní pro okamžitou kontrolu
  6. Automatická kontrola pak běží dle PAYMENT_CHECK_INTERVAL_MINUTES

Jak skenování funguje

  1. Systém se připojí přes IMAP a najde e-maily, které ještě nebyly zpracovány
  2. První sken projede celou schránku — najde se tak i stará avíza, která přišla ještě před nastavením integrace
  3. Další skeny jsou inkrementální — systém si pamatuje pozici (UID kurzor + UIDVALIDITY) poslední zpracované zprávy a žádá server jen o novější e-maily. Schránka s tisíci starých zpráv se nikdy neprohledává celá znovu
  4. Rozpozná banku podle odesílatele; když odesílatele nezná (např. přeposlané avízo), zkouší rozpoznat banku podle těla e-mailu
  5. Z každého avíza extrahuje variabilní symbol, specifický symbol, částku a datum platby
  6. Transakce se předá párovacímu enginu (stejný jako u API providerů)
  7. Zpracované e-maily se pamatují podle Message-ID (a fallback hashe) v kolekci bank_email_messages, takže se stejné avízo nikdy nezpracuje dvakrát. E-maily se ze schránky nemazají — deduplikace probíhá v systému
  8. Výsledek skenu se zobrazí v UI: kolik zpráv se naskenovalo, kolik bylo nových transakcí, kolik už bylo známých, kolik se nepodařilo rozpoznat (unmatched) a kolik selhalo při parsování (parse_failed)
  9. V kartě Bankovní e-mail a v hlavičce karty Bankovní transakce je navíc trvalý souhrn: počet uložených e-mailů, pozice UID kurzoru a čas poslední úspěšné kontroly (PAYMENT_LAST_RUN_TIMESTAMP; „dosud neproběhla“, pokud kontrola ještě nikdy neproběhla)

Jak funguje UID kurzor

Pozice skenu je uložena v app_config pod klíčem BANK_EMAIL_IMAP_CURSOR (systém používá jednu schránku, takže je klíč pevný). Kurzor se posouvá jen přes zprávy, které se podařilo uložit — pokud zpracování zprávy selže (dočasná chyba), příští sken se k ní vrátí. Když se změní UIDVALIDITY schránky (např. byla znovu vytvořena), kurzor se zahodí a schránka se projede znovu od začátku.

Reset kurzoru: pokud jste smazali záznam zprávy a chcete, aby se e-mail zpracoval znovu, použijte tlačítko Reset kurzoru (nebo endpoint POST /api/v1/admin/bank/email/reset-cursor). Kurzor se smaže a příští sken projede celou schránku — dosud zpracované zprávy přeskočí deduplikace, takže se znovu načtou jen smazané/chybějící e-maily.

Server bez podpory datového vyhledávání

Některé IMAP servery (např. „mtd”) odmítají rozsahové vyhledávání UID. Systém to automaticky pozná a přepne se na vyhledání všech UID s filtrací podle kurzoru přímo u klienta. Výsledek je stejný, jen může být o něco pomalejší u velkých schránek.

Stavy zpráv (interní)

Rozlišení mezi rozpoznaným avízem a „nepořádkem" se stále ukládá interně:

Stav Význam
processed E-mail se podařilo rozpoznat a rozparsovat na platební záznam (VS/SS/částka) a předal se párovacímu enginu. Neznamená, že se už spároval s platbou — výsledek párování se promítá do příslušné entity (dobíjení status=completed, atd.)
unmatched Žádný parser e-mail nepoznal (neznámý odesílatel ani tělo)
parse_failed Parser banku poznal, ale pole se nepodařilo rozparsovat — příčina je v error_message

Kde se to zobrazuje

Dřívější tabulka „Zprávy" v administraci byla v UI nahrazena jednotnou tabulkou Bankovní transakce (viz výše), která pokrývá všechny providery včetně e-mailových avíz. Kolekce bank_email_messages zůstává interně beze změny — je to deduplikace / kurzor pro IMAP skeny a ukládá stavy parse_failed/unmatched, které nejsou platební transakce. Skenování avíz (nastavení, reset kurzoru, parsery) je dál pod kartou Bankovní e-mail.

Řešení problémů

Problém Řešení
„IMAP dial … x509: certificate signed by unknown authority” Schránka má neplatný/neznámý certifikát — ověřte hostname a certifikát
„Authentication failed” Špatné heslo, nebo schránka vyžaduje aplikační heslo
„Mailbox … does not exist” Špatný název složky — použijte Procházet složky pro přesné názvy
Avízo se nespáruje (stav unmatched) Zkontrolujte v e-mailu VS/částku; případně doplňte regex providera
Banka se nepozná Přidejte vlastní regex providera do INCOMING_PAYMENT_NOTIFICATION_REGEX_PROVIDERS
Sken ukazuje „0 nových transakcí” Zpráva už byla zpracovaná dříve (deduplikace podle Message-ID) — zkontrolujte kartu Bankovní transakce; nebo avízo nesedí VS/částkou s žádnou čekající platbou
Smazaná zpráva se po skenu nevrátila Kurzor už je posunutý za ni — použijte Reset kurzoru a sken znovu

Testování celého toku v UI

Nejjednodušší ověření, že e-mailové avízo opravdu dobije kredit:

  1. Přihlaste se jako běžný uživatel → stránka Dobití → vyberte částku (např. 100 Kč) → vytvořte dobíjení
  2. Poznamenejte si Variabilní symbol a Specifický symbol, které systém pro dobíjení vygeneroval (zobrazí se v detailu dobíjení spolu s QR)
  3. Pošlete na schránku testovací e-mail — buď přes skutečný bankovní převod, nebo prostě odešlete e-mail s tělem avíza s těmito přesnými VS/SS a částkou na adresu, kterou systém sleduje přes IMAP
  4. Administrace → Integrace → Bankovní e-mail → Skenovat nyní
  5. Ověřte v odpovědi skenu, že se transakce načetla (VS/SS/částka sedí)
  6. Uživatel → Dobití → dobíjení je Dokončeno a kredit se navýšil

Pro rychlé otestování parseru bez IMAP je k dispozici endpoint POST /api/v1/admin/bank/parser/test (tělo: sender, subject, date, text) — vrátí, který parser se chytil a co z e-mailu rozparsoval.

Ukázkové e-maily (HTML/base64)

E-maily, které banky posílají jako HTML (např. Komerční banka — tělo je base64 ve windows-1250), nelze do testu parseru vložit jako prostý text. K tomu slouží uložené vzorky (internal/bankemail/samples/): skutečná struktura bankovního avíza se sanitizovanými hodnotami. V UI (Nastavení → Bankovní integrace → E-mail → Test parseru → Ukázkový e-mail) je vyberete a spustíte test — endpoint je dekóduje přesně stejnou cestou jako IMAP fetch (přenosové kódování + znaková sada + odstranění HTML). Vzorky zároveň slouží jako regresní test „banka změnila formát e-mailu" (TestSamplesParseThroughPipeline v internal/bankemail/).

Přehled všech parserů (routingová kritéria + vzory polí) vrací GET /api/v1/admin/bank/email/parsers — stejná data, která se zobrazují v administraci v „Přehled parserů".

Vlastní regex providery (BANK_EMAIL_REGEX_PROVIDERS)

Pokud banka nemá systémový parser (Go kód v internal/bankemail/), lze ji přidat bez změny kódu — definováním vlastního regex providera v JSON. Vlastní regex providery mají přednost před systémovými parsery: sken nejdřív projde je (v pořadí v poli) a teprve když žádný nechytí, zkusí systémové parsery.

Kdy použít

Situace Použít systémový parser? Použít regex providera?
Banka je v seznamu systémových parserů (csob, csob_ceb, fio, unicredit, raiffeisenbank, ...) ✔ — (regex má přednost, viz řádek níže)
Banka nemá systémový parser (mBank, AirBank, KB, lokální banka, ...) — ✔
Banka změnila formát avíza (systémový parser už nesedí) — ✔ — vlastní regex provider se sender_whitelist přebije systémový parser a opraví to bez čekání na zásah v kódu
Chcete vyzkoušet regex, než se napíše systémový parser — ✔ + „Parser test"
Chcete párovat akce (platby za využití hřiště/session) ✔ ✔ — vyžaduje VS i SS (VS = datum, SS = čas začátku akce) + částku
Chcete párovat dobíjení/produkty ✔ ✔ — vyžaduje VS + SS + částku

Struktura JSON

JSON je pole objektů Provider. Povinné je jen code; ostatní pole mají smysl jen v kombinaci s regex parserem (parser_type: "regex"):

Pole Typ Povinné Význam
code string ✔ Unikátní kód providera — píše se do bank_email_messages.provider_code. Malá písmena, bez mezer (např. mbank)
name string — Popisný název (jen pro přehled, např. „mBank")
parser_type string — Typ parseru. Podporovaná hodnota: regex (výchozí, když chybí). Jakákoli jiná hodnota = provider se přeskočí
enabled bool — true (výchozí chování) = aktivní; false = přeskočí se
sender_whitelist string — Přesná adresa/adresy odesílatele, ze kterých avíza chodí. Podporuje více adres oddělených mezerou, čárkou nebo středníkem — např. notification@csob.cz, avizo@csob.cz (jedna banka může posílat avíza z více adres). Prázdné = povoleno vše (i přeposlané). Regex provider routuje podle přesné adresy, ne podle domény
subject_pattern string — Regex pro předmět e-mailu. Prázdný = nekontroluje se
body_pattern string — Regex pro tělo e-mailu. Prázdný = nekontroluje se
field_patterns objekt — Mapování polí na regex extrakci (viz níže)
system bool — Jen pro systémové parsery — u vlastních se ignoruje
provider_ref string — Referenční identifikátor (jen pro přehled)

Všechny regex vzory jsou case-insensitive a odolné vůči diakritice (zkouší se originál i ASCII varianta bez diakritiky). Adresa odesílatele v sender_whitelist se porovnává přesně (case-insensitive), včetně podpory přeposlaných avíz.

Pole v field_patterns

Každý klíč = název pole, hodnota = regex s pojmenovanou skupinou (?P<value>...) (povinná). Nepovinné pole, jehož vzor nesedí, se prostě přeskočí — povinné je 5 klíčů, bez nichž Parse vrátí chybu: variable_symbol, amount, currency, posted_at, recipient_account.

Klíč Povinné Význam Poznámka k vzoru
variable_symbol ✔ Variabilní symbol (VS) Číslice; vedoucí nuly a nečíselné znaky se ořežou
specific_symbol ✔* Specifický symbol (SS) Kritické pro všechna párování plateb. Pro akce: VS = datum (YYYYMMDD) a SS = čas (HHMMSS) začátku akce z QR; pro dobíjení/produkty: SS = hash uživatele. Párování vždy srovnává VS i SS i částku — prázdné SS nikdy neodpovídá, takže bez něj se nespáruje žádná platba
amount ✔ Částka Číslo s desetinnou čárkou (případně mezerami); znaménko určí směr
currency ✔ Měna (CZK, EUR, ...)
posted_at ✔ Datum platby Formát d.m.YYYY [H:MM]; pokud chybí, použije se datum doručení e-mailu
recipient_account ✔ Účet, na který platba přišla Číslice/pomlčka/„/"
constant_symbol — Konstantní symbol (KS) Pro párování se nepoužívá
counterparty_account — Účet protistrany Rozparsuje se na číslo účtu + kód banky
counterparty_name — Název protistrany
message — Zpráva pro příjemce
bank_ref — Referenční číslo platby (externí ID transakce)
balance — Zůstatek po transakci
direction — Směr platby (příchozí/odchozí apod.) Pokud vzor obsahuje slovo „odchozí/výdej/odepsán/outgoing...", částka se převede na zápornou

* specific_symbol není v seznamu povinných klíčů pro Parse — ale pro párování plateb je praktická nutné ho definovat: akce (VS = datum, SS = čas začátku akce), dobíjení (VS+SS+částka) i produkty (VS+SS+částka) se párují pouze, když sedí VS i SS i částka.

Všech 10 systémových parserů má vzor specific_symbol vždy definovaný (viz přehled parserů v UI). U bank, jejichž avízo SS neuvádí, jde o „záložní" vzor, který na reálném e-mailu prostě nematchuje → SS zůstává prázdné a parser není rozbitý. To je záměr: bez SS se žádná platba nespáruje, takže při výpadku párování je první kontrola, jestli formát banky SS skutečně posílá.

recipient_account je povinný jako součást formátu avíza (Parse ho od konfigurovaného providera vyžaduje), ale jeho hodnota se nikdy neporovnává s účtem instalace — párování plateb pracuje čistě s VS + SS + částkou.

Příklad: ČSOB CEB jako vlastní regex provider

Avízo „CEB Info: Zaúčtování platby" (formát podle skutečného e-mailu, údaje jsou smyšlené):

Dne 6.8.2026 byla na účtu 987654321 zaúčtována transakce typu: Příchozí úhrada okamžitá.
Účet: 987654321, CZK, OBEC DOBROVICE
Částka: +250,00 CZK
Účet protistrany: 670100-1234567890/6210
Název protistrany: JAN NOVÁK
Variabilní symbol: 1234567890
Konstantní symbol: 1234
Specifický symbol: 1234567890
Zpráva příjemci: Platba test
Zůstatek na účtu po zaúčtování transakce: +125 000,00 CZK.

Odpovídající JSON do BANK_EMAIL_REGEX_PROVIDERS:

[{
  "code": "csob_ceb_regex",
  "name": "ČSOB CEB (regex)",
  "parser_type": "regex",
  "enabled": true,
  "sender_whitelist": "notification@csob.cz",
  "subject_pattern": "CEB\\s+Info\\s*:\\s*Za[úu]čtov[áa]n[íi]\\s+platby",
  "body_pattern": "Za[úu]čtov[áa]n[íi]\\s+transakce",
  "field_patterns": {
    "variable_symbol": "(?i)Variabilní\\s+symbol\\s*:\\s*(?P<value>[0-9]+)",
    "specific_symbol": "(?i)Specifický\\s+symbol\\s*:\\s*(?P<value>[0-9]+)",
    "constant_symbol": "(?i)Konstantní\\s+symbol\\s*:\\s*(?P<value>[0-9]+)",
    "amount": "(?i)Částka\\s*:\\s*(?P<value>[+\\-]?[0-9 ]+,[0-9]{2})",
    "currency": "(?i)Částka\\s*:\\s*[+\\-]?[0-9 ]+,[0-9]{2}\\s*(?P<value>[A-Z]{3})",
    "posted_at": "(?i)Dne\\s*(?P<value>\\d{1,2}\\.\\d{1,2}\\.\\d{4})",
    "recipient_account": "(?i)Účet\\s*:\\s*(?P<value>[0-9\\-]+)",
    "counterparty_account": "(?i)Účet\\s+protistrany\\s*:\\s*(?P<value>[0-9\\-]+\\/[0-9]{4})",
    "counterparty_name": "(?i)Název\\s+protistrany\\s*:\\s*(?P<value>[^\\r\\n]+)",
    "message": "(?i)Zpráva\\s+příjemci\\s*:\\s*(?P<value>[^\\r\\n]+)",
    "balance": "(?i)Zůstatek\\s+na\\s+účtu\\s+po\\s+zaúčtování\\s+transakce\\s*:\\s*(?P<value>[+\\-]?[0-9][0-9 .]*,[0-9]{2})",
    "direction": "(?i)transakce\\s+typu\\s*:\\s*(?P<value>Příchozí|Odchozí|Výdej|Výdaj)"
  }
}]

Poznámky k příkladu:

  • sender_whitelist: "notification@csob.cz" — regex provider routuje podle přesné adresy odesílatele (na rozdíl od systémových parserů, které kontrolují doménu). Uvádí se úplná adresa; prázdná hodnota povolí vše. Spoofing falší neprojde, protože adresa se porovnává celá
  • Více adres odesílatele: pokud banka posílá avíza z více adres, stačí je uvést do sender_whitelist oddělené mezerou, čárkou nebo středníkem — např. "sender_whitelist": "notification@csob.cz, avizo@csob.cz". Obě adresy jsou pak povolené (avíza z jakékoli jiné adresy se nezpracují)
  • Vzory s (?i) jsou case-insensitive; diakritika se toleruje (zkouší se i varianta bez diakritiky, takže Zaúčtování i Zauctovani chytí)
  • amount zachytí +0,01 i záporné částky; direction podle typu transakce určí, jestli jde o příchozí/odchozí platbu — odchozí dostane zápornou částku (jako systémový parser)
  • specific_symbol je definovaný → párují se akce (VS = datum, SS = čas začátku akce), dobíjení i produkty (VS+SS+částka). Bez SS by se nespárovala žádná platba
  • Ověřte si vzory v UI Parser test (tlačítko „Parser test" v kartě Bankovní e-mail) před uložením — vyplníte odesílatele, předmět a tělo a systém ukáže, co rozparsoval

Přidání nového e-mailového parseru banky (postup)

Každá banka s e-mailovými avízy má vlastní parser (např. csob, csob_ceb, fio, unicredit, raiffeisenbank). Parser se sám zaregistruje přes init() — stačí jeden nový soubor, žádné změny stávajícího kódu.

Postup (příklad pro novou banku):

  1. Vytvořte soubor internal/bankemail/banks_<banka>.go
  2. Definujte parser — struktura s metodami:
  3. Key() — unikátní kód provideru (např. kb), píše se do bank_email_messages.provider_code
  4. DefaultProvider() — odesílatel, subject/body vzor (jen pro UI přehled)
  5. Supports(m Message, p Provider) bool — rozpoznání e-mailu:
    • nejdřív senderMatchesDomain(m, "kb.cz") (chrání před spoofingem — csob.cz.evil.com neprojde)
    • pak kontrola subjectu a těla (bez diakritiky + s diakritikou)
  6. Parse(m Message, p Provider) (ParsedPayment, error) — extrakce polí:
    • VS → VariableSymbol, SS → SpecificSymbol, KS → ConstantSymbol (každé volitelné, required() jen pro kritická pole)
    • částka + měna, datum (parseDate), účty (splitAccount), zůstatek
  7. func init() { registerSystemParser(BankaParser{}) }
  8. Napište unit test internal/bankemail/<banka>_test.go s přesnou kopií reálného e-mailu (odesílatel, subject, tělo) + spoofing test
  9. Napište integrační test — vzor: TestIntegration_CsobCebEmailFullFlow v internal/app/bankemail_integration_test.go:
  10. seedTopupWithVSSS — pending dobíjení s VS/SS/částkou z e-mailu
  11. bankemail.Message s tělem avíza → ep.FetchTransactions
  12. a.TopupService.MatchBankTransactions(ctx, txs) → dobíjení dokončeno, kredit připsán
  13. Spusťte testy a build: make all (+ go test -tags integration ./internal/app/)

E2E testy s reálnými payloady bank API

Pro provider API (Fio REST / Erste PSD2) jsou uloženy přesné kopie reálných odpovědí v internal/payment/providers/testdata/ (fio_transactions.json, erste_accounts.json, erste_transactions.json), automaticky zabudované do integration buildů přes //go:build integration (fixtures_loader.go). Slouží jako pojistka proti tiché změně formátu API.

Vzory testů v internal/app/payment_providers_e2e_integration_test.go:

  • TestIntegration_E2E_FioRealtimePayload_Parse / TestIntegration_E2E_ErsteRealtimePayload_Parse — reálný payload projde provider parserem beze změny (VS/SS/částka, external ID, provider name, stránkování u Erste)
  • TestIntegration_E2E_FioRealtimePayload_SessionPaid / TestIntegration_E2E_ErsteRealtimePayload_SessionPaid — reálná struktura payloadu přes reálný provider → CheckPayments → session zaplacená + bank_transactions s matched stavem

Při nahrávání reálné odpovědi od banky zachovejte konvenci vzorových emailů (viz výše): čísla účtů/IBAN/BIC/ID a VS/SS posuňte o konstantu (např. +7), jména nahraďte fiktivními. Hodnoty VS/SS/částky v E2E testech se přepisují z reálné struktury na hodnoty seedované session — testuje se, že struktura payloadu projde celým řetězcem a věrně se naparsuje.

SS = specifický symbol

Pro párování dobíjení je kritický specifický symbol (SS) — systém ho generuje pro každé dobíjení z ID uživatele. Parser musí SS parsovat do pole SpecificSymbol (ne do ConstantSymbol), jinak se transakce nespáruje. Transaction.SS se bere pouze z SpecificSymbol — KS se jako fallback nepoužívá. Chybějící symboly se parsují jako prázdné, avízo bez nich se nerozbije.