PHPackages                             meita/zatca-engine - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. meita/zatca-engine

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

meita/zatca-engine
==================

Framework-agnostic ZATCA Phase 2 engine for generating UBL 2.1 e-invoices (XML, hashing, signing, QR, chaining).

3.0.0(5mo ago)034MITPHPPHP ^8.1

Since Dec 13Pushed 5mo agoCompare

[ Source](https://github.com/EngMEita/zatca-engine)[ Packagist](https://packagist.org/packages/meita/zatca-engine)[ RSS](/packages/meita-zatca-engine/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (19)Used By (0)

meita/zatca-engine
==================

[](#meitazatca-engine)

A framework-agnostic PHP engine for generating ZATCA Phase 2 compliant e-invoices (UBL 2.1) with built-in validation, hashing, signing hooks, QR (TLV), and invoice chaining (ICV/PIH).

Goal: produce XML that passes the ZATCA simulator without XSD errors or BR warnings and is ready for Phase 2 clearance/reporting flows.

---

Requirements
------------

[](#requirements)

- PHP 8.1+
- Extensions: ext-dom, ext-openssl
- No other runtime dependencies

---

Installation
------------

[](#installation)

```
composer require meita/zatca-engine
```

---

End-to-End Quick Start (Plain PHP)
----------------------------------

[](#end-to-end-quick-start-plain-php)

```
require __DIR__.'/vendor/autoload.php';

use Meita\ZatcaEngine\Core\Context;
use Meita\ZatcaEngine\Core\Engine;

// 1) Company context (multi-tenant friendly)
$ctx = Context::fromArray([
  'company_key' => 'dania',
  'currency'    => 'SAR',   // required by ZATCA
  'tax_rate'    => 15,      // default VAT percent

  'seller' => [
    'name' => 'DANIA AIR CONTROL SYSTEM FACTORY',
    'vat'  => '310123456700003',
    'crn'  => '2341682066', // will be output once with schemeID=CRN
    'address' => [
      'street'      => 'Sudair Street',
      'building_no' => '4230',    // 4 digits (KSA-17)
      'city'        => 'Riyadh',
      'district'    => 'Al Nadheem',
      'postal_code' => '12987',
      'country'     => 'SA',
    ],
  ],
]);

// 2) Build invoice
$engine = new Engine($ctx);

$invoice = $engine->invoice()
  ->standard()                // or ->simplified()
  ->number('INV-2025-0001')
  ->counter(1)                // ICV (KSA-16)
  ->previousHash(null)        // PIH (KSA-13); null -> zeros for first invoice
  ->issueAt('2025-12-13', '09:26:57')
  ->supplyDate('2025-12-12')  // mandatory for standard
  ->buyer(
      'MohEita Company',
      [
        'street'      => 'King Fahd Rd',
        'building_no' => '1234',   // 4 digits (KSA-18)
        'city'        => 'Riyadh',
        'district'    => 'Al Olaya',
        'postal_code' => '11564',
        'country'     => 'SA',
      ],
      '319123456700003' // Buyer VAT (BT-48); if missing, set buyerId instead
  )
  ->addItem('Air Grill 25x25', 2, 100.00)
  ->addItem('Air Grill 30x30', 5, 120.00)
  ->addItem('Air Grill 50x50', 17, 165.15)
  ->build();

// 3) Outputs
$xml  = $invoice->toXml();      // UBL 2.1 XML (ordered for XSD)
$hash = $invoice->hash();       // SHA-256 over canonicalized XML (hex)
$qr   = $invoice->qrBase64();   // TLV QR as base64 (Phase 2 tags)

file_put_contents('invoice.xml', $xml);
file_put_contents('invoice.hash.txt', $hash);
file_put_contents('invoice.qr.txt', $qr);
```

---

Worked Example (with comments and masked IDs)
---------------------------------------------

[](#worked-example-with-comments-and-masked-ids)

```
require __DIR__.'/vendor/autoload.php';

use Meita\ZatcaEngine\Core\Context;
use Meita\ZatcaEngine\Core\Engine;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel;
use Endroid\QrCode\QrCode;
use Endroid\QrCode\Writer\PngWriter;

$tz  = new DateTimeZone('Asia/Riyadh');
$now = new DateTimeImmutable('now', $tz);

// Dynamic timestamps (override with env vars if needed)
$issueDate  = getenv('INVOICE_DATE') ?: $now->format('Y-m-d');
$issueTime  = getenv('INVOICE_TIME') ?: $now->format('H:i:s');
$supplyDate = getenv('SUPPLY_DATE') ?: $issueDate; // mandatory for standard

$seller = [
    'name' => 'MohEita Systems LLC',
    'vat'  => '310*********0003', // masked example VAT
    'crn'  => '2341****66',       // masked example CRN (schemeID=CRN)
    'address' => [
        'street'      => 'Sudair St',
        'building_no' => '4230',   // 4 digits (KSA-17)
        'city'        => 'Riyadh',
        'district'    => 'Azzahra',
        'postal_code' => '12987',
        'country'     => 'SA',
    ],
];

$buyer = [
    'name' => 'Dania Air Control System Factory',
    'vat'  => '319*********0003', // buyer VAT; or set buyer_id instead
    'buyer_id' => null,
    'address' => [
        'street'      => 'King Fahd Rd',
        'building_no' => '1250',   // 4 digits (KSA-18)
        'city'        => 'Riyadh',
        'district'    => 'Al Olaya',
        'postal_code' => '11564',
        'country'     => 'SA',
    ],
];

$items = [
    ['name' => 'Air Grill 25x25', 'qty' => 2,  'price' => 100.00],
    ['name' => 'Air Grill 30x30', 'qty' => 5,  'price' => 120.00],
    ['name' => 'Air Grill 50x50', 'qty' => 17, 'price' => 165.15],
    // ...add more lines as needed
];

// Context (SAR enforced; 15% VAT)
$ctx = Context::fromArray([
    'company_key' => 'dania',
    'currency'    => 'SAR',
    'tax_rate'    => 15,
    'seller'      => $seller,
]);

$engine = new Engine($ctx);

// These can be computed from storage/history for chaining
$invoiceNumber = 'INV-2025-0008';
$counter       = 8;     // ICV (KSA-16)
$previousHash  = null;  // PIH (KSA-13); null => zeros for first

$builder = $engine->invoice()
    ->standard()
    ->number($invoiceNumber)
    ->counter($counter)
    ->issueAt($issueDate, $issueTime)
    ->supplyDate($supplyDate) // required for standard invoices
    ->buyer(
        $buyer['name'],
        $buyer['address'],
        $buyer['vat'],
        $buyer['buyer_id']
    )
    ->previousHash($previousHash);

foreach ($items as $item) {
    $builder->addItem($item['name'], $item['qty'], $item['price']);
}

$invoice = $builder->build();

// Outputs
$xml  = $invoice->toXml();
$hash = $invoice->hash();
$qr   = $invoice->qrBase64(); // TLV-encoded base64

// Optional: QR PNG rendering
$writer = new PngWriter();
$qrImg = $writer->write(
    QrCode::create($qr)
        ->setEncoding(new Encoding('UTF-8'))
        ->setErrorCorrectionLevel(ErrorCorrectionLevel::High)
        ->setSize(320)
        ->setMargin(10)
);
$qrImg->saveToFile(__DIR__.'/invoices/'.$invoiceNumber.'.qr.png');

file_put_contents(__DIR__.'/invoices/'.$invoiceNumber.'.xml', $xml);
file_put_contents(__DIR__.'/invoices/'.$invoiceNumber.'.hash.txt', $hash);
file_put_contents(__DIR__.'/invoices/'.$invoiceNumber.'.qr.txt', $qr);

echo "Invoice generated: {$invoiceNumber}\n";
```

Hints to avoid warnings in the example above:

- Fill every seller/buyer address field (street, 4-digit building\_no, city, district, postal\_code, country).
- Always set supplyDate for standard invoices.
- Provide buyer VAT or buyer ID (scheme NAT) for standard invoices.
- Keep TaxCurrencyCode equal to DocumentCurrencyCode (SAR) unless you must differ; if you change it, BG-23 (TaxSubtotal) will be omitted by design.
- Maintain chaining: increment counter (ICV) and feed previousHash from the prior invoice hash.

---

Data Checklist (avoid common warnings/errors)
---------------------------------------------

[](#data-checklist-avoid-common-warningserrors)

- Currency: SAR (BR-KSA-68).
- Seller identity: name, vat, single crn (alphanumeric, schemeID=CRN).
- Seller address: street, building\_no (4 digits), city, district, postal\_code, country.
- Standard invoices:
    - buyerName present.
    - buyerVat or buyerId (scheme NAT by default) provided.
    - supplyDate provided (KSA-5).
    - Buyer address when country = SA: street, building\_no (4 digits), city, district, postal\_code, country.
- Items: quantity &gt; 0, price &gt;= 0, name present, VAT category (defaults to S), unit code (defaults to EA).
- Chaining: counter (ICV) required; previousHash (PIH) required (zeros auto for first).

---

Builder Reference
-----------------

[](#builder-reference)

- standard() / simplified()
- number(string $id)
- counter(string|int $icv) // KSA-16
- previousHash(?string $hash) // KSA-13, hex64 or base64; null =&gt; zeros
- issueAt(string $date, string $time)
- supplyDate(string $date) // required for standard
- buyer(string $name, array $address, ?string $vat = null, ?string $buyerId = null)
    - Address keys: street, building\_no, city, district, postal\_code, country
- addItem(string $name, float $qty, float $unitPrice, ?float $vatRate = null, string $vatCategory = 'S', string $unitCode = 'EA')

---

Output &amp; Validation Notes
-----------------------------

[](#output--validation-notes)

- XML ordering matches UBL 2.1 and ZATCA expectations:
    - Header -&gt; InvoicePeriod (for standard) -&gt; AdditionalDocumentReference (ICV/PIH) -&gt; Parties -&gt; Totals -&gt; Lines.
    - PostalAddress uses PostalZone then District; no CitySubdivisionName.
- Tax totals:
    - Always one document-level TaxTotal.
    - TaxSubtotal (BG-23) is included only when TaxCurrencyCode == DocumentCurrencyCode.
    - Line-level TaxTotal contains TaxAmount (KSA-11) and RoundingAmount = line gross (KSA-12).
- Totals use integer cents internally to avoid rounding drift (BR-CO-15).
- CRN is sanitized to alphanumeric before emitting with schemeID="CRN" (BR-KSA-08).

---

Common ZATCA Findings and How to Avoid Them
-------------------------------------------

[](#common-zatca-findings-and-how-to-avoid-them)

- BR-CO-18 (missing VAT breakdown): ensure TaxCurrencyCode matches DocumentCurrencyCode (or leave unset) so BG-23 is emitted.
- BR-KSA-EN16931-09 (TaxSubtotal present with different tax currency): if you set a different tax currency, no TaxSubtotal will be emitted; prefer same currency unless required.
- BR-KSA-15 (supply date missing): set supplyDate for standard invoices.
- BR-KSA-52/53 (line VAT and gross missing): keep line TaxTotal with TaxAmount and RoundingAmount (already handled).
- BR-KSA-08 (seller ID): provide one CRN, alphanumeric, via seller crn.
- BR-KSA-09 / BR-KSA-63 (address completeness): fill all address fields listed in the checklist; building numbers must be 4 digits.
- BR-KSA-F-06-C28 (district length): districts are auto-truncated to 127 chars; ensure non-empty when required.

---

Laravel Adapter (Optional)
--------------------------

[](#laravel-adapter-optional)

```
php artisan vendor:publish --tag=zatca-engine-config
```

```
use ZatcaEngine;

$xml = ZatcaEngine::company('dania')
  ->invoice()
  ->standard()
  ->number('INV-2025-0001')
  ->supplyDate('2025-12-12')
  ->buyer(
    'Customer',
    [
      'street' => 'Street',
      'building_no' => '1234',
      'city' => 'Riyadh',
      'district' => 'District',
      'postal_code' => '11564',
      'country' => 'SA',
    ],
    '319...'
  )
  ->addItem('Item A', 2, 100)
  ->build()
  ->toXml();
```

---

Troubleshooting Checklist
-------------------------

[](#troubleshooting-checklist)

- Verify all required seller/buyer fields are present (see Data Checklist).
- Keep TaxCurrencyCode aligned with DocumentCurrencyCode unless you must differ (then expect no TaxSubtotal).
- For first invoice in a chain, previousHash(null) to auto-fill zeros.
- Use 4-digit building\_no for both seller and buyer (when country is SA).
- Regenerate XML after any data change and re-run the ZATCA validator.

---

License
-------

[](#license)

MIT

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance73

Regular maintenance activity

Popularity8

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

18

Last Release

154d ago

Major Versions

1.2.1 → 2.0.02025-12-14

2.0.1 → 3.0.02025-12-15

### Community

Maintainers

![](https://www.gravatar.com/avatar/5915383ac10e7c148c23e142195199c8e7909acc2cda2003c26e688090956014?d=identicon)[EngMEita](/maintainers/EngMEita)

---

Top Contributors

[![EngMEita](https://avatars.githubusercontent.com/u/11925529?v=4)](https://github.com/EngMEita "EngMEita (18 commits)")

---

Tags

qr codeE-InvoicePHP LibraryZATCAubl

### Embed Badge

![Health badge](/badges/meita-zatca-engine/health.svg)

```
[![Health](https://phpackages.com/badges/meita-zatca-engine/health.svg)](https://phpackages.com/packages/meita-zatca-engine)
```

###  Alternatives

[chillerlan/php-qrcode

A QR Code generator and reader with a user-friendly API. PHP 8.4+

2.4k28.9M208](/packages/chillerlan-php-qrcode)[salla/zatca

A helper to generate the QR code and signed it for ZATCA e-invoicing

159416.7k2](/packages/salla-zatca)[lodash-php/lodash-php

A port of Lodash to PHP

527719.0k5](/packages/lodash-php-lodash-php)[amirezaeb/heroqr

A Powerful QR Code Management Library For PHP

9510.3k](/packages/amirezaeb-heroqr)[saleh7/php-zatca-xml

An unofficial PHP library for generating ZATCA Fatoora e-invoices. This library facilitates the creation of compliant e-invoices, QR Codes, and certificates, as well as the submission of e-invoices to ZATCA's servers. It provides developers with an easy-to-use, customizable, and robust toolkit to integrate and automate ZATCA e-invoicing processes in PHP applications.

5112.5k](/packages/saleh7-php-zatca-xml)[zxing/qr-reader

PHP qr code reader library

7542.7k1](/packages/zxing-qr-reader)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
