За програмере
SDK за систем електронских фактура УЈП-а. Потписивање, провера, обрачун збирова и слање из вашег софтвера, исто оно што користи апликација.
Брзи почетак
- Обезбедите приступ тест окружењу е-Фактуре УЈП-а: ЕДБ, еУЈП ID и квалификовани сертификат.
- Додајте SDK за ваш језик из репоа, са путање у табели испод.
- Отворите потписник (USB токен, датотеку .p12 или macOS Keychain) и направите клијента за тест окружење.
- Пошаљите документ и проверите EUID у одговору.
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 и други). Инсталирају се из репоа, који добијате при куповини.
| Језик | Пакет | Инсталација | Статус |
|---|---|---|---|
| Rust | efakt-sdk, efakt-signer | из репоа (crates/efakt-sdk, crates/efakt-signer) | Доступно |
| TypeScript | @rid/efakt | из репоа (sdks/typescript) | Доступно |
| Python | efakt | из репоа (sdks/python) | Доступно |
| PHP | rid/efakt | из репоа (sdks/php) | Доступно |
| .NET | Rid.Efakt | из репоа (sdks/dotnet) | Доступно |
| Java | mk.rid:efakt | из репоа (sdks/java) | Доступно |
| Go | github.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).
Извори
- JSON структура на wiki-ју УЈП-а.
- Wiki за е-Фактуру УЈП-а.
Нисте нашли одговор? Пишите нам