Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Fio Api Php Laravel Package

mhujer/fio-api-php

PHP client for the Fio Bank API to fetch account transactions, download bank statements, and send domestic and foreign payments. It wraps the bank's REST endpoints in simple typed objects, handles authentication tokens, and installs easily via Composer.

Deep Wiki
Context7

Getting Started

Install and authenticate

composer require mhujer/fio-api-php

Get a token from Fio internet banking (Settings → API, i.e. Nastavení → API). Generate a read-only token unless you genuinely need more, and note two things: the token is bound to a single Fio account, and there is no sandbox — every call hits production.

Three-line first call

use Mhujer\FioApi\FioApi;

$fio = new FioApi(config('services.fio.token'));
$list = $fio->getTransactions(
    new \DateTime('first day of last month'),
    new \DateTime('last day of last month')
);

foreach ($list->getTransactions() as $transaction) {
    // $transaction->getColumn1() ... getColumnN()
}

Use \DateTime, not \DateTimeImmutable — the library's type hints were written in the PHP 5/7 era and may reject immutable instances.

First use case: a nightly job that pulls yesterday's movements into a bank_transactions table so finance can reconcile incoming payments against invoices. That is 90% of what this package is ever used for.

Where to look first: the README (short, with a full method list), then FioApi.php (the whole API surface is one class), TransactionList (iterating results), and the Transaction value object (how columns are exposed). Because the API is column-oriented, the Transaction class is where you will spend your time.

Implementation Patterns

Incremental sync with a cursor. Don't re-download everything. Persist the newest transaction date (or the last transaction id from column 22) after each run and query from there with a small overlap window (e.g. start = cursor − 2 days), then dedupe on insert. Overlap protects you against late-settled or back-dated entries.

Idempotent upsert keyed on transaction id. Column 22 (ID pohybu) is the bank's stable identifier. Make it a unique index, prefix it with an account slug if you store multiple accounts in one table, and use upsert() — cron retries and manual re-runs should be free.

Map columns to a domain object once. Fio returns a fixed set of numbered columns whose meaning depends on the account type and currency (a CZK account and a EUR account do not put the same thing in column 5). Read the column headers off the response (getColumnHeaders() / the name attributes on <column_*> elements) and build a named-array mapper (amount, counter_account, variable_symbol, constant_symbol, specific_symbol, message, transaction_id) in one place. Everything downstream should speak your names, not getColumn14().

Wrap the client in a Laravel service. The package has no service provider and no config file, so bind it yourself:

// AppServiceProvider::register()
$this->app->singleton(FioApi::class, fn () => new FioApi(config('services.fio.token')));

Then put your own FioRepository / FioSync class in front of it. This gives you the seam you need for tests (bind a fake), a place to add retry/throttle logic, and freedom to swap in a fork later.

Drive it from a scheduled command, php artisan fio:sync --days=3, wrapped in withoutOverlapping() and queued if the transaction volume is high. Poll the "last transactions" endpoint for near-real-time notification (a payment landed) and the date-range endpoint for the nightly ledger sync.

Reconciliation tip: Fio's variable symbol is the field Czech counterparties use to carry an invoice number. Match variable_symbol against invoices.variable_symbol, fall back to a regex over the message field, and leave the rest in an "unmatched" queue for a human. Never auto-close an invoice purely on amount.

Gotchas and Tips

  • No sandbox and no test account. Your first integration call is against live data. Use a read-only token, a narrow date range, and an account whose data you don't mind seeing in logs.
  • The token lives in the URL path, not a header. Any exception trace, HTTP access log, or dd($url) leaks full account access. Redact before logging, and never let the client's exceptions reach a public error page.
  • Period requests are capped. You can't ask for an arbitrary multi-year range in one call — chunk your backfills into ~monthly windows and loop, with a small sleep between calls. Fio also throttles per token, so a tight loop over 30 windows is a good way to get temporarily blocked. Add a delay (a few hundred ms is usually enough) and treat 4xx/5xx as retryable-with-backoff rather than crashing the scheduled job.
  • Amounts are strings, and negatives are debits. Don't cast to float; store as DECIMAL(15,2) or handle with bcmath. Foreign-currency accounts give you both the original amount and the CZK equivalent in different columns — pick the one your accounting rules require and name it explicitly.
  • Timezone/DST. Dates from the bank are Prague-local. If your app stores UTC, convert on import, and remember the DST boundary in March/October can shift a payment into the "wrong" day if you truncate naively.
  • Empty responses. A day with no movement can yield an empty list (or an unexpected shape) — your mapper must handle zero transactions without assuming at least one node exists.
  • This package is read-only and unmaintained since 2017. There's no payment-order submission, and no PHP 8 compatibility guarantee. Check composer why-not php 8.x early; if constraints block you, either pin the runtime or fork and bump the composer.json plus any deprecated DateTime handling. The code is small enough that a fork is a realistic afternoon, not a rewrite.
  • Debug by bypassing the library. When parsing produces nonsense, curl the raw endpoint (/rest/periods/{token}/{from}/{to}/transactions.xml) and eyeball the <column_*> id/name pairs against what your mapper assumes. Nine times out of ten the bug is a column-meaning assumption, not the library.
  • Statement PDFs/CSVs are binary. If you use the statement-download side for archival, treat the response as bytes and write straight to storage — running it through an XML/JSON serializer will corrupt it.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums