SDK Node.js / TypeScript - @itzenata/efact-node

SDK officiel EFact pour Node.js, compatible TypeScript natif. Toutes les opérations sur les factures sont exposées via efact.invoices.*.

Installation

npm install @itzenata/efact-node

Initialisation

Basique

Basic Initialization
import { EFact } from '@itzenata/efact-node'

// La clé secrète identifie ET authentifie votre organisation.
// Ne la committez jamais : chargez-la depuis l'environnement.
const efact = new EFact(process.env.EFACT_SECRET_KEY!)

efact.invoices.create(input)

Signature

Method Signature
efact.invoices.create(input: CreateInvoiceRequest): Promise<InvoiceResponse>

Paramètres

Parameters
interface CreateInvoiceLineRequest {
  lineNumber: number     // Requis, positif, unique dans la facture
  description: string    // Requis, <= 512 caractères
  quantity: number       // Requis, positif
  unitPrice: number      // Requis, >= 0
  vatRate: number        // Requis, >= 0 (pourcentage)
  unit?: string          // Optionnel, <= 32 caractères, code UN/ECE (ex. "HUR")
}

interface CreateInvoiceRequest {
  invoiceNumber: string                // Requis, <= 64 caractères
  invoiceDate: string                  // Requis, format YYYY-MM-DD
  currency: string                     // Requis, ISO 3 lettres majuscules (ex. "MAD")
  paymentMethod: CreateInvoiceRequestPaymentMethodEnum
                                       // Requis : 'BANK_TRANSFER' | 'CASH' | 'CHECK' | 'CARD' | 'OTHER'
  buyerIce?: string                    // Optionnel, exactement 15 chiffres si fourni
  buyerName?: string                   // Optionnel, raison sociale de l'acheteur
  buyerAddress?: string                // Optionnel, adresse de l'acheteur
  dueDate?: string                     // Optionnel, format YYYY-MM-DD
  lines: CreateInvoiceLineRequest[]    // Non vide
}

Exemple complet

Complete Example
import { EFact, CreateInvoiceRequestPaymentMethodEnum } from '@itzenata/efact-node'

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

const invoice = await efact.invoices.create({
  invoiceNumber: 'INV-2026-043',
  invoiceDate: '2026-06-19',
  currency: 'MAD',
  paymentMethod: CreateInvoiceRequestPaymentMethodEnum.BankTransfer,
  buyerIce: '002345678901234',
  buyerName: 'Client SARL',
  buyerAddress: '12 Rue Allal Ben Abdellah, Rabat',
  lines: [
    {
      lineNumber: 1,
      description: 'Licence logiciel annuelle',
      quantity: 2,
      unitPrice: 100,
      vatRate: 20,
    },
    {
      lineNumber: 2,
      description: 'Formation (exonérée TVA)',
      quantity: 1,
      unitPrice: 40,
      vatRate: 0,
    },
  ],
})

console.log(invoice.id)          // identifiant interne
console.log(invoice.status)      // 'PENDING'
console.log(invoice.amountTtc)   // montant total TTC, calculé côté serveur

efact.invoices.retrieve(invoiceId)

Retrieve Invoice
const invoice = await efact.invoices.retrieve('inv_01H8XYZABC')

if (invoice.status === InvoiceResponseStatusEnum.Accepted) {
  console.log('Référence DGI :', invoice.dgiReference)
  console.log('QR code DGI :', invoice.dgiQrCode)
}

if (invoice.status === InvoiceResponseStatusEnum.Rejected) {
  console.log('Code erreur :', invoice.errorCode)
  console.log('Message :', invoice.errorMessage)
}

Lève EFactNotFoundError (HTTP 404) si la facture n’existe pas ou n’appartient pas au tenant de la clé API utilisée.

efact.invoices.retrieveStatus(invoiceId)

Retrieve Status
const result = await efact.invoices.retrieveStatus('inv_01H8XYZABC')

console.log(result.status)           // InvoiceStatusResponseStatusEnum
console.log(result.dgiReference)     // référence de clairance DGI, si disponible
console.log(result.dgiQrPayload)     // contenu du QR code, si disponible
console.log(result.dgiErrorCode)     // code d'erreur DGI, si rejeté
console.log(result.dgiErrorMessage)  // message d'erreur DGI, si rejeté
console.log(result.dgiSubmittedAt)   // date de soumission à la DGI
console.log(result.dgiValidatedAt)   // date de validation par la DGI

efact.invoices.list(params?)

List Invoices
import { ListStatusEnum } from '@itzenata/efact-node'

const page = await efact.invoices.list({
  status: ListStatusEnum.Accepted,
  page: 0,
  size: 50,
  sort: 'createdAt,desc',
})

for (const invoice of page.content) {
  console.log(invoice.id, invoice.invoiceNumber)
}

Pagination par page/size, pas par curseur. La réponse expose content: InvoiceResponse[] ainsi que les métadonnées de pagination du backend. Filtrage par statut via ListStatusEnum.

efact.invoices.update(invoiceId, input)

Update Invoice
// Uniquement pour une facture au statut PENDING — sinon 409 (INVOICE_NOT_EDITABLE)
// Tous les champs sont optionnels — seuls les champs fournis sont appliqués.
const updated = await efact.invoices.update('inv_01H8XYZABC', {
  dueDate: '2026-07-31',
  buyerAddress: 'Nouvelle adresse, Casablanca',
})

efact.invoices.cancel(invoiceId)

Cancel Invoice
// Uniquement pour une facture au statut PENDING
const cancelled = await efact.invoices.cancel('inv_01H8XYZABC')
console.log(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/catch :

Error Handling
import {
  EFact,
  EFactError,
  EFactConfigError,
  EFactConnectionError,
  EFactTimeoutError,
  EFactApiError,
  EFactAuthError,
  EFactValidationError,
  EFactNotFoundError,
  EFactConflictError,
} from '@itzenata/efact-node'

try {
  const invoice = await efact.invoices.create({ /* ... */ })
} catch (error) {
  if (error instanceof EFactAuthError) {
    // Clé API manquante ou invalide — HTTP 401/403
    console.error('Authentification :', error.message)
  } else if (error instanceof EFactValidationError) {
    // Payload invalide — HTTP 400/422
    console.error('Validation :', error.code, error.errors)
  } else if (error instanceof EFactNotFoundError) {
    // Facture introuvable ou hors tenant — HTTP 404
    console.error('Introuvable :', error.message)
  } else if (error instanceof EFactConflictError) {
    // Ex. modification d'une facture qui n'est plus PENDING — HTTP 409
    console.error('Conflit :', error.message)
  } else if (error instanceof EFactTimeoutError) {
    console.error('Timeout après', error.timeoutMs, 'ms')
  } else if (error instanceof EFactConnectionError) {
    console.error('Réseau indisponible :', error.cause)
  } else if (error instanceof EFactApiError) {
    // Toute autre erreur applicative (5xx, etc.)
    console.error('Erreur API :', error.code, error.httpStatus)
  } else {
    throw error
  }
}

Hiérarchie des erreurs

ClasseCode HTTPQuand
EFactConfigError-Clé manquante ou option de configuration invalide
EFactAuthError401 / 403Clé API manquante ou invalide
EFactNotFoundError404Ressource introuvable (y compris hors tenant)
EFactValidationError400 / 422Payload invalide
EFactConflictError409Ex. modification/annulation d’une facture qui n’est plus PENDING
EFactApiError5xxErreur côté EFact non classifiée plus précisément
EFactTimeoutError-Délai dépassé avant réponse
EFactConnectionError-Échec réseau (DNS, connexion refusée, TLS...)

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

Intégration Next.js App Router

app/api/invoices/route.ts
// app/api/invoices/route.ts
import { EFact, CreateInvoiceRequestPaymentMethodEnum } from '@itzenata/efact-node'
import { NextRequest, NextResponse } from 'next/server'

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

export async function POST(req: NextRequest) {
  const body = await req.json()

  const invoice = await efact.invoices.create({
    invoiceNumber: body.invoiceNumber,
    invoiceDate: body.invoiceDate,
    currency: 'MAD',
    paymentMethod: CreateInvoiceRequestPaymentMethodEnum.BankTransfer,
    buyerIce: body.buyerIce,
    buyerName: body.buyerName,
    buyerAddress: body.buyerAddress,
    lines: body.lines,
  })

  return NextResponse.json({ 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)