PHP code example of khaledhajsalem / zatca-php

1. Go to this page and download the library: Download khaledhajsalem/zatca-php library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

khaledhajsalem / zatca-php example snippets


use KhaledHajSalem\Zatca\Support\CertificateBuilder;

$builder = new CertificateBuilder();
$builder->setOrganizationIdentifier('300000000000003') // 15 digits, starts & ends with 3
    ->setSerialNumber('MySolution', 'Model1', 'SN001')
    ->setCommonName('Your Company Name')
    ->setCountryName('SA')
    ->setOrganizationName('Your Company Name')
    ->setOrganizationalUnitName('IT Department')
    ->setAddress('123 Main Street, Riyadh, Saudi Arabia')
    ->setInvoiceType(1100) // 4 digits: [Standard][Simplified][future][future] — 1100 = both standard & simplified
    ->setProduction(false) // false = sandbox/simulation, true = production
    ->setBusinessCategory('Legal Entity');

$builder->generateAndSave('storage/certificate.csr', 'storage/private.pem');

use KhaledHajSalem\Zatca\Services\ZatcaAPIService;

// Initialize API service
$apiService = new ZatcaAPIService('sandbox'); // 'sandbox', 'simulation', or 'production'

// ── Step 2a: Request Compliance Certificate ──
$csr = file_get_contents('storage/certificate.csr');
$otp = '123456'; // OTP from ZATCA Fatoora Portal

$complianceResult = $apiService->requestComplianceCertificate($csr, $otp);

// Save the compliance certificate (keep it separate from the production one)
file_put_contents('storage/compliance_certificate.pem', $complianceResult->getCertificate());
$complianceSecret    = $complianceResult->getSecret();     // Compliance API secret (keep it for step 2c)
$complianceRequestId = $complianceResult->getRequestId();  // Need this for production certificate

// ── Step 2b: Run Compliance Tests ──
// Submit test invoices with $apiService->validateInvoiceCompliance(...) using the
// compliance certificate and secret. In simulation/production, every invoice type
// declared in the CSR (e.g. 1100 → standard + simplified invoice, credit note and
// debit note) must pass before the production certificate is issued.
// Once all tests pass...

// ── Step 2c: Request Production Certificate ──
$complianceCert = file_get_contents('storage/compliance_certificate.pem');

$productionResult = $apiService->requestProductionCertificate(
    $complianceCert,          // Compliance certificate
    $complianceSecret,        // Compliance secret
    $complianceRequestId      // Request ID from step 2a
);

// Save the production certificate — use this for all real invoices
file_put_contents('storage/certificate.pem', $productionResult->getCertificate());
file_put_contents('storage/secret.txt', $productionResult->getSecret()); // Your API secret for invoices

use KhaledHajSalem\Zatca\ZatcaManager;
use KhaledHajSalem\Zatca\Data\InvoiceData;
use KhaledHajSalem\Zatca\Data\SellerData;
use KhaledHajSalem\Zatca\Data\BuyerData;
use KhaledHajSalem\Zatca\Data\InvoiceLineData;

// ── 1. Initialize ZatcaManager ──
$zatcaManager = new ZatcaManager([
    'environment'      => 'sandbox',                              // 'sandbox', 'simulation', or 'production'
    'certificate_path' => __DIR__ . '/storage/certificate.pem',   // Path to your certificate file
    'private_key_path' => __DIR__ . '/storage/private.pem',       // Path to your private key file
    'secret'           => trim(file_get_contents(__DIR__ . '/storage/secret.txt')), // API secret from step 2c
    'validate'         => true,                                   // Check business rules before submitting (optional)
]);

// ── 2. Create Invoice Data ──
$invoiceData = new InvoiceData();
$invoiceData
    ->setInvoiceNumber('INV-001')           // Your invoice number
    ->simplified()                          // B2C invoice (reporting) — or ->standard() for B2B (clearance)
    ->taxInvoice()                          // Tax Invoice (388) — or ->creditNote(), ->debitNote(), ->prepaymentInvoice()
    ->setIssueDate('2025-01-15')            // Issue date in Y-m-d format
    ->setIssueTime('10:30:00')              // Issue time in H:i:s format
    ->setDueDate('2025-02-15')              // Due date in Y-m-d format
    ->setCurrencyCode('SAR')                // Currency code (ISO 4217)
    ->setDocumentCurrencyCode('SAR')        // Document currency (usually same as above)
    ->setTaxCurrencyCode('SAR')             // Tax currency (usually same as above)
    ->setInvoiceCounter('1')                // ICV: sequential counter, must increment per invoice
    ->setPreviousInvoiceHash(InvoiceData::FIRST_INVOICE_HASH); // PIH: first invoice, then use hash from previous invoice

// ── 3. Create Seller Data ──
$seller = new SellerData();
$seller->setRegistrationName('Your Company Name')  // Company legal name
    ->setVatNumber('399999999900003')               // VAT number (15 digits)
    ->setPartyIdentification('1010203020')          // Identification value (e.g., CRN number)
    ->setPartyIdentificationId('CRN')               // Identification type — see "Party Identification Schemes" above
    ->setStreetName('Main Street')                  // Street name
    ->setBuildingNumber('1234')                     // Building number
    ->setCityName('Riyadh')                         // City
    ->setPostalZone('12345')                        // Postal/ZIP code
    ->setCountryCode('SA')                          // 2-letter country code
    ->setPlotIdentification('PLOT-001')             // Plot identification (optional)
    ->setCitySubdivisionName('District 1');         // District name (

$invoiceData = new InvoiceData();
$invoiceData
    ->setInvoiceNumber('INV-002')
    ->standard()                                    // ← This is the only change (B2B, Code('SAR')
    ->setInvoiceCounter('2')                        // ICV: second invoice
    ->setPreviousInvoiceHash($previousInvoiceHash); // PIH: hash from INV-001

// ... seller, buyer, lines same as above ...

$result = $zatcaManager->processInvoice($invoiceData);

// For standard invoices, ZATCA returns a cleared XML:
if ($result['response']['clearanceStatus'] === 'CLEARED') {
    $clearedXml = $result['xml']; // Use this XML (not your original)
}

$invoice->standard()->export();                                   // 0100100
$invoice->simplified()->summary()                                 // 0200010
    ->setSupplyDate('2024-01-01')->setSupplyEndDate('2024-01-31');

$invoice->setSupplyDate('2024-01-01')->setSupplyEndDate('2024-01-31');

$invoice->addPrepayment([
    'id'             => 'PP-001',       // invoice number of the prepayment invoice
    'issue_date'     => '2024-01-10',
    'issue_time'     => '09:30:00',
    'taxable_amount' => 86.96,
    'tax_category'   => 'S',            // optional, default S
    'tax_percent'    => 15,             // optional, default 15 for S, 0 for Z/E/O
]);                                     // tax_amount (13.04) is calculated

// ── Credit Note (returns/refunds) ──
$creditNote = new InvoiceData();
$creditNote
    ->setInvoiceNumber('CN-001')
    ->simplified()                                  // or ->standard()
    ->creditNote()                                  // Type code 381
    ->setIssueDate('2025-01-20')
    ->setIssueTime('14:00:00')
    ->setCurrencyCode('SAR')
    ->setDocumentCurrencyCode('SAR')
    ->setTaxCurrencyCode('SAR')
    ->setInvoiceCounter('3')
    ->setPreviousInvoiceHash($previousHash)

    // REQUIRED: Reference to the original invoice
    ->addBillingReference([
        'id'   => 'INV-001',                        // Original invoice number
        'uuid' => '63decc4e-cc4d-4e3b-878c-b772560bb5f1', // Original invoice UUID
    ])

    // OPTIONAL: Payment means (reason for the note)
    ->addPaymentMeans([
        'code'             => '10',                  // Payment method code
        'instruction_note' => 'Returns',             // Reason: Returns, Correction, Cancellation, etc.
    ]);

// ... set seller, buyer, lines, calculateTotals(), then submit
$result = $zatcaManager->processInvoice($creditNote);

// ── Debit Note (additional charges) ──
$debitNote = new InvoiceData();
$debitNote
    ->setInvoiceNumber('DN-001')
    ->standard()                                    // or ->simplified()
    ->debitNote()                                   // Type code 383
    ->setIssueDate('2025-01-20')
    ->setIssueTime('14:00:00')
    ->setCurrencyCode('SAR')
    ->setDocumentCurrencyCode('SAR')
    ->setTaxCurrencyCode('SAR')
    ->setInvoiceCounter('4')
    ->setPreviousInvoiceHash($previousHash)
    ->addBillingReference([
        'id'   => 'INV-001',
        'uuid' => '63decc4e-cc4d-4e3b-878c-b772560bb5f1',
    ])
    ->addPaymentMeans([
        'code'             => '10',
        'instruction_note' => 'Addition',
    ]);

// ... set seller, buyer, lines, calculateTotals(), then submit
$result = $zatcaManager->processInvoice($debitNote);

use KhaledHajSalem\Zatca\Support\TaxCategory;

// Zero-rated export
$line->setTaxCategory(TaxCategory::ZERO_RATED, 'VATEX-SA-32');

// Exempt financial service
$line->setTaxCategory(TaxCategory::EXEMPT, 'VATEX-SA-29');

// Out of scope (reason text 

$invoiceData->addAllowance(['amount' => 10.00, 'reason' => 'discount', 'tax_category' => 'S', 'tax_percent' => 15.0]);

// Invoice 1 (first invoice — no previous)
$invoice1->setPreviousInvoiceHash(InvoiceData::FIRST_INVOICE_HASH); // base64(hex SHA-256 of '0'), BR-KSA-26
$result1 = $zatcaManager->processInvoice($invoice1);
$hash1 = $result1['invoice_hash'];                   // Save this!

// Invoice 2 (references invoice 1)
$invoice2->setPreviousInvoiceHash($hash1);
$result2 = $zatcaManager->processInvoice($invoice2);
$hash2 = $result2['invoice_hash'];                   // Save this!

// Invoice 3 (references invoice 2)
$invoice3->setPreviousInvoiceHash($hash2);
// ... and so on

$issues = $invoiceData->validate();

foreach ($issues as $issue) {
    echo "{$issue['severity']} [{$issue['rule']}] {$issue['field']}: {$issue['message']}\n";
}
// error [BR-KSA-40] seller.vat_number: Seller VAT number must be 15 digits, starting and ending with 3.
// error [BR-KSA-81] buyer.party_identification: Buyer ID is 

use KhaledHajSalem\Zatca\Exceptions\ZatcaValidationException;

try {
    $result = $zatcaManager->processInvoice($invoiceData);
} catch (ZatcaValidationException $e) {
    $errors = $e->getIssues(); // same format as validate()
}

use KhaledHajSalem\Zatca\Exceptions\ZatcaException;
use KhaledHajSalem\Zatca\Exceptions\CertificateBuilderException;
use KhaledHajSalem\Zatca\Exceptions\ZatcaApiException;
use KhaledHajSalem\Zatca\Exceptions\ZatcaValidationException;

try {
    $result = $zatcaManager->processInvoice($invoiceData);
} catch (ZatcaValidationException $e) {
    // Invoice data breaks ZATCA rules (only with 'validate' => true)
    print_r($e->getIssues());
} catch (CertificateBuilderException $e) {
    // Certificate generation errors
    echo "Certificate error: " . $e->getMessage();
    echo "Details: " . json_encode($e->getContext());
} catch (ZatcaApiException $e) {
    // ZATCA API errors (network, validation, auth)
    echo "API error: " . $e->getMessage();
    echo "Details: " . json_encode($e->getContext());
} catch (ZatcaException $e) {
    // General package errors (missing config, file not found, etc.)
    echo "Error: " . $e->getMessage();
    echo "Details: " . json_encode($e->getContext());
}
bash
composer