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-nodeInitialisation
Basique
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
efact.invoices.create(input: CreateInvoiceRequest): Promise<InvoiceResponse>Paramètres
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
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é serveurefact.invoices.retrieve(invoiceId)
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)
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 DGIefact.invoices.list(params?)
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)
// 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)
// 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 :
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
| Classe | Code HTTP | Quand |
|---|---|---|
EFactConfigError | - | Clé manquante ou option de configuration invalide |
EFactAuthError | 401 / 403 | Clé API manquante ou invalide |
EFactNotFoundError | 404 | Ressource introuvable (y compris hors tenant) |
EFactValidationError | 400 / 422 | Payload invalide |
EFactConflictError | 409 | Ex. modification/annulation d’une facture qui n’est plus PENDING |
EFactApiError | 5xx | Erreur 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
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 :
| Statut | Signification |
|---|---|
PENDING | Créée, modifiable, pas encore transmise |
SIGNED | Signée (XAdES), en attente de transmission |
TRANSMITTED | Envoyée à la plateforme DGI, en attente de clairance |
ACCEPTED | Validée par la DGI — référence et QR code disponibles |
REJECTED | Rejetée par la DGI — voir errorCode / errorMessage |
FAILED | Échec technique de traitement |
CANCELLED | Annulée (uniquement possible depuis PENDING) |