Download the PHP package khaledhajsalem/zatca-php without Composer
On this page you can find all versions of the php package khaledhajsalem/zatca-php. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download khaledhajsalem/zatca-php
More information about khaledhajsalem/zatca-php
Files in khaledhajsalem/zatca-php
Package zatca-php
Short Description A comprehensive PHP package for ZATCA (Saudi Arabia e-invoicing) invoice processing, signing, and submission
License MIT
Homepage https://github.com/khaledhajsalem/zatca-php
Informations about the package zatca-php
ZATCA PHP Package
A PHP package for ZATCA (Saudi Arabia) Phase 2 e-invoicing. Handles XML generation (UBL 2.1), digital signing, QR codes, and API submission — all without database dependencies.
Table of Contents
- Requirements
- Installation
- Key Concepts
- How It Works (Lifecycle)
- Step 1: Generate CSR & Private Key
- Step 2: Get Your Certificate from ZATCA
- Step 3: Create & Submit an Invoice
- Invoice Types
- Credit & Debit Notes
- Data Reference
- Validation
- Error Handling
- Package Structure
- Testing
- Contributing
- License
Requirements
- PHP 8.0+
- OpenSSL extension
- Composer
Installation
Key Concepts
Before using this package, understand these ZATCA-specific terms:
| Term | What it means |
|---|---|
| Standard Invoice | B2B/B2G invoice. Must be cleared by ZATCA before you can send it to the buyer. Type name: 0100000. |
| Simplified Invoice | B2C invoice (e.g., retail receipt). Must be reported to ZATCA within 24 hours. Type name: 0200000. |
| Clearance | ZATCA validates and approves a Standard invoice in real-time. You get back a "cleared" XML. |
| Reporting | You send a Simplified invoice to ZATCA for record-keeping. No real-time approval needed. |
| PIH (Previous Invoice Hash) | SHA-256 hash of the previous invoice. Creates a tamper-proof chain. First invoice uses InvoiceData::FIRST_INVOICE_HASH, the base64 SHA-256 of '0' (BR-KSA-26). |
| ICV (Invoice Counter Value) | Sequential counter starting at 1. Must increment for every invoice. |
| CSR | Certificate Signing Request — you generate this and send it to ZATCA to get your signing certificate. |
| OTP | One-Time Password — ZATCA gives you this when you register your device on the Fatoora portal. |
Invoice Type Codes
| Code | Type | Method |
|---|---|---|
388 |
Tax Invoice | ->taxInvoice() |
381 |
Credit Note | ->creditNote() |
383 |
Debit Note | ->debitNote() |
386 |
Prepayment Invoice | ->prepaymentInvoice() |
Party Identification Schemes
Both seller and buyer support these identification types:
| Scheme ID | Description |
|---|---|
CRN |
Commercial Registration Number |
VAT |
VAT Number |
TIN |
Tax Identification Number |
NAT |
National ID |
IQA |
Iqama Number |
GCC |
GCC ID |
PAS |
Passport ID |
MOM |
MOMRAH License |
MLS |
MHRSD License |
SAG |
MISA License |
700 |
700 Number |
OTH |
Other ID |
How It Works (Lifecycle)
Here is the complete flow from setup to invoice submission:
Step 1: Generate CSR & Private Key
Do this once when setting up your system with ZATCA.
What happens next:
- Log in to the ZATCA Fatoora Portal
- Register your device — ZATCA gives you an OTP
- Use the OTP to request a compliance certificate (see Step 2)
Important: The OTP, compliance certificate and production certificate must all come from the same environment. For
simulation, generate the OTP from the Fatoora Simulation portal; sandbox/developer-portal credentials will be rejected with "You are not authorized to use this api endpoint".
Step 2: Get Your Certificate from ZATCA
Step 3: Create & Submit an Invoice
This is the main workflow you'll use for every invoice.
Simplified Tax Invoice (B2C)
Standard Tax Invoice (B2B)
The only difference from simplified is the invoice type — everything else is the same:
Invoice Types
Summary
| Type | Code | Name | Clearance? | Method |
|---|---|---|---|---|
| Standard Tax Invoice | 388 |
0100000 |
Yes (real-time) | ->standard()->taxInvoice() |
| Simplified Tax Invoice | 388 |
0200000 |
No (report within 24h) | ->simplified()->taxInvoice() |
| Standard Credit Note | 381 |
0100000 |
Yes | ->standard()->creditNote() |
| Simplified Credit Note | 381 |
0200000 |
No | ->simplified()->creditNote() |
| Standard Debit Note | 383 |
0100000 |
Yes | ->standard()->debitNote() |
| Simplified Debit Note | 383 |
0200000 |
No | ->simplified()->debitNote() |
| Prepayment Invoice | 386 |
— | Depends on standard/simplified | ->prepaymentInvoice() |
Transaction Flags (KSA-2)
The name attribute is NNPNESB: the subtype (01 standard, 02 simplified) followed by five flags. Set them with chainable methods, in any order:
| Flag | Method | Position | Allowed on simplified? |
|---|---|---|---|
| Third party | ->thirdParty() |
P (3) | Yes |
| Nominal supply | ->nominal() |
N (4) | Yes |
| Export | ->export() |
E (5) | No |
| Summary | ->summary() |
S (6) | Yes |
| Self-billed | ->selfBilled() |
B (7) | No |
Pass false to clear a flag (->export(false)). generateXml() rejects the combinations ZATCA forbids:
- an export invoice that is self-billed (BR-KSA-07)
- a simplified invoice flagged as export or self-billed (BR-KSA-31)
- an export invoice with a buyer VAT number (BR-KSA-46)
- a simplified summary invoice without a supply end date or buyer name (BR-KSA-71/72)
Supply Dates
The supply date (KSA-5) defaults to the issue date. Summary invoices also need a supply end date (KSA-24):
Prepayment Adjustment
To deduct an earlier prepayment invoice (386) from a final invoice, add it with addPrepayment(). Each prepayment becomes a zero-value invoice line that references it (§9.5). PrepaidAmount becomes the sum of taxable amount and VAT, and PayableAmount is reduced by it:
Credit & Debit Notes
Credit and debit notes must reference the original invoice using addBillingReference(). Payment means are optional but recommended.
Data Reference
InvoiceData — All Setters
| Method | Type | Required | Description |
|---|---|---|---|
setInvoiceNumber($num) |
string |
Yes | Your invoice number |
standard() |
— | Yes* | Set as Standard (B2B). *One of standard/simplified required |
simplified() |
— | Yes* | Set as Simplified (B2C) |
taxInvoice() |
— | Yes* | Tax Invoice (388). *One of tax/credit/debit/prepayment required |
creditNote() |
— | — | Credit Note (381) |
debitNote() |
— | — | Debit Note (383) |
prepaymentInvoice() |
— | — | Prepayment (386) |
setIssueDate($date) |
string |
Yes | Format: Y-m-d |
setIssueTime($time) |
string |
Yes | Format: H:i:s |
setDueDate($date) |
string |
No | Format: Y-m-d |
setCurrencyCode($code) |
string |
Yes | ISO 4217 (e.g., SAR) |
setDocumentCurrencyCode($code) |
string |
Yes | Usually same as currency code |
setTaxCurrencyCode($code) |
string |
Yes | Must be SAR (BR-KSA-EN16931-02) |
setTaxExchangeRate($rate) |
float |
If currency ≠ SAR | 1 unit of the invoice currency in SAR, used for the SAR VAT total (BT-111) |
setInvoiceCounter($icv) |
string |
Yes | Sequential counter starting at 1 |
setPreviousInvoiceHash($pih) |
string |
Yes | InvoiceData::FIRST_INVOICE_HASH for first invoice (the default) |
thirdParty() / nominal() / export() / summary() / selfBilled() |
bool |
No | KSA-2 flags. See Transaction Flags |
setSupplyDate($date) |
string |
No | Supply date (KSA-5). Default: issue date |
setSupplyEndDate($date) |
string |
Summary | Supply end date (KSA-24) |
setNote($note, $lang) |
string |
No | Invoice note (BT-22), optional languageID |
addPrepayment($prepayment) |
array |
No | Deduct a prepayment invoice. See Prepayment Adjustment |
setPayableRoundingAmount($amt) |
float |
No | Rounding amount (BT-114) added to the amount due |
getFileName() |
— | — | File name per §14, e.g. 300000000000003_20240115T103000_INV-001.xml |
setSeller($seller) |
SellerData |
Yes | Seller information |
setBuyer($buyer) |
BuyerData |
Yes | Buyer information |
addLine($line) |
InvoiceLineData |
Yes | At least one line required |
calculateTotals() |
— | Yes | Call after adding all lines |
addBillingReference($ref) |
array |
For CN/DN | Keys: id, uuid |
addPaymentMeans($pm) |
array |
No | Keys: code (10, 30, 42, 48, 1), instruction_note, due_date, channel_code, payment_id |
addAllowance($allowance) |
array |
No | Document-level discount. Keys: amount, reason (default discount), reason_code (UNTDID 5189, e.g. 95), base_amount + multiplier (percentage, both or neither; amount is computed if omitted), tax_category, tax_percent |
addCharge($charge) |
array |
No | Document-level charge. Same keys as addAllowance(); reason and reason_code (UNTDID 7161, e.g. FC = freight) are required |
SellerData — All Setters
| Method | Type | Required | Description |
|---|---|---|---|
setRegistrationName($name) |
string |
Yes | Company legal name |
setVatNumber($vat) |
string |
Yes | 15-digit VAT number |
setPartyIdentification($value) |
string |
Yes | ID value (e.g., CRN number) |
setPartyIdentificationId($scheme) |
string |
Yes | Scheme: CRN, VAT, TIN, etc. |
setStreetName($street) |
string |
Yes | Street name |
setBuildingNumber($num) |
string |
Yes | Building number |
setCityName($city) |
string |
Yes | City name |
setPostalZone($zip) |
string |
Yes | Postal/ZIP code |
setCountryCode($code) |
string |
Yes | 2-letter code (e.g., SA) |
setPlotIdentification($plot) |
string |
No | Plot identification |
setCitySubdivisionName($district) |
string |
Yes | District name (BR-KSA-09) |
setAdditionalStreetName($name) |
string |
No | Additional street name |
BuyerData — All Setters
Same methods as SellerData. For simplified invoices, setVatNumber() is optional.
InvoiceLineData — All Setters
| Method | Type | Required | Description |
|---|---|---|---|
setId($id) |
int |
Yes | Line number (sequential: 1, 2, 3...) |
setItemName($name) |
string |
Yes | Item name |
setDescription($desc) |
string |
No | Item description (cac:Item/cbc:Description) |
setQuantity($qty) |
float |
Yes | Quantity |
setUnitPrice($price) |
float |
Yes | Unit price (tax-exclusive) |
setTaxPercent($pct) |
float |
Yes | VAT percentage (e.g., 15.0). Ignored (0%) for Z, E and O lines |
setTaxCategory($cat, $reasonCode, $reasonText) |
string |
No | VAT category: S (default), Z, E or O. See Zero-rated, exempt & out-of-scope items |
setUnitCode($code) |
string |
No | UN/ECE Rec 20 unit code (default: PCE) |
setItemCode($code) |
string |
No | Seller's item code (BT-155) |
setBuyersItemCode($code) |
string |
No | Buyer's item code (BT-156) |
setStandardItemCode($code, $scheme) |
string |
No | Standard item ID (BT-157), e.g. GTIN |
setPriceDiscount($amt) |
float |
No | Per-unit discount (BT-147). setUnitPrice() is then the gross price; the net price is gross − discount |
setBaseQuantity($qty) |
float |
No | Units the price applies to (BT-149), e.g. 12 for a price per dozen |
calculateTotals() |
— | Yes | Auto-calculates all amounts from qty × price × tax% |
setAllowanceAmount($amt) |
float |
No | Line-level discount total (set before calculateTotals). setUnitPrice() is the gross, pre-discount unit price. |
setAllowanceReason($reason) |
string |
No | Reason for the line discount (default: discount) |
setAllowanceReasonCode($code) |
string |
No | UNTDID 5189 code for the line discount (e.g. 95) |
setChargeAmount($amt) |
float |
No | Line-level charge (set before calculateTotals). Requires setChargeReason() and setChargeReasonCode() |
setChargeReason($reason) |
string |
With a charge | Reason for the line charge (BR-KSA-22) |
setChargeReasonCode($code) |
string |
With a charge | UNTDID 7161 code (e.g. CG = cleaning) (BR-KSA-20) |
Rounding: amounts are rounded half-up to 2 decimals at each step (line net, line VAT, VAT per category, totals), as the standard requires. Unit prices and quantities keep their decimals.
Tip: Call
calculateTotals()on each line item, then callcalculateTotals()on the invoice. This auto-fillslineExtensionAmount,taxAmount,taxExclusiveAmount,taxInclusiveAmount, and all invoice-level totals.See also: Line discounts (BG-27) & B2B/B2C buyer party — how a line discount becomes a valid
cac:AllowanceCharge, and how the buyer party differs between Standard (B2B) and Simplified (B2C) invoices.
Zero-rated, Exempt & Out-of-scope Items
Every line is standard-rated (S) by default. Use setTaxCategory() for other VAT categories. Z, E and O lines are always taxed at 0% and need a ZATCA exemption reason code. The reason text is filled in from ZATCA's official wording, except for VATEX-SA-OOS, where you must supply your own text.
| Category | Meaning | Reason codes |
|---|---|---|
S |
Standard rated | none |
Z |
Zero rated | VATEX-SA-32, VATEX-SA-33, VATEX-SA-34-1 … VATEX-SA-34-5, VATEX-SA-35, VATEX-SA-36, VATEX-SA-EDU, VATEX-SA-HEA, VATEX-SA-MLTRY |
E |
Exempt | VATEX-SA-29, VATEX-SA-29-7, VATEX-SA-30 |
O |
Out of scope | VATEX-SA-OOS |
You can mix categories in one invoice. calculateTotals() builds one VAT breakdown entry (cac:TaxSubtotal) per category, rate and reason, and the XML includes it automatically. For VATEX-SA-EDU and VATEX-SA-HEA, ZATCA also requires the buyer's national ID (NAT).
A document-level allowance or charge uses the first line's category unless you set tax_category (and tax_percent for S):
Previous Invoice Hash (PIH) Chain
Every invoice references the hash of the previous invoice to create a tamper-proof chain:
Validation
validate() checks an invoice against the ZATCA business rules before anything is sent, and lists every problem at once. You don't have to wait for ZATCA to reject the invoice one error at a time.
error: ZATCA rejects the invoice.warning: accepted, but probably not what you want (e.g. a credit note without a real reason).
Set 'validate' => true in the ZatcaManager config to validate automatically in processInvoice(). Errors then throw a ZatcaValidationException before signing or sending. Warnings never block.
What is checked:
| Area | Rules |
|---|---|
| Header | Invoice number, issue date (valid, not in the future), issue time format, date formats, currency codes, ICV digits, previous invoice hash (BR-02/03, BR-KSA-04/26/34/70, BR-KSA-F-01, BR-CL-04, BR-KSA-EN16931-02) |
| Seller | Name, VAT number format, identification and scheme, full address, 4-digit building number, 5-digit postal code, country code (BR-06, BR-KSA-08/09/37/39/40/66, BR-CL-14) |
| Buyer | Name and address on tax invoices, VAT number format, ID when there is no VAT number, ID scheme, Saudi address fields (BR-KSA-10/14/42/44/63/67/81) |
| Notes | Billing reference for credit/debit notes, reason warning (BR-KSA-56, BR-55, BR-KSA-17) |
| Payment | Payment means codes 10, 30, 42, 48, 1 (BR-KSA-16) |
| Lines | At least one line, item names, no negative values, standard rate above 0%, national ID for education/healthcare exemptions (BR-16, BR-25, BR-KSA-F-04, BR-KSA-DEC-02, BR-KSA-25/49) |
| Allowances/charges | Non-negative amounts, standard rate above 0% (BR-S-06/07) |
| Flags & dates | Flag combinations, supply dates, simplified summary requirements, prepayment date/time (BR-KSA-07/31/36/46/71/72, BR-KSA-F-05) |
Character limits (BR-KSA-F-06) come from ZATCA's separate Data Dictionary and are not checked.
Error Handling
All exceptions extend ZatcaException and provide a getContext() method with structured error details.
Package Structure
Testing
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
Acknowledgments
This package includes code and inspiration from:
- php-zatca-xml by Saleh7 — XML generation and ZATCA compliance logic.
License
This package is open-sourced software licensed under the MIT license.
Support
- Email: [email protected]
- GitHub Issues: Create an issue
Changelog
See CHANGELOG.md for version history.
All versions of zatca-php with dependencies
phpseclib/phpseclib Version ^3.0
guzzlehttp/guzzle Version ^7.0
chillerlan/php-qrcode Version ^4.3