Skip to main content
Version: Latest

10. Invoice Lifecycle

10.1 Invoice Pipeline Services

ServiceResponsibility
InvoiceProcessFlowServiceTop-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
InvoiceGeneraterServiceRuns validation, then produces the signed invoice via the signer
InvoiceUblServiceMaps UblInvoiceDtoInvoiceType (XSD-generated UBL model), stamps ICV/PIH, supplier party from tenant settings
InvoiceValidaterServiceThree-tier validation: XSD schema, EN 16931 FluentValidation rules, KSA-specific rules. Has one dead method, SDK_Validation (commented out)
InvoiceSignerServiceProduces the XAdES-signed XML (XML-DSig, ECDSA secp256k1, SHA-256 digest = invoice hash) and the QR TLV payload
InvoiceSubmissionServiceRoutes Standard → clearance (with 303 fallback to reporting) / Simplified → reporting; maps ZATCA HTTP responses to SubmissionStatus
InvoiceSaverServiceWrites 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:

StatusMeaning
AcceptedZATCA returned 200/202 with no warnings
AcceptedWithWarningsZATCA returned 202 with a ValidationResult containing warnings
RejectedZATCA 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-file endpoint.
  • On rejection: the file is still written, but its FileName is 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/PurchaseInvoice records 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.