10. Invoice Lifecycle
10.1 Invoice Pipeline Services
| Service | Responsibility |
|---|---|
InvoiceProcessFlowService | Top-level orchestrator — GenerateAndSubmit(dto) calls the onboarding-status check, generation, UBL building, submission, and file saving in sequence. No try/catch of its own; lets exceptions propagate |
InvoiceGeneraterService | Runs validation, then produces the signed invoice via the signer |
InvoiceUblService | Maps UblInvoiceDto → InvoiceType (XSD-generated UBL model), stamps ICV/PIH, supplier party from tenant settings |
InvoiceValidaterService | Three-tier validation: XSD schema, EN 16931 FluentValidation rules, KSA-specific rules. Has one dead method, SDK_Validation (commented out) |
InvoiceSignerService | Produces the XAdES-signed XML (XML-DSig, ECDSA secp256k1, SHA-256 digest = invoice hash) and the QR TLV payload |
InvoiceSubmissionService | Routes Standard → clearance (with 303 fallback to reporting) / Simplified → reporting; maps ZATCA HTTP responses to SubmissionStatus |
InvoiceSaverService | Writes the signed XML to ./Files/Invoices/Signed/{...}.xml via raw System.IO.StreamWriter. On Rejected, the file is still written but FileName is nulled — unreachable via the download endpoint |
See Chapter 4 — Runtime Flow for the full sequence diagram.
10.2 Outcome States
SubmissionStatus has exactly three values:
| Status | Meaning |
|---|---|
Accepted | ZATCA returned 200/202 with no warnings |
AcceptedWithWarnings | ZATCA returned 202 with a ValidationResult containing warnings |
Rejected | ZATCA returned 400, or the clearance/reporting call otherwise failed validation |
There is no Pending, Failed, or RetryCount state — a submission either completes synchronously with one
of the three outcomes above, or the request throws (propagating to an HTTP 500) if something outside the
modeled ZATCA response codes goes wrong.
10.3 Previous Invoice Hash (PIH) Chain
Each invoice's SHA-256 digest (computed as part of XML-DSig signing) becomes the next invoice's
Previous-Invoice-Hash, stamped into the UBL model by InvoiceUblService.AddPreviousInvoiceHash. This is the
hash-chaining ZATCA Phase 2 requires between consecutive invoices from the same device.
10.4 What Happens After a Submission Completes
- On success (
Accepted/AcceptedWithWarnings): the signed XML is written to./Files/Invoices/Signed/{...}.xml, retrievable via the (now path-traversal-hardened, see Chapter 7)download-fileendpoint. - On rejection: the file is still written, but its
FileNameis nulled in the result — meaning a rejected invoice's XML is not reachable through the download endpoint even though it exists on disk. - Nothing persists the outcome against the originating invoice entity.
SalesInvoice/PurchaseInvoicerecords in the ERP have no field recording whether, or how, they were submitted to ZATCA — because no ERP workflow calls this pipeline in the first place (see Chapter 1 — Overview and Chapter 2 — Architecture). - No notification fires, regardless of outcome. See Chapter 12 — Known Limitations.
- No retry occurs on rejection or on an unexpected failure — the caller that made the original synchronous HTTP request is responsible for deciding whether and how to retry.
10.5 Local File Archive — No Lifecycle Management
The local-filesystem invoice archive (./Files/Invoices/Signed/) and debug-output files
(clearanceRequestModel.json, invoice.xml, overwritten on every call) have no cleanup or archival job —
these accumulate, or get silently overwritten, with no lifecycle management of any kind.
