eFiskalizacija.cloud

PHP SDK

Zvanični PHP SDK za integraciju sa eFiskalizacija.cloud API-jem. Podržava PHP 8.1+ sa automatskom HMAC autentifikacijom, retry logikom i tipiziranim odgovorima.

Instalacija

composer require infogrambeg/efiskalizacija-php-sdk

Brzi početak

Kreirajte klijent i pošaljite prvi račun sa samo nekoliko linija koda:

<?php
declare(strict_types=1);

use Efiskalizacija\EfiskalizacijaClient;
use Efiskalizacija\DTO\Invoice;
use Efiskalizacija\DTO\InvoiceItem;
use Efiskalizacija\Enum\PaymentType;

// Kreiranje klijenta (produkcija)
$client = EfiskalizacijaClient::create(
    apiKey: 'efisk_1_abc123...',
    apiSecret: 'vas-64-karakter-secret'
);

// Ili za sandbox testiranje
$client = EfiskalizacijaClient::sandbox(
    apiKey: 'efisk_1_abc123...',
    apiSecret: 'vas-64-karakter-secret'
);

// Kreiranje računa
$invoice = Invoice::create()
    ->addItem(new InvoiceItem(
        naziv: 'Laptop HP ProBook',
        kolicina: 1,
        jedinicnaCena: 85000.00,
        pdvStopa: 20
    ))
    ->addItem(new InvoiceItem(
        naziv: 'Miš bežični Logitech',
        kolicina: 2,
        jedinicnaCena: 4500.00,
        pdvStopa: 20
    ))
    ->setPaymentType(PaymentType::Kartica);

// Fiskalizacija
$result = $client->fiskalizacija()->fiskalizuj($invoice);

echo "PFR broj: " . $result->pfrBroj . "\n";
echo "QR kod: " . $result->qrCode . "\n";
echo "Račun ID: " . $result->racunId . "\n";

API metode

Sve metode su dostupne preko klijent instance:

MetodaHTTPOpis
$client->fiskalizacija()->fiskalizuj($invoice) POST Fiskalizacija računa, vraća FiskalizacijaResult
$client->status()->fetch() GET Status tenanta i statistike, vraća TenantStatus
$client->invoices()->list($limit, $offset) GET Lista računa, vraća InvoiceListResult
$client->pdf()->download($pfrBroj) GET Preuzimanje PDF-a (binarni sadržaj)
$client->pdf()->downloadToFile($pfrBroj, $path) GET Čuvanje PDF-a u fajl
$client->email()->send($pfrBroj, $email) POST Slanje računa na email
$client->test()->run() POST Test fiskalizacija (samo sandbox)

Tipovi računa

Tip računa se postavlja pomoću InvoiceType enum-a:

Enum vrednostOpis
InvoiceType::ProdajaPromet prodaja (default)
InvoiceType::AvansAvansni račun
InvoiceType::ProformaProforma račun
InvoiceType::KopijaKopija računa
InvoiceType::ObukaRačun za obuku
use Efiskalizacija\Enum\InvoiceType;
use Efiskalizacija\Enum\TransactionType;

$invoice = Invoice::create()
    ->addItem($item)
    ->setPaymentType(PaymentType::Kartica)
    ->setInvoiceType(InvoiceType::Prodaja)
    ->setTransactionType(TransactionType::Sale);

Za povraćaj (refund) koristite TransactionType::Refund.

Načini plaćanja

Enum vrednostVSDC kodOpis
PaymentType::Gotovina1Gotovina
PaymentType::Kartica2Platna kartica
PaymentType::Virman4Virmansko plaćanje
PaymentType::Vaucer5Vaučer
PaymentType::Instant6Instant plaćanje (IPS)
PaymentType::Drugo0Drugo bezgotovinsko

Kupci

Klasa Customer ima factory metode za svaki tip kupca:

use Efiskalizacija\DTO\Customer;

// Pravno lice (PIB - 9 cifara)
$kupac = Customer::pravnoLice(
    pib: '123456789',
    ime: 'Firma DOO',
    adresa: 'Knez Mihailova 10',
    mesto: 'Beograd',
    email: 'firma@example.com'
);

// Fizičko lice (JMBG - 13 cifara)
$kupac = Customer::fizickoLice(
    jmbg: '0101990710001',
    ime: 'Petar Petrović',
    email: 'petar@example.com'
);

// Javni sektor (JBKJS)
$kupac = Customer::javniSektor(
    jbkjs: '12345',
    ime: 'Gradska uprava'
);

// Anonimni kupac
$kupac = Customer::anonimni(email: 'kupac@example.com');

// Postavljanje kupca na račun
$invoice = Invoice::create()
    ->addItem($item)
    ->setPaymentType(PaymentType::Kartica)
    ->setCustomer($kupac);

Popusti

Popust se može primeniti na stavku na dva načina (međusobno isključiva):

// Fiksni popust u RSD
$item = new InvoiceItem(
    naziv: 'Laptop',
    kolicina: 1,
    jedinicnaCena: 85000.00,
    pdvStopa: 20,
    popust: 5000.00  // Osnovica: 85000 - 5000 = 80000
);

// Procentualni popust (0-100%)
$item = new InvoiceItem(
    naziv: 'Miš',
    kolicina: 2,
    jedinicnaCena: 4500.00,
    pdvStopa: 20,
    rabatProcenat: 10.0  // Osnovica: 9000 - 10% = 8100
);
Važno: Polja popust i rabatProcenat ne mogu se koristiti na istoj stavci. Fiksni popust ne sme biti veći od iznosa stavke (kolicina × jedinicnaCena).

PDV kategorije (0% stavke)

Za stavke sa pdvStopa: 0, koristite TaxCategory enum za razlikovanje:

Enum vrednostSandbox labelProdukcija labelOpis
TaxCategory::OslobodjenB (0%)Г (0%)Oslobođen PDV-a
TaxCategory::NijeUPdvC (0%)А (0%)Nije u sistemu PDV-a
use Efiskalizacija\Enum\TaxCategory;

$item = new InvoiceItem(
    naziv: 'Usluga oslobođena PDV-a',
    kolicina: 1,
    jedinicnaCena: 5000.00,
    pdvStopa: 0,
    pdvKategorija: TaxCategory::Oslobodjen
);

Split payment

Kada kupac plaća kombinacijom više načina plaćanja:

use Efiskalizacija\DTO\Payment;

$invoice = Invoice::create()
    ->addItem(new InvoiceItem(
        naziv: 'Proizvod',
        kolicina: 1,
        jedinicnaCena: 12800.00,
        pdvStopa: 20
    ))
    ->setSplitPayments([
        new Payment(PaymentType::Gotovina, 5000.00),
        new Payment(PaymentType::Kartica, 7800.00),
    ]);

$result = $client->fiskalizacija()->fiskalizuj($invoice);
Pravila: Minimum 2 plaćanja, zbir mora biti jednak ukupnom iznosu, svaki tip mora biti jedinstven.

Avansni računi

Kompletan lanac avansnih računa sa referencama:

use Efiskalizacija\Enum\InvoiceType;
use Efiskalizacija\Enum\TransactionType;

// 1. Prvi avans (bez reference)
$avans1 = Invoice::create()
    ->addItem(new InvoiceItem('Avans za laptop', 1, 50000.00, 20))
    ->setPaymentType(PaymentType::Kartica)
    ->setInvoiceType(InvoiceType::Avans)
    ->setTransactionType(TransactionType::Sale)
    ->setCustomer(Customer::pravnoLice('123456789'));

$result1 = $client->fiskalizacija()->fiskalizuj($avans1);

// 2. Drugi avans (referenca na prvi)
$avans2 = Invoice::create()
    ->addItem(new InvoiceItem('Drugi avans za laptop', 1, 30000.00, 20))
    ->setPaymentType(PaymentType::Virman)
    ->setInvoiceType(InvoiceType::Avans)
    ->setTransactionType(TransactionType::Sale)
    ->setCustomer(Customer::pravnoLice('123456789'))
    ->setReferentDocument($result1->pfrBroj);

$result2 = $client->fiskalizacija()->fiskalizuj($avans2);

// 3. Avans refundacija (referenca na poslednji avans)
$refund = Invoice::create()
    ->addItem(new InvoiceItem('Refundacija avansa', 1, 80000.00, 20))
    ->setPaymentType(PaymentType::Kartica)
    ->setInvoiceType(InvoiceType::Avans)
    ->setTransactionType(TransactionType::Refund)
    ->setCustomer(Customer::pravnoLice('123456789'))
    ->setReferentDocument($result2->pfrBroj);

$result3 = $client->fiskalizacija()->fiskalizuj($refund);

// 4. Konačni promet račun (referenca na avans refundaciju)
$konacni = Invoice::create()
    ->addItem(new InvoiceItem('Laptop Dell XPS 15', 1, 250000.00, 20))
    ->setPaymentType(PaymentType::Kartica)
    ->setInvoiceType(InvoiceType::Prodaja)
    ->setTransactionType(TransactionType::Sale)
    ->setCustomer(Customer::pravnoLice('123456789'))
    ->setReferentDocument($result3->pfrBroj);

$result4 = $client->fiskalizacija()->fiskalizuj($konacni);

PDF preuzimanje

// Preuzmi PDF kao string
$pdfContent = $client->pdf()->download('AB12CD34-Ef5Gh6i7-101');
file_put_contents('racun.pdf', $pdfContent);

// Ili direktno u fajl
$client->pdf()->downloadToFile('AB12CD34-Ef5Gh6i7-101', '/path/to/racun.pdf');

// Slanje na email
$client->email()->send('AB12CD34-Ef5Gh6i7-101', 'kupac@example.com');

Status i lista računa

// Status tenanta
$status = $client->status()->fetch();
echo "Firma: {$status->naziv}\n";
echo "PIB: {$status->pib}\n";
echo "Ovaj mesec: {$status->thisMonth}/{$status->maxInvoicesPerMonth}\n";
echo "Uspešnost: {$status->successRate}%\n";

// Lista računa
$lista = $client->invoices()->list(limit: 20, offset: 0);
foreach ($lista->invoices as $racun) {
    echo "{$racun['pfr_broj']} - {$racun['ukupan_iznos']} RSD\n";
}

Webhook prijem

SDK pruža WebhookPayload klasu za parsiranje webhook notifikacija:

use Efiskalizacija\Webhook\WebhookPayload;

// U vašem webhook endpoint-u
$json = file_get_contents('php://input');
$payload = WebhookPayload::fromJson($json);

if ($payload->isFiscalized()) {
    // Uspešna fiskalizacija
    echo "PFR: " . $payload->pfrBroj . "\n";
    echo "Iznos: " . $payload->iznos . " RSD\n";
    // Ažurirajte narudžbinu u vašem sistemu
}

if ($payload->isFailed()) {
    // Neuspela fiskalizacija
    error_log("Greška: " . $payload->status);
    // Implementirajte retry logiku
}

// Kompletni podaci
$sviPodaci = $payload->rawData;

Idempotentnost

SDK podržava idempotency ključeve za sprečavanje duplikata:

$invoice = Invoice::create()
    ->addItem($item)
    ->setPaymentType(PaymentType::Kartica)
    ->setIdempotencyKey('order-12345');  // Jedinstveni ključ

// Čak i ako se pozove više puta, račun se kreira samo jednom
$result = $client->fiskalizacija()->fiskalizuj($invoice);

Error handling

SDK koristi specifične exception klase za svaki tip greške:

use Efiskalizacija\Exception\AuthenticationException;
use Efiskalizacija\Exception\ValidationException;
use Efiskalizacija\Exception\RateLimitException;
use Efiskalizacija\Exception\NetworkException;
use Efiskalizacija\Exception\ServerException;
use Efiskalizacija\Exception\ForbiddenException;
use Efiskalizacija\Exception\EfiskalizacijaException;

try {
    $result = $client->fiskalizacija()->fiskalizuj($invoice);
} catch (AuthenticationException $e) {
    // 401 - Neispravan API ključ ili secret
    error_log("Auth greška: " . $e->getMessage());

} catch (ValidationException $e) {
    // 400/422 - Neispravni podaci
    foreach ($e->getErrors() as $error) {
        echo "Greška: $error\n";
    }

} catch (RateLimitException $e) {
    // 429 - Previše zahteva
    $retryAfter = $e->getRetryAfter(); // sekunde do ponovnog pokušaja
    sleep($retryAfter ?? 60);

} catch (ForbiddenException $e) {
    // 403 - Nalog deaktiviran
    error_log("Pristup odbijen: " . $e->getMessage());

} catch (NetworkException $e) {
    // Timeout, DNS, konekcija
    error_log("Mrežna greška: " . $e->getMessage());

} catch (ServerException $e) {
    // 500/503 - Server greška
    error_log("Server greška: " . $e->getMessage());

} catch (EfiskalizacijaException $e) {
    // Sve ostale greške
    error_log("Greška: " . $e->getMessage());
    $responseBody = $e->getResponseBody();
}
Automatski retry: SDK automatski ponavlja zahteve za kodove 429, 502, 503 i 504 sa eksponencijalnim back-off-om. Podrazumevano 3 pokušaja sa baznim kašnjenjem od 1 sekunde.

Konfiguracija

Za naprednu konfiguraciju koristite Config klasu:

use Efiskalizacija\Config;
use Efiskalizacija\EfiskalizacijaClient;

// Produkcija sa default podešavanjima
$config = Config::production('api_key', 'api_secret');

// Sandbox sa default podešavanjima
$config = Config::sandbox('api_key', 'api_secret');

// Potpuna kontrola
$config = new Config(
    apiKey: 'efisk_1_abc123...',
    apiSecret: 'vas-64-karakter-secret',
    baseUrl: Config::BASE_URL_PRODUCTION,
    timeout: 30,           // HTTP timeout u sekundama
    connectTimeout: 10,    // Konekcija timeout u sekundama
    maxRetries: 3,         // Broj ponovnih pokušaja
    retryBaseDelayMs: 1000, // Bazno kašnjenje (ms)
    retryMultiplier: 2      // Množilac za eksponencijalni back-off
);

$client = new EfiskalizacijaClient($config);

Podrazumevane vrednosti

ParametarDefaultOpis
timeout30sHTTP request timeout
connectTimeout10sTimeout za uspostavljanje konekcije
maxRetries3Broj automatskih ponovnih pokušaja
retryBaseDelayMs1000msBazno kašnjenje pre retry-a
retryMultiplier2Množilac (1s → 2s → 4s)

URL-ovi okruženja

OkruženjeURLKonstanta
Produkcijahttps://efiskalizacija.cloudConfig::BASE_URL_PRODUCTION
Sandboxhttps://staging.efiskalizacija.cloudConfig::BASE_URL_SANDBOX

Logovanje

SDK podržava PSR-3 logger interfejs za praćenje zahteva:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$logger = new Logger('efiskalizacija');
$logger->pushHandler(new StreamHandler('storage/logs/sdk.log'));

$client = EfiskalizacijaClient::create(
    apiKey: 'efisk_1_abc123...',
    apiSecret: 'secret',
    logger: $logger
);

Kompletan primer

E-commerce integracija sa svim opcijama:

<?php
declare(strict_types=1);

require_once 'vendor/autoload.php';

use Efiskalizacija\EfiskalizacijaClient;
use Efiskalizacija\DTO\{Invoice, InvoiceItem, Customer};
use Efiskalizacija\Enum\{PaymentType, TaxCategory};
use Efiskalizacija\Exception\EfiskalizacijaException;

$client = EfiskalizacijaClient::create(
    apiKey: $_ENV['EFISK_API_KEY'],
    apiSecret: $_ENV['EFISK_API_SECRET']
);

try {
    $invoice = Invoice::create()
        ->setInvoiceNumber('WS-2026-0042')
        ->setCashier('Web Shop')
        ->setNote('Hvala na kupovini!')
        ->setIdempotencyKey('order-42')
        ->setCustomer(Customer::pravnoLice(
            pib: '123456789',
            ime: 'Primer DOO',
            email: 'firma@example.com'
        ))
        ->addItem(new InvoiceItem(
            naziv: 'Laptop Dell XPS 15',
            kolicina: 1,
            jedinicnaCena: 250000.00,
            pdvStopa: 20,
            sifra: 'DELL-XPS-15',
            barcode: '8606123456789'
        ))
        ->addItem(new InvoiceItem(
            naziv: 'Dostava',
            kolicina: 1,
            jedinicnaCena: 500.00,
            pdvStopa: 20
        ))
        ->addItem(new InvoiceItem(
            naziv: 'Osiguranje (oslobođeno)',
            kolicina: 1,
            jedinicnaCena: 2000.00,
            pdvStopa: 0,
            pdvKategorija: TaxCategory::Oslobodjen
        ))
        ->setPaymentType(PaymentType::Kartica);

    $result = $client->fiskalizacija()->fiskalizuj($invoice);

    // Sačuvaj rezultat u bazu
    $order->update([
        'pfr_broj' => $result->pfrBroj,
        'qr_code' => $result->qrCode,
        'racun_id' => $result->racunId,
    ]);

    // Pošalji PDF kupcu
    $client->email()->send($result->pfrBroj, 'kupac@example.com');

} catch (EfiskalizacijaException $e) {
    error_log("Fiskalizacija neuspešna: " . $e->getMessage());
    // Implementirajte fallback/retry logiku
}