Integračná príručka: Napojenie tretej strany na QHUB a prijímanie/odosielanie Peppol dokumentov cez APEX
Určené pre: Vývojárov externých systémov (ERP, účtovné systémy, portály), ktorí sa chcú napojiť na QCloud HUB a vymieňať Peppol dokumenty (faktúry, …) prostredníctvom APEX Access Pointu.
1. Ako funguje integrácia
Celkový pohľad
Čo QHUB robí za vás
- Smerovanie – QHUB doručí správu z vášho systému do APEX-u a naopak; nemusíte vedieť, kde APEX beží.
- Retry a DLQ – pri dočasnom výpadku QHUB správu zopakuje; ak všetky pokusy zlyhajú, správa ide do Dead-Letter Queue.
- Audit – každá správa je zaznamenaná v transakčnej tabuľke so stavom a časom.
Čo musíte urobiť vy
| Smer | Vaša zodpovednosť |
|---|---|
| Prijímanie (Peppol → váš systém) | Konzumovať správy z vašej delivery queue; spracovať UBL XML; odoslať response naspäť. |
| Odosielanie (váš systém → Peppol) | Pripraviť UBL XML dokument; odoslať synchronizačnú správu do sync queue; sledovať stav. |
2. Predpoklady a onboarding
2.1 Čo potrebujete pred začatím
-
Peppol participant ID – váš klient musí byť zaregistrovaný v Peppol sieti. Identifikátor má formát
{schemeId}:{participantId}, napríklad0245:2024000001.Schéma
0245= slovenský DIČ v Peppol. Ak neviete svoje Peppol ID, kontaktujte vášho Peppol Service Provider (SMP). -
ASOL kontakt – požiadajte ASOL tím o:
- pridelenie
SystemCodevášho systému (napr.MYERP), - pridelenie
TenantId(UUID) vášho tenanta v QHUB, - prístup k QHUB Registration API (URL + API kľúč).
- pridelenie
-
RabbitMQ klient – váš systém musí vedieť komunikovať s RabbitMQ (AMQP 0-9-1 protokol). Odporúčame:
- Java: Spring AMQP / RabbitMQ Java Client
- .NET: MassTransit / RabbitMQ.Client
- Python: pika
- Node.js: amqplib
2.2 Onboarding kroky (prehľad)
1. Dohodnutie SystemCode + TenantId s ASOL
↓
2. Odoslanie popisu entity PeppolDocument do QHUB Registration API
↓
3. Administrátor ASOL vygeneruje jednorazový registračný kľúč
↓
4. Váš systém odošle kľúč na Registration API → získa RabbitMQ credentials
↓
5. Nakonfigurujete RabbitMQ v svojom systéme
↓
6. Otestujete end-to-end komunikáciu3. Registrácia systému v QHUB
Registrácia prebieha v dvoch krokoch cez HTTP REST API.
3.1 Odoslanie popisu entity
Pred vytvorením registrácie musíte QHUB informovať, aké entity váš systém podporuje. Pre Peppol dokumenty ide o entitu PeppolDocument.
Endpoint:
POST {QHUB_REGISTRATION_URL}/api/v1/SyncEntityCatalogItem/StoreSyncEntityItems Content-Type: application/json X-Api-Key: {váš API kľúč}
Request body:
{
"System": "MYERP",
"SyncEntityDatas": [
{
"EntityCode": "PeppolDocument",
"EntityType": 100,
"DefaultAllowed": true,
"Version": "1.0.0",
"SyncEntityDefinitionData": {
"SyncPropertiesData": [
{ "Name": "SenderParticipantId", "OriginalName": "senderParticipantId", "ProperyType": "String", "Required": true, "IsBusinessKey": false },
{ "Name": "ReceiverParticipantId", "OriginalName": "receiverParticipantId", "ProperyType": "String", "Required": true, "IsBusinessKey": false },
{ "Name": "DocumentTypeId", "OriginalName": "documentTypeId", "ProperyType": "String", "Required": true, "IsBusinessKey": false },
{ "Name": "ProcessId", "OriginalName": "processId", "ProperyType": "String", "Required": true, "IsBusinessKey": false },
{ "Name": "UblDocument", "OriginalName": "ublDocument", "ProperyType": "String", "Required": true, "IsBusinessKey": false },
{ "Name": "As4MessageId", "OriginalName": "as4MessageId", "ProperyType": "String", "Required": false, "IsBusinessKey": true },
{ "Name": "ReceivedAt", "OriginalName": "receivedAt", "ProperyType": "DateTime", "Required": false, "IsBusinessKey": false }
],
"SyncForeignKeysData": []
}
}
]
}
EntityType: 100– presná hodnota bude pridelená ASOL tímom. Použite hodnotu, ktorú dostanete od ASOL pri onboardingu. Číslo musí byť konzistentné medzi všetkými systémami.
Úspešná odpoveď: HTTP 200, body true
Toto volanie opakujte pri každom štarte vašej aplikácie aj pri každej zmene schémy (a zvýšte pritom Version).
3.2 Validácia registračného kľúča
Administrátor ASOL vám cez zabezpečený kanál (napr. email, Jira) odovzdá jednorazový registračný kľúč (platný 24 hodín).
Endpoint:
POST {QHUB_REGISTRATION_URL}/api/v1/Registration/Validate Content-Type: application/json
Request body:
{
"ParticipantSystem": "MYERP",
"ParticipantTenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"RegistrationKey": "REG-KEY-ODOSLANY-ADMINOM",
"RoutingKey": "MYERP.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
RoutingKey– pre systémy mimo ASOL Cloud (3ST) má formát{SystemCode}.{TenantId}. Tento kľúč identifikuje vašu delivery queue.
Úspešná odpoveď (HTTP 200):
{
"ParticipantSystem": "MYERP",
"ParticipantTenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"RabbitMqUrl": "amqps://rmq.qcloud.assecosoldevsk.com:5671",
"RabbitMqName": "myerp_tenant_abc123",
"RabbitMqPass": "••••••••••••",
"ErrorCode": null,
"Success": true,
"RegisteredEntities": [
{
"EntityType": 100,
"OwnerSystem": "APEX",
"OwnerTenantId": "...",
"RoutingKey": "MYERP.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"Operations": 1,
"Direction": 0,
"SyncMode": 0,
"AllowedFrom": "2026-04-16T00:00:00Z",
"AllowedTo": "2027-04-16T00:00:00Z",
"DeliverySystem": "MYERP",
"DeliveryTenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
]
}⚠️ Bezpečnosť:
RabbitMqPassje citlivý údaj. Uložte ho do Kubernetes Secret, HashiCorp Vault, alebo iného bezpečného úložiska. Nikdy ho nevkladajte priamo do zdrojového kódu ani do konfiguračných súborov v repozitári.
Chybové kódy:
| Kód | Popis |
|---|---|
RegCodeNotFound | Kľúč neexistuje alebo bolo nájdených viacero záznamov |
RegCodeExpired | Kľúč expiroval (platný 24h) |
SystemMismatch | ParticipantSystem sa nezhoduje s registráciou |
TenantMismatch | ParticipantTenantId sa nezhoduje s registráciou |
HubInternalError | Interná chyba QHUB |
4. Nastavenie RabbitMQ pripojenia
Po úspešnej registrácii získate prihlasovacie údaje k RabbitMQ. Pripojenie prebieha cez AMQPS (TLS, port 5671).
4.1 Queues a exchanges
Musíte pracovať s týmito frontami a výmennými miestami:
| Názov | Typ | Účel |
|---|---|---|
clouderp-delivery-queue.MYERP.{TenantId} | Direct queue | Príjem správ z QHUB do vášho systému |
clouderp.sync | Fanout exchange | Odosielanie správ z vášho systému do QHUB |
clouderp.sync.response | Fanout exchange | Odosielanie odpovede o výsledku spracovania |
clouderp.delivery | Direct exchange | Zdrojová exchange, z ktorej QHUB plní delivery queue |
4.2 Oprávnenia vášho RabbitMQ účtu
Váš účet má oprávnenia:
| Operácia | Zdroj |
|---|---|
| Configure + Read | clouderp-delivery-queue.MYERP.{TenantId} |
| Write | clouderp.sync (na odosielanie dokumentov do Peppolu) |
| Write | clouderp.sync.response (na odosielanie response) |
4.3 Deklarácia vašej delivery queue
Vašu delivery queue musí deklarovať váš systém (nie QHUB). Deklarujte ju pred prvým consume:
Meno: clouderp-delivery-queue.MYERP.{TenantId}
Durable: true
Auto-delete: false
Arguments:
x-dead-letter-exchange: clouderp.dlq (odporúčané)Binding na exchange:
Exchange: clouderp.delivery
Type: direct
Routing key: MYERP.{TenantId}5. Prijímanie dokumentov z Peppolu
Keď APEX prijme Peppol dokument adresovaný vášmu klientovi, QHUB ho doručí na vašu delivery queue ako správu SynchronizationMessage.
5.1 Tok prijímania
Peppol odosielateľ
→ AS4 → APEX
→ QHUB (SynchronizationMessage, EntityType=PeppolDocument)
→ clouderp-delivery-queue.MYERP.{TenantId}
→ VÁŠ KONZUMER
→ spracujete UBL XML (importujete faktúru)
→ SynchronizationResponseMessage → clouderp.sync.response5.2 Čo dostanete – SynchronizationMessage
Správa prichádza ako JSON objekt (pozri sekciu 7):
{
"TransactionId": 123456,
"EventType": 1,
"EntityType": 100,
"RoutingKey": "MYERP.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"OwnerTenantId": "apex-tenant-uuid",
"OwnerSystem": "APEX",
"DeliverySystem": "MYERP",
"DeliveryTenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"SyncKey": "550e8400-e29b-41d4-a716-446655440000",
"SchemaVersion": "1.0.0",
"Environment": "prod",
"DomainSyncVersion": 1,
"OccurredAt": "2026-04-16T10:30:00+02:00",
"Data": "{\"SenderParticipantId\":\"0245:2024000001\",\"ReceiverParticipantId\":\"0245:9999999999\",\"DocumentTypeId\":\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1\",\"ProcessId\":\"urn:fdc:peppol.eu:2017:poacc:billing:01:1.0\",\"UblDocument\":\"<?xml version=\\\"1.0\\\"...>\",\"As4MessageId\":\"uuid@peppol\",\"ReceivedAt\":\"2026-04-16T10:29:58+02:00\"}",
"IgnoredProperties": null
}Pole Data je JSON string (serializovaný ešte raz) obsahujúci entitu PeppolDocument. Je nutné ho deserializovať zvlášť.
5.3 Čo musíte urobiť po prijatí
- Deserializujte
DatanaPeppolDocument. - Skontrolujte
DeliverySystemaDeliveryTenantId– mali by zodpovedať vášmu systému. - Importujte
UblDocument(UBL 2.1 XML) do vášho systému. - Odošlite odpoveď
SynchronizationResponseMessagena exchangeclouderp.sync.response.
5.4 Odoslanie odpovede (povinné)
Po každom spracovaní správy musíte odoslať odpoveď späť do QHUB. Bez odpovede QHUB nevie, či bola správa spracovaná.
{
"TransactionId": 123456,
"Result": {
"Success": true,
"ErrorCode": null,
"ErrorParams": null
}
}Pri chybe:
{
"TransactionId": 123456,
"Result": {
"Success": false,
"ErrorCode": "ImportFailed",
"ErrorParams": ["Duplicitná faktúra INV-2026-001"]
}
}Dôležité: Odpoveď odošlite vždy – aj pri chybe. Ak odpoveď neodošlete, QHUB môže správu po čase znovu doručiť (retry).
6. Odosielanie dokumentov do Peppolu
Ak chcete odoslať dokument (napr. faktúru) cez Peppol, odošlite SynchronizationMessage s entitou PeppolDocument do exchange clouderp.sync. QHUB správu doručí APEX-u, ktorý vykoná AS4 odoslanie.
6.1 Tok odosielania
VÁŠ SYSTÉM
→ SynchronizationMessage (EntityType=PeppolDocument)
→ clouderp.sync (fanout exchange)
→ QHUB Synchronization Service
→ APEX (clouderp-delivery-queue.APEX)
→ AS4 / HTTPS
→ Peppol príjemca
QHUB → SynchronizationResponseMessage → clouderp.sync.response
→ VÁŠ konzumer odpovede (voliteľné, ale odporúčané)6.2 Čo musíte odoslať
Odošlite SynchronizationMessage na exchange clouderp.sync (bez routing key – ide o fanout):
{
"TransactionId": 0,
"EventType": 1,
"EntityType": 100,
"RoutingKey": "MYERP.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"OwnerTenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"OwnerSystem": "MYERP",
"DeliverySystem": null,
"DeliveryTenantId": null,
"SyncKey": "novy-uuid-pre-tuto-spravu",
"SchemaVersion": "1.0.0",
"Environment": "prod",
"DomainSyncVersion": 1,
"OccurredAt": "2026-04-16T10:30:00+02:00",
"Data": "{\"SenderParticipantId\":\"0245:2024000001\",\"ReceiverParticipantId\":\"0245:9999999999\",\"DocumentTypeId\":\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1\",\"ProcessId\":\"urn:fdc:peppol.eu:2017:poacc:billing:01:1.0\",\"UblDocument\":\"<?xml version=\\\"1.0\\\"...>\"}",
"IgnoredProperties": null
}
TransactionId: 0– pri odosielaní ho nastavte na 0; QHUB pridelí vlastný TransactionId pri spracovaní.
SyncKey– vygenerujte nové UUID pre každú správu. Je to trvalý identifikátor entity v QHUB synchronizácii.
DomainSyncVersion– začnite od 1. Pri každej ďalšej zmene tej istej entity zvýšte o 1 (Lamport timestamp).
6.3 Sledovanie výsledku odoslania (voliteľné)
QHUB vám odpíše výsledok AS4 odoslania cez clouderp.sync.response. Ak chcete sledovať úspech/neúspech doručenia, musíte konzumovať odpovede.
Poznámka: Odpoveď od QHUB dostanete dvakrát – raz keď APEX zaradí dokument do fronty (optimistický úspech), a druhýkrát po skutočnom AS4 odoslaní. Záleží na tom, ako APEX implementuje callback.
7. Štruktúry správ
Všetky správy sú JSON s PascalCase názvami vlastností (kompatibilné s .NET serializáciou).
7.1 SynchronizationMessage
Správa používaná v oboch smeroch (odosielanie aj prijímanie).
| Pole | Typ | Popis | Povinné |
|---|---|---|---|
TransactionId | long | Identifikátor záznamu v QHUB transakčnej tabuľke | áno |
EventType | int | Typ udalosti: 1=CREATE, 2=UPDATE, 4=DELETE | áno |
EntityType | int | Typ entity: 100=PeppolDocument (TBD) | áno |
RoutingKey | string | Routing key príjemcu (MYERP.{TenantId}) | nie |
OwnerTenantId | string (UUID) | Tenant zdrojového systému | áno |
OwnerSystem | string | Kód zdrojového systému (napr. APEX, MYERP) | áno |
DeliverySystem | string | Kód cieľového systému (vypĺňa QHUB) | nie |
DeliveryTenantId | string (UUID) | Tenant cieľového systému (vypĺňa QHUB) | nie |
SyncKey | string (UUID) | Unikátny trvalý identifikátor entity | áno |
SchemaVersion | string | Verzia schémy entity (SemVer, napr. "1.0.0") | áno |
Environment | string | Prostredie: "prod", "test" | áno |
DomainSyncVersion | int | Verzia zmeny entity (Lamport timestamp, ≥1) | áno |
OccurredAt | ISO 8601 datetime | Čas vzniku udalosti vo zdrojovom systéme | áno |
Data | string (JSON) | Serializovaná entita PeppolDocument | áno |
IgnoredProperties | string[] | Vlastnosti, ktoré cieľ nemá ignorovať (vypĺňa QHUB) | nie |
7.2 SynchronizationResponseMessage
Odpoveď, ktorú váš systém odošle po spracovaní prijatej správy.
| Pole | Typ | Popis |
|---|---|---|
TransactionId | long | Skopírované z prijatej SynchronizationMessage |
Result.Success | boolean | true = spracovanie prebehlo úspešne |
Result.ErrorCode | string | Kód chyby (ak Success=false) |
Result.ErrorParams | string[] | Doplňujúce parametre chyby |
8. Entita PeppolDocument
Entita, ktorá sa prenáša v poli Data správy SynchronizationMessage.
8.1 Schéma (verzia 1.0.0)
| Pole | Typ | Povinné | Business Key | Popis |
|---|---|---|---|---|
SenderParticipantId | string | áno | nie | Peppol ID odosielateľa. Formát: {schemeId}:{id} |
ReceiverParticipantId | string | áno | nie | Peppol ID príjemcu. Formát: {schemeId}:{id} |
DocumentTypeId | string | áno | nie | Peppol Document Type URN (viď prílohu A) |
ProcessId | string | áno | nie | Peppol Process URN (viď prílohu A) |
UblDocument | string | áno | nie | Kompletné UBL 2.1 XML ako string |
As4MessageId | string | nie | áno | AS4 MessageId (prítomné pri inbound správach) |
ReceivedAt | ISO 8601 | nie | nie | Čas príjmu z Peppol siete (inbound) |
8.2 Príklad entity
{
"SenderParticipantId": "0245:2024000001",
"ReceiverParticipantId": "0245:9999999999",
"DocumentTypeId": "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1",
"ProcessId": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0",
"UblDocument": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Invoice xmlns=\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2\">...</Invoice>",
"As4MessageId": "abc123@receiver.peppol.ap",
"ReceivedAt": "2026-04-16T10:29:58+02:00"
}8.3 Peppol Participant ID formáty (najčastejšie)
| Krajina | Schéma | Príklad |
|---|---|---|
| Slovensko (DIČ) | 0245 | 0245:2024000001 |
| Česká republika | 0060 | 0060:12345678 |
| GLN | 0088 | 0088:1234567890123 |
9. Chybové kódy a spracovanie chýb
9.1 Chyby registrácie
| Kód | Príčina | Riešenie |
|---|---|---|
RegCodeNotFound | Neplatný alebo neexistujúci registračný kľúč | Vyžiadajte nový kľúč od ASOL admina |
RegCodeExpired | Kľúč expiroval (>24h) | Vyžiadajte nový kľúč |
SystemMismatch | ParticipantSystem nesedí | Skontrolujte SystemCode |
TenantMismatch | ParticipantTenantId nesedí | Skontrolujte TenantId |
9.2 Chyby synchronizácie (QHUB)
Ak QHUB nedokáže spracovať vašu správu, nastaví stav na Failed a zaznamená:
| Kód | Príčina |
|---|---|
EventTypeMissing / EventTypeWrong | Neplatný EventType |
EntityDataMissing | Chýba pole Data pri CREATE/UPDATE |
TenantIdMissing | Chýba OwnerTenantId |
SchemaVersionMissing | Chýba SchemaVersion |
DomainSyncVersionWrong | DomainSyncVersion < 1 |
DeliverySystemWrong | DeliverySystem nezodpovedá registrácii |
9.3 Vaše chybové kódy (odosielané v odpovedi)
V SynchronizationResponseMessage.Result.ErrorCode môžete použiť ľubovoľný string kód (odporúčame angličtinu, bez medzier). Príklady:
| Kód | Kedy použiť |
|---|---|
ImportFailed | Nepodarilo sa importovať dokument do vášho systému |
DuplicateDocument | Dokument s týmto ID už existuje |
ValidationFailed | UBL XML zlyhalo vašu biznis validáciu |
UnsupportedDocumentType | Typ dokumentu váš systém nepodporuje |
UnexpectedError | Nečakaná chyba |
9.4 Dead-Letter Queue
Ak správu nedokážete spracovať ani po opakovaných pokusoch, QHUB ju presunie do Dead-Letter Queue. ASOL admin dostane notifikáciu. Kontaktujte ASOL support na riešenie.
10. Príklady implementácie
10.1 Java (Spring AMQP)
Konfigurácia
@Configuration public class QhubRabbitConfig { @Value("${qhub.rabbitmq.host}") private String host; @Value("${qhub.rabbitmq.port:5671}") private int port; @Value("${qhub.rabbitmq.username}") private String username; @Value("${qhub.rabbitmq.password}") private String password; @Value("${myapp.tenant-id}") private String tenantId; @Bean public ConnectionFactory qhubConnectionFactory() { CachingConnectionFactory cf = new CachingConnectionFactory(host, port); cf.setUsername(username); cf.setPassword(password); cf.setVirtualHost("/"); cf.getRabbitConnectionFactory().useSslProtocol(); // TLS povinné return cf; } @Bean public RabbitTemplate qhubRabbitTemplate(ConnectionFactory qhubConnectionFactory) { RabbitTemplate rt = new RabbitTemplate(qhubConnectionFactory); rt.setMessageConverter(new Jackson2JsonMessageConverter()); return rt; } @Bean public Queue myDeliveryQueue() { return QueueBuilder .durable("clouderp-delivery-queue.MYERP." + tenantId) .withArgument("x-dead-letter-exchange", "clouderp.dlq") .build(); } @Bean public DirectExchange deliveryExchange() { return new DirectExchange("clouderp.delivery", true, false); } @Bean public Binding myDeliveryBinding(Queue myDeliveryQueue, DirectExchange deliveryExchange) { return BindingBuilder.bind(myDeliveryQueue) .to(deliveryExchange) .with("MYERP." + tenantId); } }
Konzumer (prijímanie)
@Slf4j @Component @RequiredArgsConstructor public class PeppolDocumentConsumer { private static final int PEPPOL_DOCUMENT_ENTITY_TYPE = 100; // dohodnuté s ASOL private final ObjectMapper objectMapper; private final RabbitTemplate qhubRabbitTemplate; private final MyInvoiceImportService importService; // vaša biznis logika @RabbitListener(queues = "#{myDeliveryQueue.name}") public void onMessage(SynchronizationMessage message) { if (message.getEntityType() != PEPPOL_DOCUMENT_ENTITY_TYPE) { return; // neznáma entita, ignorovať } SyncResult result; try { PeppolDocument doc = objectMapper.readValue( message.getData(), PeppolDocument.class); // Importujte UBL XML do vášho systému importService.importInvoice(doc.getUblDocument(), doc.getSenderParticipantId()); result = new SyncResult(true, null, null); } catch (DuplicateDocumentException e) { log.warn("Duplicate document, transactionId={}", message.getTransactionId()); result = new SyncResult(false, "DuplicateDocument", new String[]{e.getMessage()}); } catch (Exception e) { log.error("Failed to process PeppolDocument, transactionId={}", message.getTransactionId(), e); result = new SyncResult(false, "UnexpectedError", new String[]{e.getMessage()}); } // Vždy odošlite odpoveď sendResponse(message.getTransactionId(), result); } private void sendResponse(long transactionId, SyncResult result) { SynchronizationResponseMessage response = new SynchronizationResponseMessage(transactionId, result); qhubRabbitTemplate.convertAndSend("clouderp.sync.response", "", response); } }
Odosielanie dokumentu
@Slf4j @Component @RequiredArgsConstructor public class PeppolDocumentSender { private static final int PEPPOL_DOCUMENT_ENTITY_TYPE = 100; private static final int EVENT_TYPE_CREATE = 1; private static final String SCHEMA_VERSION = "1.0.0"; private final RabbitTemplate qhubRabbitTemplate; private final ObjectMapper objectMapper; @Value("${myapp.system-code:MYERP}") private String systemCode; @Value("${myapp.tenant-id}") private String tenantId; /** * Odošle UBL XML dokument do QHUB → APEX → Peppol. */ public void send(String senderParticipantId, String receiverParticipantId, String documentTypeId, String processId, String ublXml) throws Exception { PeppolDocument doc = new PeppolDocument(); doc.setSenderParticipantId(senderParticipantId); doc.setReceiverParticipantId(receiverParticipantId); doc.setDocumentTypeId(documentTypeId); doc.setProcessId(processId); doc.setUblDocument(ublXml); SynchronizationMessage msg = new SynchronizationMessage(); msg.setEventType(EVENT_TYPE_CREATE); msg.setEntityType(PEPPOL_DOCUMENT_ENTITY_TYPE); msg.setRoutingKey(systemCode + "." + tenantId); msg.setOwnerSystem(systemCode); msg.setOwnerTenantId(tenantId); msg.setSyncKey(UUID.randomUUID().toString()); msg.setSchemaVersion(SCHEMA_VERSION); msg.setEnvironment("prod"); msg.setDomainSyncVersion(1); msg.setOccurredAt(OffsetDateTime.now()); msg.setData(objectMapper.writeValueAsString(doc)); qhubRabbitTemplate.convertAndSend("clouderp.sync", "", msg); log.info("Document sent to QHUB: sender={}, receiver={}", senderParticipantId, receiverParticipantId); } }
10.2 .NET (MassTransit / RabbitMQ.Client)
Konfigurácia MassTransit
services.AddMassTransit(config => { config.AddConsumer<PeppolDocumentConsumer>(); config.UsingRabbitMq((ctx, cfg) => { cfg.Host(configuration["Qhub:RabbitMq:Host"], "/", h => { h.Username(configuration["Qhub:RabbitMq:Username"]); h.Password(configuration["Qhub:RabbitMq:Password"]); h.UseSsl(s => s.Protocol = SslProtocols.Tls12); }); // Konfigurácia odosielania (sync queue) cfg.Message<SynchronizationMessage>(p => p.SetEntityName("clouderp.sync")); // Konfigurácia odposielania odpovede cfg.Message<SynchronizationResponseMessage>(p => p.SetEntityName("clouderp.sync.response")); // Delivery queue s binding na exchange cfg.ReceiveEndpoint( $"clouderp-delivery-queue.MYERP.{tenantId}", e => { e.ConfigureConsumeTopology = false; e.UseMessageRetry(r => r.Immediate(1)); e.Bind("clouderp.delivery", x => { x.RoutingKey = $"MYERP.{tenantId}"; x.ExchangeType = ExchangeType.Direct; }); e.ConfigureConsumer<PeppolDocumentConsumer>(ctx); }); }); });
Konzumer
public class PeppolDocumentConsumer : IConsumer<SynchronizationMessage> { private const int PeppolDocumentEntityType = 100; private readonly IInvoiceImportService _importService; private readonly IPublishEndpoint _publishEndpoint; public PeppolDocumentConsumer(IInvoiceImportService importService, IPublishEndpoint publishEndpoint) { _importService = importService; _publishEndpoint = publishEndpoint; } public async Task Consume(ConsumeContext<SynchronizationMessage> context) { var message = context.Message; if (message.EntityType != PeppolDocumentEntityType) return; SyncResult result; try { var doc = JsonSerializer.Deserialize<PeppolDocument>(message.Data!); await _importService.ImportAsync(doc.UblDocument, doc.SenderParticipantId); result = SyncResult.Ok(); } catch (DuplicateDocumentException ex) { result = SyncResult.Fail("DuplicateDocument", new[] { ex.Message }); } catch (Exception ex) { result = SyncResult.Fail("UnexpectedError", new[] { ex.Message }); } await _publishEndpoint.Publish( new SynchronizationResponseMessage(message.TransactionId, result)); } }
Odosielanie dokumentu
public class PeppolDocumentSender { private const int PeppolDocumentEntityType = 100; private const string SchemaVersion = "1.0.0"; private readonly IPublishEndpoint _bus; private readonly string _systemCode; private readonly string _tenantId; public PeppolDocumentSender(IPublishEndpoint bus, IConfiguration config) { _bus = bus; _systemCode = config["MyApp:SystemCode"]!; _tenantId = config["MyApp:TenantId"]!; } public async Task SendAsync(string senderParticipantId, string receiverParticipantId, string documentTypeId, string processId, string ublXml, CancellationToken ct = default) { var doc = new PeppolDocument { SenderParticipantId = senderParticipantId, ReceiverParticipantId = receiverParticipantId, DocumentTypeId = documentTypeId, ProcessId = processId, UblDocument = ublXml }; var message = new SynchronizationMessage { EventType = 1, // CREATE EntityType = PeppolDocumentEntityType, RoutingKey = $"{_systemCode}.{_tenantId}", OwnerSystem = _systemCode, OwnerTenantId = _tenantId, SyncKey = Guid.NewGuid().ToString(), SchemaVersion = SchemaVersion, Environment = "prod", DomainSyncVersion = 1, OccurredAt = DateTimeOffset.UtcNow, Data = JsonSerializer.Serialize(doc) }; await _bus.Publish(message, ct); } }
10.3 Python (pika)
import json import ssl import uuid from datetime import datetime, timezone import pika QHUB_HOST = "rmq.qcloud.assecosoldevsk.com" QHUB_PORT = 5671 QHUB_USER = "myerp_tenant_abc123" QHUB_PASS = "••••••••" MY_SYSTEM_CODE = "MYERP" MY_TENANT_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ENTITY_TYPE = 100 # PeppolDocument DELIVERY_QUEUE = f"clouderp-delivery-queue.{MY_SYSTEM_CODE}.{MY_TENANT_ID}" SYNC_EXCHANGE = "clouderp.sync" RESPONSE_EXCHANGE = "clouderp.sync.response" def connect(): ssl_ctx = ssl.create_default_context() credentials = pika.PlainCredentials(QHUB_USER, QHUB_PASS) params = pika.ConnectionParameters( host=QHUB_HOST, port=QHUB_PORT, credentials=credentials, ssl_options=pika.SSLOptions(ssl_ctx, QHUB_HOST) ) return pika.BlockingConnection(params) # ── Prijímanie dokumentov ────────────────────────────────────────────────── def on_message(channel, method, properties, body): msg = json.loads(body) if msg.get("EntityType") != ENTITY_TYPE: channel.basic_ack(delivery_tag=method.delivery_tag) return transaction_id = msg["TransactionId"] try: doc = json.loads(msg["Data"]) ubl_xml = doc["UblDocument"] # --- vaša biznis logika --- import_invoice(ubl_xml, doc["SenderParticipantId"]) result = {"Success": True, "ErrorCode": None, "ErrorParams": None} except Exception as e: result = {"Success": False, "ErrorCode": "UnexpectedError", "ErrorParams": [str(e)]} response = {"TransactionId": transaction_id, "Result": result} channel.basic_publish( exchange=RESPONSE_EXCHANGE, routing_key="", body=json.dumps(response), properties=pika.BasicProperties(content_type="application/json") ) channel.basic_ack(delivery_tag=method.delivery_tag) def start_consuming(): conn = connect() channel = conn.channel() channel.queue_declare(queue=DELIVERY_QUEUE, durable=True, arguments={"x-dead-letter-exchange": "clouderp.dlq"}) channel.queue_bind(queue=DELIVERY_QUEUE, exchange="clouderp.delivery", routing_key=f"{MY_SYSTEM_CODE}.{MY_TENANT_ID}") channel.basic_consume(queue=DELIVERY_QUEUE, on_message_callback=on_message) channel.start_consuming() # ── Odosielanie dokumentov ────────────────────────────────────────────────── def send_document(sender_id, receiver_id, document_type_id, process_id, ubl_xml): doc = { "SenderParticipantId": sender_id, "ReceiverParticipantId": receiver_id, "DocumentTypeId": document_type_id, "ProcessId": process_id, "UblDocument": ubl_xml, } message = { "TransactionId": 0, "EventType": 1, "EntityType": ENTITY_TYPE, "RoutingKey": f"{MY_SYSTEM_CODE}.{MY_TENANT_ID}", "OwnerSystem": MY_SYSTEM_CODE, "OwnerTenantId": MY_TENANT_ID, "SyncKey": str(uuid.uuid4()), "SchemaVersion": "1.0.0", "Environment": "prod", "DomainSyncVersion": 1, "OccurredAt": datetime.now(timezone.utc).isoformat(), "Data": json.dumps(doc), } conn = connect() channel = conn.channel() channel.basic_publish( exchange=SYNC_EXCHANGE, routing_key="", body=json.dumps(message), properties=pika.BasicProperties(content_type="application/json", delivery_mode=2) # persistent ) conn.close() def import_invoice(ubl_xml: str, sender_id: str): # Vaša implementácia importu pass
11. Časté otázky
Ako zistím, že mi prišla faktúra?
Nastavte konzumera na delivery queue clouderp-delivery-queue.MYERP.{TenantId}. Správa s EntityType=100 a OwnerSystem=APEX je dokument z Peppol siete.
Môžem odoslať aj iné typy dokumentov ako faktúra?
Áno. UblDocument môže obsahovať akýkoľvek Peppol BIS 3.0 dokument (kreditnú nótu, objednávku, …). Stačí správne nastaviť DocumentTypeId a ProcessId (viď prílohu A).
Čo ak odoslanie cez Peppol zlyhá?
APEX automaticky zopakuje odoslanie (exponenciálny backoff, max 5 pokusov). Výsledok dostanete cez SynchronizationResponseMessage so Success=false a príslušným ErrorCode.
Je UBL XML validované?
Áno, APEX validuje každý dokument pred odoslaním (XSD schéma, EN 16931 Schematron, Peppol BIS 3.0 pravidlá). Ak dokument nie je validný, dostanete Success=false.
Aký je Peppol schéma kód pre Slovensko?
Slovenský IČO v Peppol sieti používa schému 0245, teda ID má formát 0245:{DIČ}, napr. 0245:2024000001.
Ako overím, či je príjemca zaregistrovaný v Peppol sieti?
APEX to overuje automaticky (SMP lookup) pred odoslaním. Ak príjemca nie je registrovaný, dostanete chybu PARTICIPANT_NOT_FOUND.
Musím zachovávať poradie správ?
Pre samostatné dokumenty (každá faktúra je nová správa) nie je poradie kritické. Ak posielate viacero verzií toho istého dokumentu (UPDATE operácie), použite DomainSyncVersion na riešenie konfliktov.
Príloha A: Peppol Document Type identifikátory
Najčastejšie typy dokumentov
| Typ dokumentu | DocumentTypeId | ProcessId |
|---|---|---|
| Faktúra (Invoice) | urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1 | urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 |
| Dobropis (Credit Note) | urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2::CreditNote##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1 | urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 |
| MLR (Message Level Response) | urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##urn:fdc:peppol.eu:2017:pracc:t111:ver1.0::2.1 | urn:fdc:peppol.eu:2017:pracc:messagelevels:01:1.0 |
| IMR (Invoice Message Response) | urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##urn:fdc:peppol.eu:2017:poacc:billing:08:1.0::2.1 | urn:fdc:peppol.eu:2017:poacc:billing:08:1.0 |
Príloha B: Kontrolný zoznam integrácie
- Získaný
SystemCodeaTenantIdod ASOL - Získaný URL a API kľúč k QHUB Registration API
- Odoslaný popis entity
PeppolDocumentna Registration API - Od ASOL admina prijatý jednorazový registračný kľúč
- Zavolaný endpoint
POST /api/v1/Registration/Validate - RabbitMQ credentials bezpečne uložené
- Deklarovaná delivery queue s bindingom
- Implementovaný konzumer (prijímanie)
- Konzumer odošle
SynchronizationResponseMessagepo každom spracovaní - Implementované odosielanie (ak je potrebné)
- Otestovaný inbound tok: testovacia AS4 správa → vaša aplikácia
- Otestovaný outbound tok: vaša aplikácia → APEX → Peppol test prostredie
Príloha C: Kontakty a podpora
| Typ požiadavky | Kontakt |
|---|---|
| Onboarding (SystemCode, TenantId, API kľúč) | ASOL integračný tím |
| Technická podpora QHUB | ASOL DevOps / Cloud tím |
| Technická podpora Peppol (certifikáty, SMP registrácia) | ASOL Peppol tím / váš SMP provider |
| Nahlásenie chyby | Customer Portal |