Tester votre intégration

La plateforme nationale DGI n'étant pas encore ouverte, EFact fonctionne comme un environnement de test grandeur nature : chaque facture traverse le pipeline réel (validation, UBL 2.1, signature XAdES) puis est clairée par notre simulateur DGI. Vous pouvez valider toute votre intégration aujourd'hui, sans aucune conséquence fiscale.

En bref

Le simulateur DGI d'EFact est le mode test par défaut : vos factures traversent le pipeline réel (validation, UBL 2.1, signature XAdES) sans transmission à la plateforme officielle. En juillet 2026, toute facture valide passe à ACCEPTEDenviron 5 secondes après transmission. Vous pouvez tester dès aujourd'hui les erreurs 400, 409, 401 et 429 ; le rejet DGI forcé est planifié.

Sandbox par conception : le simulateur reproduit le flux de clairance officiel de bout en bout - soumission, vérification de la signature, référence, QR code, callbacks. Le jour où la plateforme nationale ouvre, votre code ne change pas : seule la cible de transmission change, côté EFact. Voir Architecture - Environnements.

Le cycle nominal

Dans le sandbox, toute facture valide est acceptée : créez, suivez le statut jusqu'à ACCEPTED, récupérez la référence et le QR code.

Happy Path Test
import { EFact } from '@itzenata/efact-node'

const efact = new EFact(process.env.EFACT_SECRET_KEY!)

// 1. Créer : la facture revient déjà normalisée (UBL 2.1) et signée (XAdES)
const invoice = await efact.invoices.create({
  invoiceNumber: `TEST-${Date.now()}`,   // unique par organisation
  invoiceDate: '2026-07-14',
  currency: 'MAD',
  paymentMethod: 'BANK_TRANSFER',
  buyerIce: '002345678901234',
  buyerName: 'Client de test SARL',
  lines: [
    { lineNumber: 1, description: 'Test integration', quantity: 1, unitPrice: 100, vatRate: 20 },
  ],
})
console.log(invoice.status) // 'SIGNED'

// 2. Suivre la clairance : le simulateur répond en quelques secondes
let status = await efact.invoices.retrieveStatus(invoice.id)
while (status.status === 'SIGNED' || status.status === 'TRANSMITTED') {
  await new Promise((r) => setTimeout(r, 2000))
  status = await efact.invoices.retrieveStatus(invoice.id)
}

console.log(status.status)       // 'ACCEPTED'
console.log(status.dgiReference) // référence de clairance
console.log(status.dgiQrPayload) // contenu du QR code

Chronologie attendue

StatutQuand
SIGNEDImmédiat : la réponse du POST est déjà signée
TRANSMITTEDQuelques instants après la création (soumission au simulateur)
ACCEPTEDEnviron 5 secondes après la transmission (callback simulé). Si un incident transitoire survient, les planificateurs de rattrapage re-soumettent automatiquement (2 à 5 minutes)

Un intervalle de polling de 2 à 5 secondes suffit. Restez sous la limite de 300 requêtes/minute ; le SDK gère automatiquement les 429.

Les cas d'erreur à couvrir

Tous ces comportements sont reproductibles à la demande dans le sandbox :

CasComment le déclencherRésultat attendu
Payload invalideICE ≠ 15 chiffres, lines vide, devise invalide...400 VALIDATION_ERROR + détail par champ
Numéro dupliquéRejouer un create avec le même invoiceNumber409 INVOICE_NUMBER_EXISTS
Immuabilitéupdate ou cancel sur une facture signée409 INVOICE_NOT_EDITABLE
AuthentificationClé invalide ou absente401 API_KEY_INVALID / API_KEY_MISSING
Isolation tenantUUID d'une facture d'une autre organisation404 INVOICE_NOT_FOUND (jamais 403)
Rate limitingPlus de 300 requêtes/minute429 + Retry-After
Mentions légalesCréer une facture avant d'avoir complété ICE/IF/RC au dashboard409 MISSING_LEGAL_MENTIONS
Error Cases Test
import { EFactValidationError, EFactConflictError } from '@itzenata/efact-node'

// ICE invalide (14 chiffres au lieu de 15) -> 400 VALIDATION_ERROR
try {
  await efact.invoices.create({ ...input, buyerIce: '12345678901234' })
} catch (e) {
  if (e instanceof EFactValidationError) {
    console.log(e.errors) // [{ field: 'buyerIce', message: '...' }]
  }
}

// Numéro de facture déjà utilisé -> 409 INVOICE_NUMBER_EXISTS
try {
  await efact.invoices.create({ ...input, invoiceNumber: 'TEST-DUPLICATE' })
  await efact.invoices.create({ ...input, invoiceNumber: 'TEST-DUPLICATE' })
} catch (e) {
  if (e instanceof EFactConflictError) {
    console.log(e.code) // 'INVOICE_NUMBER_EXISTS'
  }
}

// Modification d'une facture déjà signée -> 409 INVOICE_NOT_EDITABLE
try {
  await efact.invoices.update(invoice.id, { buyerName: 'Autre nom' })
} catch (e) {
  if (e instanceof EFactConflictError) {
    console.log(e.code) // 'INVOICE_NOT_EDITABLE'
  }
}

Gérer les rejets DGIPlanifié

Aujourd'hui, le sandbox accepte toute facture valide : il n'est pas encore possible de forcer un REJECTED à la demande (des valeurs de test dédiées, à la Stripe, sont planifiées). Écrivez néanmoins votre gestion de rejet dès maintenant - le contrat est stable et documenté :

Rejection Handler
// Écrivez ce handler dès maintenant : le contrat de REJECTED est stable,
// même si le sandbox ne produit pas encore de rejet à la demande.
const status = await efact.invoices.retrieveStatus(invoice.id)

switch (status.status) {
  case 'ACCEPTED':
    // Facture cleared : archiver dgiReference + QR, marquer payable
    break
  case 'REJECTED':
    // Rejet de clairance : montrer dgiErrorCode / dgiErrorMessage
    // à l'utilisateur, corriger la facture, en émettre une nouvelle
    console.error(status.dgiErrorCode, status.dgiErrorMessage)
    break
  case 'FAILED':
    // Erreur technique irrécupérable : alerter, contacter le support
    break
}

En cas de rejet, une facture n'est pas modifiable : corrigez les données et émettez une nouvelle facture avec un nouveau numéro.

Vérifier les artefacts

Après ACCEPTED, vérifiez que votre intégration récupère et archive les artefacts :

Artifacts
// Une fois ACCEPTED, les artefacts sont téléchargeables :
GET /v1/invoices/{id}/pdf   # PDF de la facture
GET /v1/invoices/{id}/xml   # XML UBL 2.1 signé (original légal)
GET /v1/invoices/{id}/qr    # QR code de clairance (PNG)

Bonnes pratiques de test

  • Numéros uniques : préfixez vos numéros de test (TEST-...) et incluez un timestamp - le invoiceNumber est unique par organisation, pour toujours.
  • Ne rejouez jamais un create en aveugle : après un timeout réseau, rejouez avec le même numéro ; soit il réussit, soit le 409 confirme que la facture existe déjà.
  • Testez les statuts inattendus : votre code doit tolérer de voir SIGNED ou TRANSMITTED pendant plusieurs minutes (rattrapage automatique) sans paniquer.
  • Une clé API dédiée aux tests : créez une clé séparée pour vos environnements de test et révoquez-la sans risque.

Étapes suivantes

Questions fréquentes

Comment tester l'API EFact sans envoyer de vraies factures à la DGI ?

EFact fonctionne avec un simulateur DGI qui est le mode test par défaut : vos factures traversent le pipeline réel (validation, UBL 2.1, signature XAdES) puis sont clairées par le simulateur, sans transmission à la plateforme officielle. Vous testez ainsi le cycle complet en toute sécurité, sans conséquence fiscale.

Quel résultat renvoie le simulateur DGI d'EFact ?

En juillet 2026, toute facture valide est acceptée (ACCEPTED) environ 5 secondes après la transmission via un callback simulé. Vous pouvez tester dès aujourd'hui les erreurs de validation (400), l'immutabilité (409), l'authentification (401) et les limites de débit (429). Le déclenchement forcé d'un rejet DGI est planifié.

À quelle fréquence interroger le statut d'une facture en test ?

Un intervalle de polling de 2 à 5 secondes suffit : le simulateur répond en quelques secondes. Restez sous la limite de 300 requêtes par minute pour éviter une réponse 429. En production, les webhooks remplaceront le polling pour être notifié du statut final sans interrogation répétée.