За програмери
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 за е-Фактура на УЈП.
Не најдовте одговор? Пишете ни