еФакт™
Српски

За програмере

SDK за систем електронских фактура УЈП-а. Потписивање, провера, обрачун збирова и слање из вашег софтвера, исто оно што користи апликација.

Брзи почетак

  1. Обезбедите приступ тест окружењу е-Фактуре УЈП-а: ЕДБ, еУЈП ID и квалификовани сертификат.
  2. Додајте SDK за ваш језик из репоа, са путање у табели испод.
  3. Отворите потписник (USB токен, датотеку .p12 или macOS Keychain) и направите клијента за тест окружење.
  4. Пошаљите документ и проверите EUID у одговору.
Слање фактуре USB токеном
import { UjpClient, Pkcs11Signer } from "@rid/efakt";

const signer = await Pkcs11Signer.open({
  module: "/usr/lib/opensc-pkcs11.so",
  pin,
});

const ujp = new UjpClient({
  environment: "test",
  edb, eujpId,
  signer,
});

const sent = await ujp.sendInvoice(invoice);
console.log(sent.euid, sent.qrLink);
from efakt import Environment, Pkcs11Signer, UjpClient

signer = Pkcs11Signer.open(
    module="/usr/lib/opensc-pkcs11.so",
    pin=pin,
)

ujp = UjpClient(
    environment=Environment.TEST,
    edb=edb, eujp_id=eujp_id,
    signer=signer,
)

sent = ujp.send_invoice(invoice)
print(sent.euid, sent.qr_link)
<?php
use Rid\Efakt\{Environment, Pkcs11Signer, UjpClient};

$signer = Pkcs11Signer::open(
    module: '/usr/lib/opensc-pkcs11.so',
    pin: $pin,
);

$ujp = new UjpClient(
    environment: Environment::Test,
    edb: $edb, eujpId: $eujpId,
    signer: $signer,
);

$sent = $ujp->sendInvoice($invoice);
echo $sent->euid, ' ', $sent->qrLink;
using Rid.Efakt;
using Rid.Efakt.Signing;

using var signer = Pkcs11Signer.Open(
    module: "/usr/lib/opensc-pkcs11.so",
    pin: pin);

var ujp = new UjpClient(new ClientConfig
{
    Environment = UjpEnvironment.Test,
    Edb = edb, EujpId = eujpId,
    Signer = signer,
});

var sent = await ujp.SendInvoiceAsync(invoice);
Console.WriteLine($"{sent.Euid} {sent.QrLink}");
import mk.rid.efakt.*;

var signer = Pkcs11Signer.open(
    "/usr/lib/opensc-pkcs11.so", pin);

var ujp = UjpClient.builder()
    .environment(Environment.TEST)
    .edb(edb).eujpId(eujpId)
    .signer(signer)
    .build();

var sent = ujp.sendInvoice(invoice);
System.out.println(sent.euid() + " " + sent.qrLink());
signer, err := efakt.OpenPKCS11(efakt.PKCS11{
	Module: "/usr/lib/opensc-pkcs11.so",
	PIN:    pin,
})
if err != nil {
	return err
}

ujp := efakt.NewClient(efakt.Config{
	Environment: efakt.Test,
	EDB:         edb,
	EUJPID:      eujpID,
	Signer:      signer,
})

sent, err := ujp.SendInvoice(ctx, invoice)
if err != nil {
	return err
}
fmt.Println(sent.EUID, sent.QRLink)
let signer = SignerSource::Pkcs11 {
    module: "/usr/lib/opensc-pkcs11.so".into(),
    slot_id: None,
    object_id_hex: None,
}.open(&pin)?;

let ujp = UjpClient::new(ClientConfig {
    environment: Environment::Test,
    edb, eujp_id,
    ..Default::default()
})?.with_signer(signer)?;

let sent = ujp.send_invoice(invoice).await?;
println!("{:?} {:?}", sent.euid, sent.qr_link);

SDK по језику

SDK-ови се плаћају, па нису на јавним регистрима (crates.io, npm, PyPI и други). Инсталирају се из репоа, који добијате при куповини.

ЈезикПакетИнсталацијаСтатус
Rustefakt-sdk, efakt-signerиз репоа (crates/efakt-sdk, crates/efakt-signer)Доступно
TypeScript@rid/efaktиз репоа (sdks/typescript)Доступно
Pythonefaktиз репоа (sdks/python)Доступно
PHPrid/efaktиз репоа (sdks/php)Доступно
.NETRid.Efaktиз репоа (sdks/dotnet)Доступно
Javamk.rid:efaktиз репоа (sdks/java)Доступно
Gogithub.com/rid/efakt-goиз репоа (sdks/go)Доступно

Потписивање

Сваки захтев ка УЈП-у који шаље или мења документ потписан је као компактни JWS са RS256 (RSASSA-PKCS1-v1_5 са SHA-256). Заглавље је тачно {"alg":"RS256","typ":"JWT"}, као у примерима УЈП-а, а payload је JSON документа заједно са requestTimestamp.

Кључ може бити на три места:

  • PKCS#11: USB токен или смарт картица. SDK зна уобичајене путање до модула (SafeNet/Thales, OpenSC, SoftHSM). Токени и сертификати могу се излистати без PIN-а, а сертификат се бира по слоту и CKA_ID.
  • PKCS#12: датотека .p12 или .pfx, са диска или из меморије.
  • macOS Keychain: само на macOS-у, укључујући токене преко CryptoTokenKit-а. PIN тада тражи macOS.

Приватни кључ никад не напушта токен или датотеку. PIN се чува само у меморији и брише се после употребе. Локална база (efakt.db, SQLite, заједничка за апликацију и CLI) не садржи тајне.

Уз захтев иду и заглавља X-EUJP-ID, X-EDB и X-SERIAL-NUMBER. Серијски број сертификата може се послати хексадецимално, малим словима или децимално, или ручно, ако га УЈП не препознаје.

requestTimestamp је по локалном времену Скопља, у формату yyyy-MM-ddTHH:mm:ss. УЈП прихвата одступање од ±5 минута, па SDK пре слања усклађује сат са временом сервера УЈП-а.

Пре првог слања можете направити тест потпис: локално потписивање и провера, без мреже. SDK и локално проверава сертификат као УЈП: да ли је истекао или још не важи (E5002, E5004), да ли има RSA кључ од најмање 2048 бита (E5004, E5005), да ли је сертификат издаваоца уместо личног, и да ли се ЕДБ у сертификату поклапа са подешеним (E1013, E4023). Опозив сертификата (OCSP/CRL) проверава само УЈП.

Провера према структури УЈП-а

Пре слања документ се проверава према званичној JSON структури УЈП-а и страницама за сваку врсту документа. Правила су генерисана из wiki-ја: формат сваког поља (дужина текста, датум YYYY-MM-DD, децимале, цео број, логичка вредност) и поља која су безусловно обавезна за сваку врсту документа.

Условно обавезна поља (означена са З* на wiki-ју, на пример она која зависе од ПДВ-а, резидентности или референци) не траже се локално. Њих проверава УЈП при слању. Поља у ставкама, референцама и адреси проверавају се када тај део постоји.

Захтеве за дату врсту документа можете прочитати и директно од УЈП-а (document_type_requirements).

Обрачун збирова

SDK обрачунава ставке, збирове и ПДВ рекапитулацију према правилима УЈП-а:

  • ставке се обрачунавају на 4 децимале и не заокружују се на 2;
  • збирови се сабирају незаокружено, па се заокружују на 2 децимале (половина навише);
  • docGrossAmountR је цео број у денарима (или без валуте), а у страној валути остаје на 2 децимале (правило УЈП-а од 19.10.2026);
  • у денарима docAvansAmount, docFinalAmount, docGrossAmountR, docAmountIncrease и docAmountDecrease морају бити цели бројеви, иначе провера враћа грешку UJP08-08 до UJP08-12;
  • docTotals и vatTotals попуњавају се из ставки и пореских индикатора.

Износи аванса, повећања и смањења су само улаз и не обрачунавају се. После обрачуна, провера потврђује да се збир vatTaxableAmount и docNetAmountDisc + docVatAmount поклапају са укупним износима у оквиру толеранције УЈП-а од 0,1.

Слање

Документ се шаље на JSONReceiver/api/v1/sales-invoices/send, са врстом документа у заглављу X-DOC-TYPE-CODE. Цео ток је један позив (send_checked): обрачун, провера, усклађивање сата, нови requestTimestamp, потпис и слање. Ако провера пронађе грешку, ништа се не шаље.

После успешног слања, одговор садржи EUID (број који УЈП додељује документу), поруку и статус, да ли купац користи систем, време пријема и линк за QR код.

За сторно и корекцију SDK припрема нови документ од оригинала: сторно са негативним количинама, docStorno = 1 и референцом „ST“, корекцију са docStorno = 2 и референцом „KR“.

УЈП дозвољава један захтев у секунди по кориснику. SDK чека најмање 1050 ms између захтева, а на одговор 429 покушава поново према Retry-After, највише 3 пута.

Статус и шифре грешака

Осим слања, SDK покрива и остале сервисе УЈП-а:

  • тренутни статус и историју статуса документа, промене по дану, листе EUID-ова и статуса за период;
  • PDF, потписани JSON и ASiC архиву документа;
  • прихватање и одбијање улазних докумената (одбијање захтева шифру разлога) и ПДВ збирове за улазне документе;
  • обвезника и огранке по ЕДБ-у, државе, валуте и средњи курс НБРСМ;
  • шифарнике: разлози за корекцију, сторно и одбијање, начини плаћања, статуси, врсте докумената, пореске групе и индикатори.

УЈП дозвољава период од највише једног месеца, па SDK дуже периоде дели на делове од 28 дана.

Шифре грешака су са званичне листе УЈП-а, у три дела: eInvoiceApi, JSON Receiver и веб апликација. Неке шифре значе различито у сваком делу (на пример E4023), па се шифра тумачи према сервису који ју је вратио. Групе су:

Група Значење
E1xxx пословне грешке: обвезник, сертификат, статус документа
E4xxx грешке у захтеву: параметри, заглавља, формат
E5xxx грешке при обради: сертификат, JWS, сервер

За сваку шифру SDK даје текст УЈП-а, да ли је повезана са сертификатом, да ли вреди покушати поново и савет на македонском. Грешка од УЈП-а враћа се са HTTP статусом, шифром, поруком и целим одговором.

Тест и продукција

Окружење eInvoiceApi JSON Receiver
Тест (подразумевано) https://efakturatest.ujp.gov.mk/einvoice_api https://efakturatest.ujp.gov.mk/JSONReceiver
Продукција https://efaktura.ujp.gov.mk/einvoice_api https://efaktura.ujp.gov.mk/JSONReceiver

Документи послати у тест окружење немају правно дејство. Продукцијске адресе нису објављене у документацији УЈП-а и још нису потврђене. Ако вам УЈП да друге адресе, постављате их као сопствене (Custom).

Извори

Нисте нашли одговор? Пишите нам