eFiskalizacija.cloud

Konfiguracija

Detaljna podešavanja za optimalan rad sa eFiskalizacija API-jem.

Podešavanje API kredencijala

API kredencijali se koriste za autentifikaciju svakog zahteva prema eFiskalizacija servisu. Preporučujemo čuvanje u environment varijablama.

Environment varijable (preporučeno)

# .env fajl vašeg web šopa
EFISK_API_KEY=vaš_api_ključ
EFISK_API_SECRET=vaš_tajni_ključ
EFISK_API_URL=https://efiskalizacija.cloud/api/multitenant.php
Nikada ne čuvajte API Secret direktno u izvornom kôdu ili git repozitorijumu. Koristite environment varijable ili secrets manager.

Konfiguracija po jeziku

Izaberite programski jezik za primer konfiguracije:

PHP getenv
<?php
// config/efiskalizacija.php
return [
    'api_key'    => getenv('EFISK_API_KEY'),
    'api_secret' => getenv('EFISK_API_SECRET'),
    'api_url'    => getenv('EFISK_API_URL') ?: 'https://efiskalizacija.cloud/api/multitenant.php',
    'timeout'    => 30, // sekundi
    'retries'    => 3,
];

// Korišćenje
$config = require 'config/efiskalizacija.php';
$client = new EfiskalizacijaClient(
    $config['api_key'],
    $config['api_secret'],
    $config['api_url']
);
Python os.environ
# config/efiskalizacija.py
import os
from dataclasses import dataclass

@dataclass
class EfiskConfig:
    api_key: str
    api_secret: str
    api_url: str = 'https://efiskalizacija.cloud/api/multitenant.php'
    timeout: int = 30
    retries: int = 3

    @classmethod
    def from_env(cls):
        return cls(
            api_key=os.environ['EFISK_API_KEY'],
            api_secret=os.environ['EFISK_API_SECRET'],
            api_url=os.environ.get('EFISK_API_URL', cls.api_url),
        )

# Korišćenje
config = EfiskConfig.from_env()
client = EfiskalizacijaClient(config.api_key, config.api_secret, config.api_url)
JavaScript / Node.js process.env
// config/efiskalizacija.js
require('dotenv').config(); // npm install dotenv

const config = {
    apiKey: process.env.EFISK_API_KEY,
    apiSecret: process.env.EFISK_API_SECRET,
    apiUrl: process.env.EFISK_API_URL || 'https://efiskalizacija.cloud/api/multitenant.php',
    timeout: 30000, // milisekundi
    retries: 3,
};

// Validacija
if (!config.apiKey || !config.apiSecret) {
    throw new Error('EFISK_API_KEY i EFISK_API_SECRET su obavezni');
}

module.exports = config;

// Korišćenje
const config = require('./config/efiskalizacija');
const client = new EfiskalizacijaClient(config.apiKey, config.apiSecret, config.apiUrl);
C# / .NET IConfiguration
// appsettings.json
{
    "Efiskalizacija": {
        "ApiKey": "",       // Ili koristi User Secrets / Environment
        "ApiSecret": "",
        "ApiUrl": "https://efiskalizacija.cloud/api/multitenant.php",
        "Timeout": 30,
        "Retries": 3
    }
}

// EfiskOptions.cs
public class EfiskOptions
{
    public string ApiKey { get; set; } = "";
    public string ApiSecret { get; set; } = "";
    public string ApiUrl { get; set; } = "https://efiskalizacija.cloud/api/multitenant.php";
    public int Timeout { get; set; } = 30;
    public int Retries { get; set; } = 3;
}

// Program.cs / Startup.cs
builder.Services.Configure<EfiskOptions>(
    builder.Configuration.GetSection("Efiskalizacija"));

// Ili iz environment varijabli
builder.Services.AddSingleton(sp => new EfiskalizacijaClient(
    Environment.GetEnvironmentVariable("EFISK_API_KEY") ?? "",
    Environment.GetEnvironmentVariable("EFISK_API_SECRET") ?? "",
    Environment.GetEnvironmentVariable("EFISK_API_URL")
        ?? "https://efiskalizacija.cloud/api/multitenant.php"
));
Java / Spring Boot @ConfigurationProperties
// application.yml
efiskalizacija:
  api-key: ${EFISK_API_KEY}
  api-secret: ${EFISK_API_SECRET}
  api-url: https://efiskalizacija.cloud/api/multitenant.php
  timeout: 30
  retries: 3

// EfiskProperties.java
@Configuration
@ConfigurationProperties(prefix = "efiskalizacija")
public class EfiskProperties {
    private String apiKey;
    private String apiSecret;
    private String apiUrl = "https://efiskalizacija.cloud/api/multitenant.php";
    private int timeout = 30;
    private int retries = 3;

    // Getters i setters
}

// EfiskConfig.java
@Configuration
public class EfiskConfig {

    @Bean
    public EfiskalizacijaClient efiskClient(EfiskProperties props) {
        return new EfiskalizacijaClient(
            props.getApiKey(),
            props.getApiSecret(),
            props.getApiUrl()
        );
    }
}

// Korišćenje u servisu
@Service
public class FiskalizacijaService {
    private final EfiskalizacijaClient client;

    public FiskalizacijaService(EfiskalizacijaClient client) {
        this.client = client;
    }
}

Timeout podešavanja

Preporučeni timeout za API zahteve:

OperacijaPreporučeni timeoutObrazloženje
Fiskalizacija30 sekundiVSDC može biti spor u vršnim časovima
Status provera10 sekundiBrz odgovor, nema VSDC poziva
PDF preuzimanje15 sekundiGenerisanje PDF-a zahteva vreme
Preporuka: Ne postavljajte timeout ispod 15 sekundi za fiskalizaciju. VSDC sandbox može imati sporije odgovore od produkcije.

Retry logika

Za povećanje pouzdanosti implementirajte exponential backoff. Izaberite programski jezik:

PHP sleep
<?php
function fiskalizujSaRetry(EfiskalizacijaClient $client, array $podaci, int $maxRetries = 3): array
{
    $attempt = 0;
    $lastException = null;

    while ($attempt < $maxRetries) {
        try {
            return $client->fiskalizuj($podaci);
        } catch (RuntimeException $e) {
            $lastException = $e;
            $httpCode = $e->getCode();

            // Ne pokušavaj ponovo za klijentske greške (4xx)
            if ($httpCode >= 400 && $httpCode < 500) {
                throw $e;
            }

            $attempt++;
            if ($attempt < $maxRetries) {
                $delay = pow(2, $attempt); // 2s, 4s, 8s
                sleep($delay);
            }
        }
    }

    throw $lastException;
}
Python time.sleep
import time
from requests.exceptions import HTTPError

def fiskalizuj_sa_retry(client, podaci, max_retries=3):
    last_exception = None

    for attempt in range(max_retries):
        try:
            return client.fiskalizuj(podaci)
        except HTTPError as e:
            last_exception = e
            status_code = e.response.status_code

            # Ne pokušavaj ponovo za klijentske greške (4xx)
            if 400 <= status_code < 500:
                raise

            if attempt < max_retries - 1:
                delay = 2 ** (attempt + 1)  # 2s, 4s, 8s
                time.sleep(delay)

    raise last_exception

# Ili koristi tenacity biblioteku
# pip install tenacity
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=2, max=8),
    retry=retry_if_exception(lambda e: getattr(e, 'status_code', 0) >= 500)
)
def fiskalizuj_sa_tenacity(client, podaci):
    return client.fiskalizuj(podaci)
JavaScript / Node.js setTimeout
async function fiskalizujSaRetry(client, podaci, maxRetries = 3) {
    let lastError = null;

    for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
            return await client.fiskalizuj(podaci);
        } catch (error) {
            lastError = error;
            const statusCode = error.statusCode || error.response?.status || 0;

            // Ne pokušavaj ponovo za klijentske greške (4xx)
            if (statusCode >= 400 && statusCode < 500) {
                throw error;
            }

            if (attempt < maxRetries - 1) {
                const delay = Math.pow(2, attempt + 1) * 1000; // 2s, 4s, 8s
                await new Promise(resolve => setTimeout(resolve, delay));
            }
        }
    }

    throw lastError;
}

// Korišćenje
try {
    const result = await fiskalizujSaRetry(client, podaci);
    console.log('PFR:', result.data.rezultat.pfr_broj);
} catch (error) {
    console.error('Fiskalizacija neuspešna nakon svih pokušaja:', error.message);
}
C# / .NET Polly
// Koristi Polly NuGet paket: dotnet add package Polly
using Polly;
using Polly.Retry;

public class FiskalizacijaService
{
    private readonly EfiskalizacijaClient _client;
    private readonly AsyncRetryPolicy _retryPolicy;

    public FiskalizacijaService(EfiskalizacijaClient client)
    {
        _client = client;

        _retryPolicy = Policy
            .Handle<HttpRequestException>(ex =>
            {
                // Retry samo za serverske greške (5xx)
                var statusCode = ExtractStatusCode(ex);
                return statusCode >= 500;
            })
            .WaitAndRetryAsync(
                retryCount: 3,
                sleepDurationProvider: attempt =>
                    TimeSpan.FromSeconds(Math.Pow(2, attempt)) // 2s, 4s, 8s
            );
    }

    public async Task<JsonDocument> FiskalizujSaRetryAsync(object podaci)
    {
        return await _retryPolicy.ExecuteAsync(async () =>
        {
            return await _client.FiskalizujAsync(podaci);
        });
    }

    private int ExtractStatusCode(HttpRequestException ex)
    {
        // .NET 5+: ex.StatusCode
        // Starije verzije: parsiraj iz Message
        return (int)(ex.StatusCode ?? 0);
    }
}
Java / Spring Boot @Retryable
// build.gradle: implementation 'org.springframework.retry:spring-retry'
// ili pom.xml: spring-retry dependency

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Retryable;
import org.springframework.web.client.HttpServerErrorException;

@Configuration
@EnableRetry
public class RetryConfig {}

@Service
public class FiskalizacijaService {

    private final EfiskalizacijaClient client;

    public FiskalizacijaService(EfiskalizacijaClient client) {
        this.client = client;
    }

    @Retryable(
        value = HttpServerErrorException.class,  // Retry samo za 5xx
        maxAttempts = 3,
        backoff = @Backoff(delay = 2000, multiplier = 2)  // 2s, 4s, 8s
    )
    public JsonNode fiskalizujSaRetry(Map<String, Object> podaci) throws Exception {
        return client.fiskalizuj(podaci);
    }

    // Ili ručna implementacija
    public JsonNode fiskalizujSaRetryManual(Map<String, Object> podaci, int maxRetries)
            throws Exception {
        Exception lastException = null;

        for (int attempt = 0; attempt < maxRetries; attempt++) {
            try {
                return client.fiskalizuj(podaci);
            } catch (HttpServerErrorException e) {
                lastException = e;
                if (attempt < maxRetries - 1) {
                    long delay = (long) Math.pow(2, attempt + 1) * 1000;
                    Thread.sleep(delay);
                }
            }
        }
        throw lastException;
    }
}

Kada pokušati ponovo

HTTP kôdRetry?Objašnjenje
400NeNeispravan zahtev - ispravite parametre
401NePogrešan potpis - proverite kredencijale
429DaRate limit - sačekajte pre ponovnog pokušaja
500DaServerska greška - privremeni problem
503DaVSDC nedostupan - pokušajte za 30s

Podešavanja u admin panelu

Osim API konfiguracije, u admin panelu možete podesiti:

PDF podešavanja

Štampač

Email notifikacije

Konfiguracija SMTP-a za slanje računa emailom. Podešava se u admin panelu ili putem environment varijable MAIL_DSN.

# Format MAIL_DSN:
MAIL_DSN=smtp://korisnik%40domen.rs:lozinka@smtp.server.rs:587

CORS podešavanja

Ako pozivate API direktno iz pregledača (frontend JavaScript), potrebno je podesiti CORS:

# Dozvoljeni origin-i za vaš domen
Access-Control-Allow-Origin: https://vaš-web-šop.rs
Access-Control-Allow-Headers: X-Api-Key, X-Timestamp, X-Signature, Content-Type
Access-Control-Allow-Methods: GET, POST, OPTIONS
Bezbednosna napomena: Pozivanje API-ja direktno iz frontend-a izlaže vaš API Secret. Preporučujemo da API pozive vršite sa backend-a vašeg servera.

Security headers

Naš API automatski vraća sledeće bezbednosne headere:

HeaderVrednost
X-Content-Type-Optionsnosniff
X-Frame-OptionsDENY
X-Request-IdJedinstveni ID zahteva (za debug)
Strict-Transport-Securitymax-age=31536000
Debug: Koristite X-Request-Id header iz odgovora za praćenje zahteva prilikom komunikacije sa našom tehničkom podrškom.

Za početno podešavanje pogledajte vodič za instalaciju ili vodič za integraciju za kompletne primere kôda.