Search by

beziworld / laravel-saldeo

kamcw

Laravel client for the SaldeoSMART API-XML: companies, contractors, documents, OCR, invoices and bank statements for accounting offices

Package info

github.com/Beziworld/laravel-saldeo

Homepage

Issues

pkg:composer/beziworld/laravel-saldeo

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

v1.0.0 2026-11-04 18:54 UTC

This package is auto-updated.

Last update: 2026-11-04 18:54:35 UTC


README

tests Latest Version License

SaldeoSMART API-XML client for Laravel, built for accounting offices and ERP integrations:

  • request signing (req_sig, MD5 over sorted, URL-encoded parameters plus the API token) and the gzip + base64 command encoding, verified against both examples of the official specification
  • companies: list, create (with the welcome e-mail option), link existing companies with COMPANY_PROGRAM_ID
  • contractors: list and merge (version 3.3 address structure)
  • documents: list by policy, month id lists with listbyid in chunks of 200, search, upload, OCR recognition and recognition results, back-references from the accounting program (document.sync), update, dimensions, deletion
  • API Engine: add and recognise a file without a company context
  • invoices (all kinds, ids by month, add), bank statements with operations, dictionaries (categories, registers, payment methods, dimensions, articles, descriptions)
  • multiple offices: named connections, runtime credentials with using(), per-company scopes
  • latest documented operation versions by default, overridable per operation; generic call() for everything else
  • saldeo:fetch-documents command dispatching DocumentFetched events for the scheduler

Polska wersja dokumentacji

Why this package

There is no SaldeoSMART package on Packagist. The existing open-source clients are a C# wrapper (2021) and a Python MCP server. This package gives Laravel applications a typed, tested client of the official API-XML: it handles signing, XML and compression details, maps the official XML structures to DTOs and error codes to exceptions, and supports several accounting offices and companies at once.

Installation

composer require beziworld/laravel-saldeo
php artisan vendor:publish --tag=saldeo-config   # optional
SALDEO_USERNAME=...              # SaldeoSMART login
SALDEO_API_TOKEN=...             # SaldeoSMART: account settings, API token
SALDEO_TEST=false                # true = https://saldeo-test.brainshare.pl (developer accounts)
SALDEO_COMPANY_PROGRAM_ID=       # default company for Saldeo::documents() and similar shortcuts
SALDEO_PRODUCER=                 # optional METAINF/PRODUCER sent with company.create

API access is available in selected SaldeoSMART plans (all plans for accounting offices). A dedicated integration profile is requested at api@saldeosmart.pl.

Several offices

// config/saldeo.php
'connections' => [
    'default' => ['username' => env('SALDEO_USERNAME'), 'api_token' => env('SALDEO_API_TOKEN')],
    'krakow' => ['username' => env('SALDEO_KRAKOW_USERNAME'), 'api_token' => env('SALDEO_KRAKOW_TOKEN')],
],
Saldeo::connection('krakow')->companies()->list();
Saldeo::using(['username' => $office->saldeo_login, 'api_token' => $office->saldeo_token])->company('ERP-17')->documents()->list();

Companies and contractors

use Beziworld\Saldeo\Data\{BankAccount, NewCompany, NewContractor};
use Beziworld\Saldeo\Facades\Saldeo;

$companies = Saldeo::companies()->list();               // list<Company>: id, programId, vatNumber, bankAccounts, ...

Saldeo::companies()->create([new NewCompany(
    programId: 'ERP-17', username: 'acme-admin', email: 'biuro@acme.pl', shortName: 'ACME',
    fullName: 'ACME Sp. z o.o.', vatNumber: '5260001246', city: 'Warszawa', postcode: '00-001', street: 'Prosta 1',
    bankAccounts: [new BankAccount('PL61109010140000071219812874')], sendEmail: true,
)]);

Saldeo::companies()->synchronize([5202 => 'ERP-17']);  // link an existing company (COMPANY_ID => COMPANY_PROGRAM_ID)

$company = Saldeo::company('ERP-17');
$company->contractors()->list();
$results = $company->contractors()->merge([
    new NewContractor('KOWAL', 'Jan Kowal', street: 'Długa', buildingNumber: '5', postcode: '30-001', city: 'Kraków', programId: 'K-1', vatNumber: '6750001923', supplier: true),
]);
$results[0]->status;   // CREATED, MERGED, CONFLICT, RECREATED or NOT_VALID; ->errors with paths

Documents and OCR

use Beziworld\Saldeo\Data\{DocumentSync, NewDocument};
use Beziworld\Saldeo\Enums\{DocumentPolicy, SplitMode};

$documents = Saldeo::company('ERP-17')->documents();

$list = $documents->list();                             // policy SALDEO: documents marked for sending
foreach ($list->documents as $document) {
    $document->number; $document->issueDate; $document->sum; $document->vatRegistries; $document->source;
    $list->contractorOf($document)?->vatNumber;
}

foreach ($documents->month(2025, 3) as $page) { /* getidlist + listbyid in chunks of 200 */ }
$documents->search(['NUMBER' => 'FV/101/2025', 'NIP' => '5270201490']);

$added = $documents->add([NewDocument::fromPath(storage_path('fv.pdf'), 2025, 3)]);
$recognition = $documents->recognize([$added[0]->id], SplitMode::NoSplit)[0];
$result = $documents->recognized([$recognition->ocrOriginId]);   // asynchronous: poll until isRecognized()

$documents->sync([new DocumentSync(saldeoId: (string) $document->id, accountDocumentNumber: 'FZ/1/2025', numberingType: 'ZAKUP', status: 'BOOKED')]);
$documents->delete([50000]);

// API Engine: recognition without a company context
Saldeo::addAndRecognize('5260001246', file_get_contents('fv.pdf'), SplitMode::NoSplit, 'COST');

Invoices, bank statements, dictionaries

$invoices = Saldeo::company('ERP-17')->invoices()->list();        // regular, corrective, advance invoices
$ids = Saldeo::company('ERP-17')->invoices()->ids(2025, 3);
Saldeo::company('ERP-17')->invoices()->byIds($ids);

foreach (Saldeo::company('ERP-17')->bankStatements()->list() as $statement) {
    foreach ($statement->operations as $operation) { $operation->value; $operation->isDebit(); }
}

$dictionaries = Saldeo::company('ERP-17')->dictionaries();
$dictionaries->mergeCategories([['CATEGORY_PROGRAM_ID' => '402', 'NAME' => 'Materiały biurowe']]);
$dictionaries->mergeRegisters([['REGISTER_PROGRAM_ID' => 'VZ', 'NAME' => 'Rejestr zakupów']]);
$dictionaries->mergePaymentMethods([['PAYMENT_METHOD_PROGRAM_ID' => 'P', 'NAME' => 'Przelew']]);
$dictionaries->mergeDimensions([['CODE' => 'MPK', 'NAME' => 'MPK', 'TYPE' => 'ENUM', 'DIMENSION_VALUES' => ['DIMENSION_VALUE' => [['CODE' => '01', 'DESCRIPTION' => 'Biuro']]]]]);

Any other operation (for example document.history, employee.list, financial_balance.merge):

$response = Saldeo::call('document.history', ['company_program_id' => 'ERP-17'], ['DOCUMENT_IDS' => ['DOCUMENT_ID' => [65]]], version: '3.2');
$response->body;   // decoded RESPONSE element

Operation versions default to the latest documented ones (for example document.list 3.1, contractor.merge 3.3); pin others with 'versions' => ['document.list' => '2.18'] in the configuration.

Scheduler

Schedule::command('saldeo:fetch-documents --all-companies')->hourly();
Event::listen(DocumentFetched::class, function (DocumentFetched $event) {
    // $event->companyProgramId, $event->document, $event->contractor
});

Errors

Exception When
ApiException STATUS=ERROR or HTTP error: errorCode() (for example 6101), operation(), httpStatus(), describe()
AuthenticationFailed 4302 user does not exist or is blocked, 4303 API not active
RateLimitExceeded 4307 more than 20 requests per minute, 4308 parallel requests, HTTP 429
InvalidConfiguration missing credentials, unknown connection or no company selected

Per-item problems of write operations (for example an invalid contractor) are not exceptions: check MergeResult::failed() and ->errors. Write operations are never retried; reads are retried once on connection errors and HTTP 5xx with a new req_id. SaldeoSMART allows 20 requests per minute per user and one request at a time.

Development

composer install
composer ci   # pint --test, phpstan (level 8), pest

Author

Created and maintained by Kamil Ćwiertnia, beziworld.eu.

Need an integration? Get in touch through beziworld.eu.

Other packages by the author

laravel-ksef · laravel-fakturownia · laravel-ifirma · laravel-wfirma · laravel-infakt

License

MIT. See LICENSE.md. This package is not affiliated with BrainShare IT or SaldeoSMART.