10 KiB
ZATCA E-Invoice Quick Reference Guide
🚀 Getting Started (5 Minutes)
Step 1: Run Migrations
python3 manage.py migrate
Step 2: Configure Your Tenant
# In Django shell or admin
from core.models import Tenant
tenant = Tenant.objects.first()
tenant.vat_number = "300000000000003" # Your actual VAT number
tenant.name_ar = "مركز أغدار"
tenant.address = "123 Main Street, Riyadh"
tenant.city = "Riyadh"
tenant.postal_code = "12345"
tenant.save()
Step 3: Create Your First Invoice
from finance.models import Invoice, InvoiceLineItem
from core.models import Patient
from datetime import date, timedelta
# Get patient
patient = Patient.objects.first()
# Create invoice
invoice = Invoice.objects.create(
tenant=tenant,
patient=patient,
invoice_type='SIMPLIFIED', # B2C
issue_date=date.today(),
due_date=date.today() + timedelta(days=30),
subtotal=100.00,
tax=15.00, # 15% VAT
total=115.00,
status='ISSUED'
)
# Add line item
InvoiceLineItem.objects.create(
invoice=invoice,
description="Medical Consultation",
quantity=1,
unit_price=100.00,
total=100.00
)
# Check auto-generated fields
print(f"✅ Invoice Counter: {invoice.invoice_counter}")
print(f"✅ Invoice Hash: {invoice.invoice_hash[:32]}...")
print(f"✅ QR Code: {invoice.qr_code[:50]}...")
📋 Invoice Types Guide
| Type | Code | When to Use | ZATCA Process |
|---|---|---|---|
| Standard | STANDARD |
B2B transactions | Clearance (real-time) |
| Simplified | SIMPLIFIED |
B2C transactions | Reporting (24 hours) |
| Standard Credit | STANDARD_CREDIT |
B2B refunds | Clearance |
| Standard Debit | STANDARD_DEBIT |
B2B adjustments | Clearance |
| Simplified Credit | SIMPLIFIED_CREDIT |
B2C refunds | Reporting |
| Simplified Debit | SIMPLIFIED_DEBIT |
B2C adjustments | Reporting |
🔄 ZATCA Submission Workflows
Workflow 1: Standard Invoice (B2B)
# 1. Create invoice
invoice = Invoice.objects.create(
invoice_type='STANDARD',
# ... other fields
)
# 2. Submit for clearance (BEFORE sharing with buyer)
from finance.zatca_tasks import submit_invoice_to_zatca
result = submit_invoice_to_zatca.delay(str(invoice.id), use_sandbox=True)
# 3. Check status
invoice.refresh_from_db()
if invoice.zatca_status == 'CLEARED':
# 4. Share cleared invoice with buyer
# Invoice now has ZATCA stamp and updated QR code
pass
Workflow 2: Simplified Invoice (B2C)
# 1. Create invoice
invoice = Invoice.objects.create(
invoice_type='SIMPLIFIED',
# ... other fields
)
# 2. Share with customer immediately (QR code already on invoice)
# 3. Report to ZATCA within 24 hours (automatic via Celery)
# Or manually:
from finance.zatca_tasks import submit_invoice_to_zatca
result = submit_invoice_to_zatca.delay(str(invoice.id), use_sandbox=True)
Workflow 3: Credit Note
# 1. Create credit note referencing original invoice
credit_note = Invoice.objects.create(
invoice_type='SIMPLIFIED_CREDIT', # or STANDARD_CREDIT
billing_reference_id=original_invoice.invoice_number,
billing_reference_issue_date=original_invoice.issue_date,
subtotal=-100.00, # Negative amounts
tax=-15.00,
total=-115.00,
# ... other fields
)
# 2. Submit to ZATCA (same process as regular invoice)
🔍 Checking Invoice Status
Via Django Admin:
- Go to Finance → Invoices
- Look at "ZATCA Status" column:
- CLEARED - Standard invoice approved by ZATCA
- REPORTED - Simplified invoice reported to ZATCA
- FAILED - Submission failed (check zatca_response)
- (empty) - Not yet submitted
Via Code:
invoice = Invoice.objects.get(invoice_number='INV-001-2025-12345')
print(f"Status: {invoice.zatca_status}")
print(f"Submitted: {invoice.zatca_submission_date}")
print(f"Response: {invoice.zatca_response}")
🛠️ Common Tasks
Generate PDF Invoice
from finance.pdf_service import PDFService
pdf_bytes = PDFService.generate_invoice_pdf(invoice)
# Save to file
with open(f'invoice_{invoice.invoice_number}.pdf', 'wb') as f:
f.write(pdf_bytes)
# Or return as HTTP response
from django.http import HttpResponse
response = HttpResponse(pdf_bytes, content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="invoice_{invoice.invoice_number}.pdf"'
return response
Generate XML
from finance.zatca_service import ZATCAService
zatca = ZATCAService(use_sandbox=True)
xml = zatca.generate_xml_invoice(invoice)
# Save to invoice
invoice.xml_content = xml
invoice.save()
Validate QR Code
# QR code is automatically generated
qr_code = invoice.qr_code
# Decode to verify
import base64
decoded = base64.b64decode(qr_code)
# First byte is tag 1 (Seller's Name)
# Second byte is length
# Following bytes are the value
Check Invoice Sequence
from finance.csid_manager import InvoiceCounterManager
is_valid, gaps = InvoiceCounterManager.validate_counter_sequence(tenant)
if not is_valid:
print(f"⚠️ Gaps found in sequence: {gaps}")
else:
print("✅ Invoice sequence is valid")
🔐 CSID Management
Check Active CSID
from finance.csid_manager import CSIDManager
csid = CSIDManager.get_active_csid(tenant)
if csid:
print(f"✅ Active CSID: {csid.common_name}")
print(f"📅 Expires in: {csid.days_until_expiry} days")
print(f"📊 Invoices signed: {csid.invoices_signed}")
else:
print("⚠️ No active CSID - please onboard via FATOORA portal")
Check if Renewal Needed
needs_renewal, message = CSIDManager.check_expiry_and_renew(tenant)
if needs_renewal:
print(f"⚠️ {message}")
else:
print("✅ CSID is valid")
🔧 Troubleshooting
Issue: Invoice counter not incrementing
Solution: Check that transaction locking is working
# Counter should auto-increment with SELECT FOR UPDATE lock
# Check last invoice counter
last = Invoice.objects.filter(tenant=tenant).order_by('-invoice_counter').first()
print(f"Last counter: {last.invoice_counter if last else 'None'}")
Issue: QR code not generating
Solution: Check that invoice has all required fields
# QR code requires:
# - Tenant name
# - Tenant VAT number
# - Issue date and time
# - Total and tax amounts
# Verify tenant has VAT number
print(f"VAT Number: {invoice.tenant.vat_number}")
Issue: ZATCA submission failing
Solution: Check CSID and error response
# Check CSID
csid = CSIDManager.get_active_csid(tenant)
print(f"CSID valid: {csid.is_valid if csid else 'No CSID'}")
# Check error response
print(f"Error: {invoice.zatca_response}")
Issue: Hash chain broken
Solution: Validate sequence
# Check for gaps
is_valid, gaps = InvoiceCounterManager.validate_counter_sequence(tenant)
print(f"Sequence valid: {is_valid}")
print(f"Gaps: {gaps}")
📊 Monitoring & Reports
Daily Compliance Check
from finance.zatca_tasks import monitor_zatca_compliance
# Run compliance monitoring
report = monitor_zatca_compliance.delay()
# Check results
print(report.get())
Generate Compliance Report
from finance.zatca_tasks import generate_compliance_report
report = generate_compliance_report.delay(
tenant_id=str(tenant.id),
start_date='2025-01-01',
end_date='2025-01-31'
)
print(report.get())
Retry Failed Submissions
from finance.zatca_tasks import retry_failed_submissions
# Retry all failed invoices for tenant
results = retry_failed_submissions.delay(tenant_id=str(tenant.id))
print(f"Retried: {results.get()}")
🎯 Best Practices
1. Invoice Creation
✅ Always set correct invoice_type (STANDARD vs SIMPLIFIED) ✅ Let counter auto-increment (don't set manually) ✅ Verify patient nationality for VAT calculation ✅ Add line items before finalizing
2. ZATCA Submission
✅ Standard invoices: Clear BEFORE sharing with buyer ✅ Simplified invoices: Share immediately, report within 24h ✅ Monitor zatca_status field ✅ Handle failures gracefully
3. Credit/Debit Notes
✅ Always reference original invoice ✅ Use negative amounts for credits ✅ Follow same submission process as original
4. CSID Management
✅ Monitor expiry dates (renew 30 days before) ✅ Keep one active CSID per tenant ✅ Revoke compromised CSIDs immediately ✅ Track usage statistics
📱 API Endpoints Reference
Sandbox (Testing):
- Base:
https://gw-fatoora.zatca.gov.sa/e-invoicing/simulation - Clearance:
/invoices/clearance/single - Reporting:
/invoices/reporting/single - Compliance CSID:
/compliance - Production CSID:
/production/csids
Production:
- Base:
https://gw-fatoora.zatca.gov.sa/e-invoicing/core - Same endpoints as sandbox
🔑 Environment Variables
Add to your .env file:
# ZATCA Configuration
ZATCA_USE_SANDBOX=True # Set to False for production
ZATCA_VAT_NUMBER=300000000000003
ENCRYPTION_KEY=your-32-byte-encryption-key-here
# Optional
ZATCA_CSID=your-production-csid
ZATCA_SECRET=your-production-secret
📞 Support Contacts
ZATCA:
- Portal: https://fatoora.zatca.gov.sa/
- Sandbox: https://sandbox.zatca.gov.sa/
- Email: info@zatca.gov.sa
- Phone: 19993 (Local), +966112048998 (International)
Developer Resources:
- SDK Download: https://zatca.gov.sa/en/E-Invoicing/SystemsDevelopers/
- API Docs: Available on Developer Portal (requires login)
- Compliance Toolbox: Web-based validator available
✅ Pre-Production Checklist
- Migrations run successfully
- Tenant VAT number configured
- Test invoice created with QR code
- QR code validated with ZATCA mobile app
- Tested in ZATCA sandbox
- Production CSID obtained
- Celery workers running
- Monitoring configured
- Staff trained
- Backup system in place
🎓 Quick Tips
💡 Tip 1: Use sandbox for all testing - never test with production CSID 💡 Tip 2: QR codes can be scanned with ZATCA mobile app to verify 💡 Tip 3: Invoice counter gaps will trigger compliance alerts 💡 Tip 4: Simplified invoices can be shared immediately, no need to wait for ZATCA 💡 Tip 5: Standard invoices MUST be cleared before sharing with buyer
Last Updated: October 27, 2025
Version: 1.0
Status: Production Ready