🍗 PCK ZRA Middleware Wiki

Invoice & Credit Note Flow

This page traces the complete lifecycle of a sale — and of a refund — through the middleware, step by step.

Flow 1: Invoice upload (the core flow)

POS Terminal
  │  POST /pckzra/api/taxint/pos/upload
  │  Headers: Authorization: Bearer <jwt>, Content-Type: application/json
  │  Body: StandardInvoicePayloadDto (Taxint format)
  ▼
RequestResponseLoggingFilter
  │  Generates transaction_id (UUID), logs raw request to api_transactions
  ▼
JwtAuthenticationFilter
  │  Extracts Bearer token; validates against Oracle IDM JWKS (when enabled)
  ▼
TaxintController.uploadInvoice()
  ├── 1. Extract SellerName (root SellerInfo → FolioInfo.SellerInfo)
  │        Missing → HTTP 400 "SellerName is required"
  ├── 2. Validate FiscalFolioId present
  │        Missing → HTTP 400 "FiscalFolioId is required"
  ├── 3. Look up store registration by SellerName
  │        Not found → HTTP 403 "Store not registered"
  │        Inactive  → HTTP 403 "Store is inactive"
  ├── 4. Tag the transaction with the store ID
  ├── 5. Convert Taxint → Chrilan (ConversionService)
  ├── 6. Submit to Chrilan: POST /invoice/create (per-store API key)
  ├── 7. Convert Chrilan response → Taxint, save FiscalInvoice record
  ├── 8. Log the response (status, body, timing)
  └── 9. Return UploadResponseDto to the POS (HTTP 200)

What the POS gets back

Response elementSourcePurpose
fiscalFolioNoChrilan rcptNoThe ZRA receipt number printed on the receipt
QR_CODE_URL / QR_CODE (base64)Chrilan qrCodeUrl, image fetched and encodedCustomer-verifiable QR code
sdcIdChrilan sdcIdSmart Device Controller ID
mrcNoChrilan mrcNoMRC number
rcptSignChrilan rcptSignDigital receipt signature
taxData / COLL_TAX1Chrilan taxDataTax breakdown for the receipt
intrlData, vsdcRcptPbctDateChrilanInternal data and publication date
BuyerHouseNo/Street/ProvincePass-through from the requestBuyer address for POS check printing

Flow 2: Credit note / refund

When a sale is reversed, ZRA requires the refund to reference the original invoice. The middleware handles this automatically:

POS sends invoice with FolioType = credit note indicator
  ▼
ConversionService
  ├── Detects refund from FolioHeaderInfo.FolioType
  ├── Looks up the original invoice:
  │     FiscalInvoiceRepository.findByFiscalFolioId(originalFolioId)
  ├── Extracts from the stored record:
  │     original receipt_number → refundNumber
  │     original sdc_id         → refundReceiptSDC
  └── Sets on the Chrilan request:
        refund = true
        refundNumber, refundReceiptSDC, refundResCode = "06"
Why fiscal_invoices exists Without the original rcptNo and sdcId, ZRA cannot link a refund to its sale. This is why every issued invoice is persisted locally: audit number, receipt number, SDC ID, store, TPIN, refund flag and buyer address fields.

Negative quantities and amounts on credit notes are converted to absolute values before submission.

Error handling matrix

ConditionHTTPNotes
Missing SellerName400Checked in all payload locations
Missing FiscalFolioId400Required for traceability
Store not registered / inactive403No fallback key — see Managing stores
Payload validation failure400Field-level errors returned as a list
Conversion failure (missing required data)400Logged to conversion_logs with mapping rules
Chrilan API error500Wrapped in ChrilanApiException with code/message/type and full body
Chrilan timeout50030-second timeout

Idempotency and retries

Timing budget

A typical successful upload completes in a few seconds: local validation and conversion are sub-second; the dominant cost is the Chrilan round trip (30s timeout) plus the QR code image fetch. Response times per transaction are visible in Reports.