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:
| Metoda | HTTP | Opis |
|---|---|---|
$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 vrednost | Opis |
|---|---|
InvoiceType::Prodaja | Promet prodaja (default) |
InvoiceType::Avans | Avansni račun |
InvoiceType::Proforma | Proforma račun |
InvoiceType::Kopija | Kopija računa |
InvoiceType::Obuka | Rač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 vrednost | VSDC kod | Opis |
|---|---|---|
PaymentType::Gotovina | 1 | Gotovina |
PaymentType::Kartica | 2 | Platna kartica |
PaymentType::Virman | 4 | Virmansko plaćanje |
PaymentType::Vaucer | 5 | Vaučer |
PaymentType::Instant | 6 | Instant plaćanje (IPS) |
PaymentType::Drugo | 0 | Drugo 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
);
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 vrednost | Sandbox label | Produkcija label | Opis |
|---|---|---|---|
TaxCategory::Oslobodjen | B (0%) | Г (0%) | Oslobođen PDV-a |
TaxCategory::NijeUPdv | C (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);
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();
}
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
| Parametar | Default | Opis |
|---|---|---|
timeout | 30s | HTTP request timeout |
connectTimeout | 10s | Timeout za uspostavljanje konekcije |
maxRetries | 3 | Broj automatskih ponovnih pokušaja |
retryBaseDelayMs | 1000ms | Bazno kašnjenje pre retry-a |
retryMultiplier | 2 | Množilac (1s → 2s → 4s) |
URL-ovi okruženja
| Okruženje | URL | Konstanta |
|---|---|---|
| Produkcija | https://efiskalizacija.cloud | Config::BASE_URL_PRODUCTION |
| Sandbox | https://staging.efiskalizacija.cloud | Config::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
}