SDK Python - itzenata-efact

SDK officiel EFact pour Python ≥ 3.9, typé avec Pydantic v2. Toutes les opérations sur les factures sont exposées via efact.invoices.*.

Installation

Installation
pip install itzenata-efact

Initialisation

Basique

Basic Initialization
import os
from efact import EFact

# La clé secrète identifie ET authentifie votre organisation.
# Ne la committez jamais : chargez-la depuis l'environnement.
efact = EFact(os.environ["EFACT_SECRET_KEY"])

efact.invoices.create(data)

Signature

Method Signature
efact.invoices.create(data: CreateInvoiceRequest | dict) -> InvoiceResponse

Exemple complet

Complete Example
invoice = efact.invoices.create({
    "invoiceNumber": "INV-2026-001",
    "invoiceDate": "2026-06-24",
    "currency": "MAD",
    "paymentMethod": "BANK_TRANSFER",
    "buyerIce": "001234567890123",
    "buyerName": "Client SARL",
    "buyerAddress": "Casablanca",
    "lines": [
        {
            "lineNumber": 1,
            "description": "Prestation de service",
            "quantity": 1,
            "unitPrice": 1000,
            "vatRate": 20,
        },
    ],
})

print(invoice.id, invoice.status)

efact.invoices.retrieve(invoice_id)

Retrieve Invoice
invoice = efact.invoices.retrieve("inv_01H8XYZABC")
print(invoice.status, invoice.invoice_number)

Lève EFactNotFoundError (HTTP 404) si la facture n’existe pas ou n’appartient pas au tenant.

efact.invoices.retrieve_status(invoice_id)

Retrieve Status
status = efact.invoices.retrieve_status("inv_01H8XYZABC")
print(status.status, status.dgi_reference)

efact.invoices.list(...)

List Invoices
page = efact.invoices.list(page=0, size=20, status="ACCEPTED")
for inv in page.content:
    print(inv.invoice_number, inv.status)

Pagination par page/size, pas par curseur.

efact.invoices.update(invoice_id, data)

Update Invoice
# Uniquement pour une facture au statut PENDING — sinon 409
updated = efact.invoices.update("inv_01H8XYZABC", {
    "dueDate": "2026-07-31",
    "buyerAddress": "Nouvelle adresse, Casablanca",
})

efact.invoices.cancel(invoice_id)

Cancel Invoice
# Uniquement pour une facture au statut PENDING
cancelled = efact.invoices.cancel("inv_01H8XYZABC")
print(cancelled.status)  # 'CANCELLED'

Gestion des erreurs

Le SDK lève des erreurs typées, toutes héritées de EFactError. Toujours envelopper les appels dans un try/except :

Error Handling
from efact import (
    EFactApiError,
    EFactAuthError,
    EFactValidationError,
    EFactNotFoundError,
    EFactConflictError,
    EFactConnectionError,
    EFactTimeoutError,
)

try:
    efact.invoices.create(data)
except EFactValidationError as e:
    print("Champs invalides :", e.errors)
except EFactAuthError:
    print("Clé API manquante ou invalide.")
except EFactConflictError:
    print("Facture non modifiable (statut ≠ PENDING).")
except EFactApiError as e:
    print(e.code, e.http_status, e)
except EFactTimeoutError:
    print("Délai dépassé.")
except EFactConnectionError:
    print("Échec réseau.")

Hiérarchie des erreurs

ClasseCode HTTPQuand
EFactConfigError-Mauvaise configuration (avant tout appel réseau)
EFactAuthError401 / 403Clé API manquante ou invalide
EFactNotFoundError404Ressource introuvable (y compris hors tenant)
EFactValidationError400 / 422Payload invalide
EFactConflictError409Ex. modification/annulation d’une facture non PENDING
EFactApiError5xxErreur côté EFact
EFactTimeoutError-Délai dépassé avant réponse
EFactConnectionError-Échec réseau

Les requêtes GET, PUT et DELETE sont rejouées automatiquement (back-off exponentiel) sur timeout, erreur réseau, ou statuts HTTP 408/425/429/500/502/503/504, jusqu’à max_retries fois. POST (création) n’est jamais rejoué.

Intégration Django

views.py
# views.py
import os
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from efact import EFact

efact = EFact(os.environ["EFACT_SECRET_KEY"])

@require_POST
def create_invoice(request):
    import json
    body = json.loads(request.body)

    invoice = efact.invoices.create({
        "invoiceNumber": body["invoiceNumber"],
        "invoiceDate": body["invoiceDate"],
        "currency": "MAD",
        "paymentMethod": "BANK_TRANSFER",
        "buyerIce": body.get("buyerIce"),
        "buyerName": body.get("buyerName"),
        "buyerAddress": body.get("buyerAddress"),
        "lines": body["lines"],
    })

    return JsonResponse({"id": invoice.id, "status": invoice.status})

Statuts de facture

Le statut reflète le cycle de vie complet, aligné avec le backend :

StatutSignification
PENDINGCréée, modifiable, pas encore transmise
SIGNEDSignée (XAdES), en attente de transmission
TRANSMITTEDEnvoyée à la plateforme DGI, en attente de clairance
ACCEPTEDValidée par la DGI — référence et QR code disponibles
REJECTEDRejetée par la DGI — voir errorCode / errorMessage
FAILEDÉchec technique de traitement
CANCELLEDAnnulée (uniquement possible depuis PENDING)