hazem7575 / zatca
ZATCA (Fatoora) e-invoicing implementation for Saudi Arabia
v1
2025-02-06 07:55 UTC
Requires
- php: ^7.4|^8.0
- ext-curl: *
- ext-dom: *
- ext-json: *
- ext-openssl: *
- illuminate/database: ^8.0|^9.0|^10.0
- illuminate/support: ^8.0|^9.0|^10.0
- salla/zatca: *
This package is auto-updated.
Last update: 2025-05-01 20:32:08 UTC
README
ZATCA (Fatoora) e-invoicing implementation for Saudi Arabia.
please visit our documentation.
Table of Contents
- Installation
- Requirements
- Configuration
- Basic Usage
- Customization
- API Reference
- Database Schema
- Security
- Contributing
- License
Installation
Install the package via Composer:
composer require hazem7575/zatca
Requirements
- PHP ^7.4|^8.0
- Laravel ^8.0|^9.0|^10.0
- OpenSSL Extension
- JSON Extension
- DOM Extension
- cURL Extension
Configuration
app.php
Hazem\Zatca\ZatcaServiceProvider::class,
- Publish configuration and migrations:
php artisan vendor:publish --provider="Hazem\Zatca\ZatcaServiceProvider"
- Run migrations:
php artisan migrate
- Configure your
.env
file:
ZATCA_LIVE=false
Basic Usage
Device Registration
Add the HasZatcaDevice
trait to your model:
use Hazem\Zatca\Traits\HasZatcaDevice; class Store extends Model { use HasZatcaDevice; }
Register a new device:
$device = $store->registerZatcaDevice($request->otp, [ 'vat_no' => $request->tax_number, 'ci_no' => $request->registration_number, 'company_name' => $request->organization_name, 'company_address' => $request->registered_address, 'company_building' => $request->building_number, 'company_plot_identification' => $request->plot_identification, 'company_city_subdivision' => $request->city_sub_division, 'company_city' => $request->city, 'company_postal' => $request->postal_number, 'company_country' => 'SA', 'solution_name' => 'MADA', 'common_name' => $request->common_name, ]); $device->active();
Invoice Creation
Add the HasZatcaInvoice
trait to your model:
use Hazem\Zatca\Traits\HasZatcaInvoice; class Order extends Model { use HasZatcaInvoice; /** * Required fields for ZATCA invoice */ protected function prepareZatcaInvoiceData() { $items = $this->prepareZatcaItems(); return [ // Required fields 'invoice_number' => $this->invoice_no, 'total_amount' => round($this->final_total, 2), 'vat_amount' => collect($items)->sum(function ($item) { return round($item['vat'] * $item['quantity'], 2); }), 'is_pos' => true, 'is_invoice' => $this->type === 'sell', 'items' => $items, 'date' => $this->transaction_date, // Optional buyer information 'buyer_name' => $this->contact->name ?? null, 'buyer_tax_number' => null, 'buyer_address' => null, 'buyer_city' => null, 'buyer_state' => null, 'buyer_postal' => null, 'buyer_building_no' => null ]; } /** * Prepare invoice items */ protected function prepareZatcaItems() { return $this->sell_lines->map(function($item) { return [ 'name' => $item->product?->name, 'quantity' => $item->quantity, 'price' => round($item->unit_price, 2), 'vat' => round(round($item->unit_price, 2) * 0.15, 2) ]; })->toArray(); } /** * Get device relationship */ protected function device() { return $this->business; } /** * Update last invoice hash */ public function update_last_hash($hash) { $this->business()->update([ 'last_hash' => $hash ]); } /** * Get last invoice hash */ public function last_hash() { $last_hash = $this->business->last_hash; if(isset($last_hash)) { return $last_hash; } return 'NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ=='; } }
use Hazem\Zatca\Traits\HasZatcaInvoice; class Order extends Model { use HasZatcaInvoice; }
Create an invoice using the fluent interface:
$invoice = Zatca::prepare() ->setInvoiceNumber('INV-001') ->setTotalAmount(115.00) ->setVatAmount(15.00) ->setBuyerName('John Doe') // Optional ->setBuyerTaxNumber('1234567890') // Optional ->setBuyerAddress('123 Main St') // Optional ->setBuyerCity('Riyadh') // Optional ->setBuyerState('Riyadh') // Optional ->setBuyerPostal('12345') // Optional ->setBuyerBuildingNumber('1234') // Optional ->isPOS() // For POS invoices ->isInvoice(true) // For standard invoices ->setDate(now()) ->addItem('Product 1', 1, 100.00, 15.00);
Invoice Submission
Submit the invoice:
$result = $order->submitToZatca($invoice->toArray());
Or submit with custom data:
$result = $order->submitToZatca([ 'invoice_number' => 'INV-001', 'total_amount' => 115.00, 'vat_amount' => 15.00, 'items' => [ [ 'name' => 'Product 1', 'quantity' => 1, 'price' => 100.00, 'vat' => 15.00 ] ] ]);
Invoice Status
Check invoice status:
// Check if submitted if ($order->isSubmittedToZatca()) { // Get status $status = $order->getZatcaStatus(); // Check for errors if ($order->hasZatcaErrors()) { $errors = $order->getZatcaErrors(); } }
API Reference
Device Facade
Device::register(string|int $businessId, string $otp, array $companyData); Device::activate(string|int $businessId); Device::hasDevice(string|int $businessId); Device::getDevice(string|int $businessId); Device::getDeviceStatus(string|int $businessId); Device::isDeviceActive(string|int $businessId);
Zatca Facade
Zatca::prepare(); Zatca::submitInvoice(string|int $businessId, array $invoiceData); Zatca::submitSimplifiedInvoice(string|int $businessId, array $invoiceData); Zatca::submitStandardInvoice(string|int $businessId, array $invoiceData); Zatca::submitCreditNote(string|int $businessId, array $invoiceData); Zatca::submitDebitNote(string|int $businessId, array $invoiceData); Zatca::getInvoiceStatus(string|int $businessId, string $invoiceNumber); Zatca::getInvoiceReport(string|int $businessId, string $invoiceNumber); Zatca::clearInvoice(string|int $businessId, string $invoiceNumber); Zatca::reportInvoice(string|int $businessId, string $invoiceNumber); Zatca::registerDevice(string|int $businessId, string $otp, array $companyData); Zatca::validateInvoice(array $invoiceData); Zatca::generateQRCode(array $invoiceData);
HasZatcaDevice Trait Methods
device(); // Get device relationship registerZatcaDevice(); // Register new device active(); // Activate device hasZatcaDevice(); // Check device existence getLatestZatcaDevice(); // Get latest device scopeHasZatca(); // Query scope for models with active devices scopeDoesntHaveZatca(); // Query scope for models without active devices
HasZatcaInvoice Trait Methods
// Basic Methods order(); // Get order relationship prepareZatcaInvoiceData(); // Prepare invoice data (required fields) prepareZatcaItems(); // Prepare items data submitToZatca(); // Submit invoice isSubmittedToZatca(); // Check submission status getZatcaStatus(); // Get invoice status hasZatcaErrors(); // Check for errors getZatcaErrors(); // Get error details // Hash Management Methods update_last_hash($hash); // Update the last invoice hash last_hash(); // Get the last invoice hash (with default) // Required Fields for prepareZatcaInvoiceData(): // - invoice_number // - total_amount // - vat_amount // - is_pos // - is_invoice // - items // - date // Optional Fields: // - buyer_name // - buyer_tax_number // - buyer_address // - buyer_city // - buyer_state // - buyer_postal // - buyer_building_no
Database Schema
devices_zatca Table
id
- Primary keydeviceable_type
- Model typedeviceable_id
- Model IDrequest_id
- ZATCA request IDstatus
- Device statusdisposition_message
- Status messagebinary_security_token
- Security tokensecret
- Device secreterrors
- Error messages (JSON)private_key
- Private keypublic_key
- Public keycsr_content
- CSR contenttimestamps
orders_zatca Table
id
- Primary keyorderable_type
- Model typeorderable_id
- Model IDinvoice_number
- Invoice numberuuid
- Unique identifierinvoice_hash
- Invoice hashsigned_invoice_xml
- Signed XMLstatus
- Invoice statusis_reported
- Reporting statusis_cleared
- Clearance statuswarnings
- Warning messages (JSON)errors
- Error messages (JSON)response
- Full response (JSON)submitted_at
- Submission timestamptimestamps
Security
- Row Level Security (RLS) enabled for all tables
- Hash chaining for invoice integrity
- Secure storage of private keys and secrets
- Authentication required for all operations
- Data access controlled through policies
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This package is open-sourced software licensed under the MIT license.