fina / fina-sdk-laravel
Laravel SDK for integrating with FINA Web API (v8.0). Provides authentication, HTTP client, and typed endpoints.
Requires
- php: ^8.2
- illuminate/cache: ^12.0 || ^13.0
- illuminate/http: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
- illuminate/validation: ^12.0 || ^13.0
Requires (Dev)
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
README
A production-ready Laravel SDK for the FINA Web API v8.0. Handles authentication, token caching, typed DTOs, request validation, chunked reporting with deduplication, and structured error handling out of the box.
Table of Contents
- What's New in v8.0
- Features
- Requirements
- Installation
- Configuration
- Quick Start
- API Reference
- DTOs & Validation
- Error Handling
- Performance
- Testing & Quality
- Troubleshooting
- License
What's New in v8.0
This release adds 18 new endpoints and expands several existing ones:
| Area | New Capabilities |
|---|---|
| Customers | Agreements, sub-account fields, sub-accounts |
| Vendors | Sub-accounts |
| Products | Rest-after-date, rest summary, rest by store, provided service prices |
| Loyalty | Gift card info by code, bonus card rest by code |
| Reference | Transportation means, account value details |
| Documents | Save received service, bonus card, gift payment; get/save auto-service docs |
| Reporting | Auto services out journal (raw + typed + chunked), cafe order detailed report |
| DTOs | 17 new typed DTOs and payloads with full validation |
Existing endpoints are unchanged -- the upgrade is fully backwards-compatible.
Features
| Feature | Description |
|---|---|
| Auto-authentication | Fetches, caches (~35h TTL), and transparently refreshes tokens on 401 |
| Configurable HTTP | Timeout, retry count, and retry delay via .env |
| 8 API clients | Customers, Vendors, Products, Documents, Loyalty, Reference, Journals, Reporting |
| Chunked reporting | Splits large date ranges into windows, merges, deduplicates by id+version |
| Typed DTOs | readonly classes with fromArray() factories for all responses |
| Validated payloads | Request DTOs with Laravel validation rules, auto-validated before sending |
| Structured exceptions | Config, HTTP, Remote, and Validation exceptions with useful properties |
| PHPStan level 6 | Full static analysis coverage |
Requirements
- PHP >= 8.2
- Laravel >= 12.0
- A running FINA Web API v8.0 instance
Installation
Via Composer
composer require giorgigrdzelidze/laravel-fina-sdk
Laravel auto-discovers the service provider. No manual registration needed.
Local (path repository)
If using as a local path repository:
{
"repositories": [
{ "type": "path", "url": "packages/fina/fina-sdk-laravel" }
]
}
composer require giorgigrdzelidze/laravel-fina-sdk:@dev
Configuration
1. Add environment variables
FINA_BASE_URL=https://your-fina-host:5007 FINA_LOGIN=your_login FINA_PASSWORD=your_password
2. Publish config (optional)
php artisan vendor:publish --tag=fina-config
Environment variable reference
| Variable | Default | Description |
|---|---|---|
FINA_BASE_URL |
(required) | FINA instance URL (scheme + host + port) |
FINA_LOGIN |
(required) | API login |
FINA_PASSWORD |
(required) | API password |
FINA_TIMEOUT |
120 |
HTTP timeout (seconds) |
FINA_RETRY_TIMES |
2 |
Retries on transient failure |
FINA_RETRY_SLEEP_MS |
300 |
Delay between retries (ms) |
FINA_TOKEN_TTL |
126000 |
Token cache lifetime (seconds, ~35h) |
FINA_DOC_TYPES_TTL |
3600 |
Doc-types cache lifetime (seconds) |
Quick Start
Get the client
use Fina\Sdk\Laravel\Client\FinaClient; // Via container $fina = app('fina'); // Via injection public function __construct(private FinaClient $fina) {}
Fetch reference data
$stores = $fina->reference()->stores(); $users = $fina->reference()->users(); // UserDto[] $docTypes = $fina->reference()->docTypesCached(); // cached 1h $vehicles = $fina->reference()->transportationMeans(); // v8.0
Save a document
use Fina\Sdk\Laravel\Operation\Dto\ProductOutPayload; use Fina\Sdk\Laravel\Operation\Dto\ProductLine; $response = $fina->documents()->saveProductOut(new ProductOutPayload( id: 0, date: now(), numPrefix: '', num: 0, purpose: 'Sale via API', amount: 100.0, currency: 'GEL', rate: 1.0, store: 1, user: 1, staff: 1, project: 0, customer: 8, isVat: true, makeEntry: true, payType: 1, wType: 0, tType: 1, tPayer: 2, wCost: 0, foreign: false, products: [new ProductLine(id: 2, subId: 0, quantity: 3, price: 33.33)], )); $response->id; // saved document ID $response->ex; // null on success
Query a report
// Raw $raw = $fina->reporting()->entriesJournal($from, $to); // Typed $dto = $fina->reporting()->entriesJournalTyped($from, $to); $dto->journals; // EntriesJournalRowDto[] // Chunked (splits large ranges, deduplicates automatically) $dto = $fina->reporting()->entriesJournalChunkedTyped($from, $to, chunkDays: 7);
API Reference
All methods are accessed through the FinaClient instance ($fina).
Customers
Access via $fina->customers().
| Method | FINA Endpoint | Returns |
|---|---|---|
all() |
getCustomers |
array |
getByCode($code) |
getCustomersByCode/{code} |
array |
groups() |
getCustomerGroups |
array |
addresses() |
getCustomerAddresses |
array |
additionalFields() |
getCustomerAdditionalFields |
array |
agreements() |
getCustomerAgreements |
CustomerAgreementDto[] |
subAccountFields() |
getContragentSubAccountFields |
ContragentSubAccountFieldDto[] |
subAccounts() |
getCustomerSubAccounts |
ContragentSubAccountDto[] |
Last 3 methods are new in v8.0.
Vendors
Access via $fina->vendors().
| Method | FINA Endpoint | Returns |
|---|---|---|
all() |
getVendors |
array |
getByCode($code) |
getVendorsByCode/{code} |
array |
groups() |
getVendorGroups |
array |
addresses() |
getVendorAddresses |
array |
additionalFields() |
getVendorAdditionalFields |
array |
subAccounts() |
getVendorSubAccounts |
ContragentSubAccountDto[] |
subAccounts()is new in v8.0.
Products
Access via $fina->products().
| Method | FINA Endpoint | Returns |
|---|---|---|
all() |
getProducts |
array |
groups() |
getProductGroups |
array |
webGroups() |
getWebProductGroups |
array |
byIds($ids) |
getProductsArray (POST) |
array |
after($date) |
getProductsAfter/{date} |
array |
prices() |
getProductPrices |
array |
pricesAfter($date) |
getProductPricesAfter/{date} |
array |
units() |
getProductUnits |
array |
characteristics() |
getCharacteristics |
array |
imagesByProductIds($ids) |
getProductsImageArray (POST) |
array |
barcodesByProductIds($ids) |
getProductsBarcodeArray (POST) |
array |
restByProductIds($ids) |
getProductsRestArray (POST) |
array |
restAfter($date) |
getProductsRestAfter/{date} |
array |
restSummary($prods, $stores, $date) |
getProductsRestSummary (POST) |
array |
restByStoreAfter($store, $date) |
getProductsRestByStoreAfter/{store}/{date} |
array |
providedServicePrices() |
getProvidedServicePrices |
array |
Last 4 methods are new in v8.0.
Reference Data
Access via $fina->reference().
Raw endpoints:
| Method | FINA Endpoint |
|---|---|
stores() |
getStores |
projects() |
getProjects |
terminals() |
getTerminals |
cashes() |
getCashes |
creditBanks() |
getCreditBanks |
priceTypes() |
getPriceTypes |
customers() |
getCustomers (returns contragents[]) |
vendors() |
getVendors (returns contragents[]) |
customerGroups() |
getCustomerGroups |
vendorGroups() |
getVendorGroups |
productGroups() |
getProductGroups |
webProductGroups() |
getWebProductGroups |
providedServiceGroups() |
getProvidedServiceGroups |
receivedServiceGroups() |
getReceivedServiceGroups |
inventoryGroups() |
getInventoryGroups |
Typed endpoints:
| Method | Returns |
|---|---|
users() |
UserDto[] |
userPermissions($id) |
UserPermissionsDto |
bankAccounts() |
BankAccountDto[] |
staffGroups() |
StaffGroupDto[] |
staffs() |
StaffDto[] |
giftCards() |
GiftCardDto[] |
discountTypes() |
DiscountTypeDto[] |
units() |
UnitDto[] |
docTypes() |
DocTypeDto[] |
docTypesCached() |
DocTypeDto[] (cached) |
supportedDocTypes() |
DocTypeDto[] (filtered) |
findDocType($id) |
?DocTypeDto |
transportationMeans() |
TransportationMeanDto[] |
accountValueDetails($account, $date, $currency?) |
AccountValueDetailDto[] (POST) |
Last 2 methods are new in v8.0.
Loyalty
Access via $fina->loyalty().
| Method | FINA Endpoint | Returns |
|---|---|---|
bonusCoeff() |
getBonusCoeff |
BonusCoeffResponse |
cardsByHolder($code) |
getLoyaltyCardsByHolder/{code} |
array |
saveBonusOperation($payload) |
saveDocBonusOperation |
BonusOperationResponse |
giftCardInfoByCode($code) |
getGiftCardInfoByCode/{code} |
GiftCardInfoDto |
bonusCardRestByCode($code) |
getBonusCardRestByCode/{code} |
array |
Last 2 methods are new in v8.0.
Documents
Access via $fina->documents().
Save methods -- all return SaveDocResponse with ->id and ->ex:
| Method | FINA Endpoint | Payload DTO |
|---|---|---|
saveCustomerOrder($p) |
saveDocCustomerOrder |
CustomerOrderPayload |
saveProductOut($p) |
saveDocProductOut |
ProductOutPayload |
saveProductIn($p) |
saveDocProductIn |
ProductInPayload |
saveProductMove($p) |
saveDocProductMove |
ProductMovePayload |
saveProductCancel($p) |
saveDocProductCancel |
ProductCancelPayload |
saveProvidedService($p) |
saveDocProvidedService |
ProvidedServicePayload |
saveCustomerReturn($p) |
saveDocCustomerReturn |
CustomerReturnPayload |
saveCafeOrder($p) |
saveDocCafeOrder |
CafeOrderPayload |
saveCustomerMoneyIn($p) |
saveDocCustomerMoneyIn |
CustomerMoneyInPayload |
saveCustomerAdvanceIn($p) |
saveDocCustomerAdvanceIn |
(raw array) |
saveCustomerMoneyReturn($p) |
saveDocCustomerMoneyReturn |
CustomerMoneyReturnPayload |
saveBonusPayment($p) |
saveDocBonusPayment |
BonusPaymentPayload |
saveCustomerMoneyOut($p) |
saveDocCustomerMoneyOut |
CustomerMoneyOutPayload |
saveVendorMoneyIn($p) |
saveDocVendorMoneyIn |
VendorMoneyInPayload |
saveVendorMoneyOut($p) |
saveDocVendorMoneyOut |
VendorMoneyOutPayload |
saveVendorMoneyReturn($p) |
saveDocVendorMoneyReturn |
VendorMoneyReturnPayload |
saveProduction($p) |
saveDocProduction |
SaveProductionPayload / ProductionPayload |
saveReceivedService($p) |
saveDocReceivedService |
ReceivedServicePayload |
saveBonusCard($p) |
saveDocBonusCard |
BonusCardPayload |
saveGiftPayment($p) |
saveDocGiftPayment |
GiftPaymentPayload |
Last 3 save methods are new in v8.0.
Get methods -- return raw arrays or typed DTOs:
| Method | FINA Endpoint | Returns |
|---|---|---|
getCustomerOrder($id) |
getDocCustomerOrder/{id} |
array |
getProductOut($id) |
getDocProductOut/{id} |
array |
getProductMove($id) |
getDocProductMove/{id} |
array |
getReceivedService($id) |
getDocReceivedService/{id} |
array |
getCustomerReturn($id) |
getDocCustomerReturn/{id} |
array |
getProduction($id) |
getDocProduction/{id} |
array |
getProductionTyped($id) |
getDocProduction/{id} |
ProductionDocDto |
getAutoService($id) |
getDocAutoService/{id} |
array |
getAutoServiceTyped($id) |
getDocAutoService/{id} |
AutoServiceDocDto |
Last 2 methods are new in v8.0.
Generic methods for any endpoint:
// Any save endpoint $fina->documents()->save('saveDocCustomerOrder', $payload); // Any get endpoint $fina->documents()->getDoc('getDocProductOut', $id);
Reporting
The Reporting API provides three access patterns for every endpoint:
| Pattern | Suffix | Description |
|---|---|---|
| Raw | (none) | Returns the decoded JSON array as-is |
| Typed | Typed |
Maps response into typed DTO objects |
| Chunked + Typed | ChunkedTyped |
Splits large ranges, merges, deduplicates, returns typed DTOs |
Access via $fina->reporting().
Journals:
| Journal | Raw | Typed | Chunked Typed | Response DTO |
|---|---|---|---|---|
| Entries | entriesJournal() |
entriesJournalTyped() |
entriesJournalChunkedTyped() |
EntriesJournalResponseDto |
| Customers Orders | customersOrderJournal() |
customersOrderJournalTyped() |
customersOrderJournalChunkedTyped() |
CustomersOrderJournalResponseDto |
| Customers Returns | customersReturnJournal() |
customersReturnJournalTyped() |
customersReturnJournalChunkedTyped() |
CustomersReturnJournalResponseDto |
| Customers Money | customersMoneyJournal() |
customersMoneyJournalTyped() |
customersMoneyJournalChunkedTyped() |
MoneyJournalResponseDto |
| Vendors Money | vendorsMoneyJournal() |
vendorsMoneyJournalTyped() |
vendorsMoneyJournalChunkedTyped() |
MoneyJournalResponseDto |
| Productions | productionsJournal() |
productionsJournalTyped() |
productionsJournalChunkedTyped() |
ProductionsJournalResponseDto |
| Discount Cards | discountCardsJournal() |
discountCardsJournalTyped() |
discountCardsJournalChunkedTyped() |
DiscountCardsJournalResponseDto |
| Provided Services | providedServicesJournal() |
providedServicesJournalTyped() |
providedServicesJournalChunkedTyped() |
ProvidedServicesJournalResponseDto |
| Received Services | receivedServicesJournal() |
receivedServicesJournalTyped() |
receivedServicesJournalChunkedTyped() |
ReceivedServicesJournalResponseDto |
| Realizes | realizesJournal() |
-- | -- | -- |
| Auto Services Out | autoServicesOutJournal() |
autoServicesOutJournalTyped() |
autoServicesOutJournalChunkedTyped() |
AutoServicesOutJournalResponseDto |
Auto Services Out is new in v8.0.
Reports:
| Report | Raw | Typed | Response DTO |
|---|---|---|---|
| Customers Cycle | customersCycleReport() |
customersCycleReportTyped() |
CycleReportResponseDto |
| Vendors Cycle | vendorsCycleReport() |
vendorsCycleReportTyped() |
CycleReportResponseDto |
| Products Last-In | productsLastInReport() |
productsLastInReportTyped() |
ProductsLastInReportResponseDto |
| Products In/Return | productsInReturnReport() |
productsInReturnReportTyped() |
ProductsInReturnReportResponseDto |
| Cafe Order Detailed | cafeOrderDetailedReport() |
cafeOrderDetailedReportTyped() |
CafeOrderDetailedReportResponseDto |
Cafe Order Detailed is new in v8.0.
Low-level Journals API -- access via $fina->journals() for simple date-range queries:
$fina->journals()->entries($from, $to); $fina->journals()->customersOrders($from, $to); $fina->journals()->autoServicesOut($from, $to); // v8.0
Generic range call:
$fina->reporting()->getRange('getEntriesJournal', $from, $to); $fina->reporting()->getRangeChunked( method: 'getEntriesJournal', collectionKey: 'journals', from: $from, to: $to, chunkDays: 7, dedupeKeyFn: fn (array $item) => $item['id'] . ':' . $item['version'], );
DTOs & Validation
Contracts
Request DTOs implement two interfaces:
ArrayPayload -- toArray() for serialisation
|
ValidatesPayload -- adds rules(), messages(), attributes()
When you pass a ValidatesPayload to any save* method, it is validated automatically before the HTTP request. On failure, a FinaValidationException is thrown -- no request is made.
Example: save with validation
use Fina\Sdk\Laravel\Operation\Dto\BonusCardPayload; use Carbon\CarbonImmutable; $payload = new BonusCardPayload( id: 0, date: CarbonImmutable::now(), numPrefix: '', num: 0, purpose: 'New bonus card', customer: 31, store: 1, user: 1, cardCode: '231', personCode: '0100010101', personName: 'John Doe', personAddress: '123 Main St', personTel: '+995597222222', status: true, ); $response = $fina->documents()->saveBonusCard($payload);
Example: manual validation
use Fina\Sdk\Laravel\Support\PayloadValidator; use Fina\Sdk\Laravel\Exceptions\FinaValidationException; try { PayloadValidator::validate($payload); } catch (FinaValidationException $e) { // $e->errors -- ['field_name' => ['Error message', ...]] }
Response DTOs
All typed response DTOs use readonly classes with fromArray() factories. Reporting row DTOs also preserve the full raw array for forward-compatibility:
$dto = $fina->reporting()->autoServicesOutJournalTyped($from, $to); foreach ($dto->journals as $row) { $row->id; // typed property $row->amount; // typed property $row->raw; // full original array (access future fields) $row->dedupeKey(); // stable key for chunk merging }
Complete DTO list
Response DTOs (Operation):
| DTO | Purpose |
|---|---|
SaveDocResponse |
Generic save response (id, ex) |
BonusOperationResponse |
Bonus operation result (res, ex) |
BonusCoeffResponse |
Bonus coefficient (coeff, ex) |
UserDto |
User reference data |
UserPermissionsDto |
User permissions and defaults |
DocTypeDto |
Document type reference |
BankAccountDto |
Bank account reference |
StaffDto |
Staff member with additional fields |
StaffGroupDto |
Staff group hierarchy |
StaffAdditionalFieldDto |
Staff field key-value |
DiscountTypeDto |
Discount type with percent |
UnitDto |
Measurement unit |
GiftCardDto |
Gift card (includes restAmount in v8.0) |
GiftCardInfoDto |
Detailed gift card info by code (v8.0) |
CustomerAgreementDto |
Customer price agreement (v8.0) |
ContragentSubAccountFieldDto |
Sub-account field definition (v8.0) |
ContragentSubAccountDto |
Contragent sub-account data (v8.0) |
TransportationMeanDto |
Vehicle/transportation mean (v8.0) |
AccountValueDetailDto |
Account debit/credit detail (v8.0) |
AutoServiceDocDto |
Full auto-service document (v8.0) |
ProductionDocDto |
Production document with materials |
Payload DTOs (Operation):
| DTO | Validates | Purpose |
|---|---|---|
CustomerOrderPayload |
-- | Customer order |
ProductOutPayload |
Yes | Product out (sale/shipment) |
ProductInPayload |
Yes | Product in (purchase) |
ProductMovePayload |
Yes | Inter-store transfer |
ProductCancelPayload |
Yes | Product cancellation |
ProvidedServicePayload |
Yes | Provided service |
CustomerReturnPayload |
Yes | Customer return |
CafeOrderPayload |
Yes | Cafe order |
CustomerMoneyInPayload |
-- | Customer money in |
CustomerMoneyReturnPayload |
Yes | Customer money return |
BonusPaymentPayload |
Yes | Bonus payment |
BonusOperationPayload |
Yes | Bonus accumulate/spend |
CustomerMoneyOutPayload |
Yes | Customer money out |
VendorMoneyInPayload |
Yes | Vendor money in |
VendorMoneyOutPayload |
Yes | Vendor money out |
VendorMoneyReturnPayload |
Yes | Vendor money return |
ProductionPayload |
Yes | Production (legacy format) |
ReceivedServicePayload |
Yes | Received service (v8.0) |
BonusCardPayload |
Yes | Bonus card issuance (v8.0) |
GiftPaymentPayload |
Yes | Gift card payment (v8.0) |
SaveProductionPayload |
Yes | Production with product tree (v8.0) |
Line item DTOs:
| DTO | Used in |
|---|---|
ProductLine |
Product out, in, move, cancel, return |
ServiceLine |
Provided/received service |
CafeOrderProductLine |
Cafe order |
CustomerOrderProduct |
Customer order |
AddField |
Additional custom fields |
ProductionChildLine |
Production child product (v8.0) |
ProductionProductLine |
Production product with children (v8.0) |
ProductionMaterialLineDto |
Production material |
ProductionConsumedLineDto |
Consumed material |
ProductionExpenseLineDto |
Production expense |
Error Handling
All exceptions extend FinaException (a RuntimeException):
FinaException
├── FinaConfigException -- missing base_url, login, or password
├── FinaHttpException -- HTTP 4xx/5xx ($status, $body)
├── FinaRemoteException -- FINA returned non-null 'ex' ($ex)
└── FinaValidationException -- payload failed validation ($errors)
Usage
use Fina\Sdk\Laravel\Exceptions\FinaException; use Fina\Sdk\Laravel\Exceptions\FinaHttpException; use Fina\Sdk\Laravel\Exceptions\FinaRemoteException; use Fina\Sdk\Laravel\Exceptions\FinaValidationException; try { $fina->documents()->saveProductOut($payload); } catch (FinaValidationException $e) { // No HTTP request was made return response()->json(['errors' => $e->errors], 422); } catch (FinaHttpException $e) { Log::error("FINA HTTP {$e->status}", ['body' => $e->body]); } catch (FinaRemoteException $e) { Log::error('FINA remote error', ['ex' => $e->ex]); } catch (FinaException $e) { Log::error("FINA: {$e->getMessage()}"); }
401 auto-refresh
When a request gets a 401, the SDK automatically:
- Clears the cached token
- Fetches a fresh token
- Retries the request once
If the retry also fails, a FinaHttpException is thrown.
Performance
- Use chunked reporting for ranges > 30 days. FINA may time out on large ranges. Chunked methods handle this transparently.
- Recommended chunk sizes: 7 days for entries (high volume), 14 days for money/order journals.
- Deduplication uses
id+versionwhen available, falls back toid, then SHA-1 hash. - Cache doc types with
docTypesCached()to avoid repeated calls for rarely-changing data. - Token caching is ~35h by default. Adjust
FINA_TOKEN_TTLfor your instance.
Testing & Quality
All tests use Http::fake() -- no real HTTP, no database, no external services.
# Run tests composer test # Static analysis (PHPStan level 6) composer phpstan -- --memory-limit=512M # Code style (Laravel Pint) composer pint -- --test # All three composer ci
Test coverage
| Suite | Tests | Assertions | Coverage |
|---|---|---|---|
ServiceProviderTest |
4 | Singleton, alias, config, accessors | |
FinaClientHttpTest |
5 | Token cache, reuse, 401 refresh, HTTP errors | |
ReportingUrlTest |
3 | Date format, URL structure | |
ReportingChunkingTest |
4 | Chunk merge, deduplication, typed output | |
DtoValidationTest |
5 | Valid/invalid payloads, nested validation | |
DtoMappingTest |
14 | DTO mapping, empty data, roundtrip | |
NewEndpointsTest |
16 | All v8.0 endpoints (HTTP mocked) | |
NewDtoMappingTest |
12 | All v8.0 DTO mapping + edge cases | |
NewDtoValidationTest |
8 | v8.0 payload validation (valid + invalid) | |
| Total | 75 | 207 |
CI
GitHub Actions on every push/PR:
- Matrix: PHP 8.2, 8.3, 8.4
- Steps: tests -> PHPStan -> Pint
- No external services required
Troubleshooting
401 loops
// Verify credentials, then clear token cache: app('fina')->auth()->forgetToken();
Base URL format
# Correct FINA_BASE_URL=https://your-fina-host:5007 # Wrong (no scheme) FINA_BASE_URL=your-fina-host:5007 # Wrong (includes path) FINA_BASE_URL=https://your-fina-host:5007/api
Reporting timeouts
// Switch to chunked: $fina->reporting()->entriesJournalChunkedTyped($from, $to, chunkDays: 7); // Or increase timeout: // FINA_TIMEOUT=300
Config exception on boot
Ensure .env contains FINA_BASE_URL, FINA_LOGIN, and FINA_PASSWORD. The SDK validates these when the client is first resolved.
License
Contributing
Contributions welcome. See CONTRIBUTING.md for guidelines.
For security vulnerabilities, see SECURITY.md.