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

SDK officiel EFact pour Node.js, compatible TypeScript natif. Fonctionne avec Node.js, Next.js (App Router), Bun et Deno.

Installation

Installation
npm install @itzenata/efact-node
# ou
yarn add @itzenata/efact-node
# ou
bun add @itzenata/efact-node

Initialisation

Basique

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

const itz = new Itzenata(process.env.ITZENATA_SECRET_KEY)

Options de configuration

Configuration Options
const itz = new Itzenata(process.env.ITZENATA_SECRET_KEY, {
  apiVersion: 'v1',
  baseUrl: 'https://api.efact.itzenata.com',  // optionnel, utilisé pour les tests
  timeout: 30000,     // ms - défaut : 30s
  maxRetries: 2,      // nombre de retries en cas d'erreur réseau
})

itz.invoices.create(params)

Signature

Method Signature
itz.invoices.create(params: InvoiceCreateParams): Promise<Invoice>

Paramètres

Parameters
interface InvoiceCreateParams {
  amount: number             // centimes DH
  currency: 'MAD'
  customer: {
    ice: string              // 15 chiffres
    name: string
    address?: string
    email?: string
  }
  lines: Array<{
    description: string
    quantity: number
    unit_price: number       // centimes DH
    tax_rate?: 0 | 7 | 10 | 14 | 20   // défaut : 20
  }>
  metadata?: Record<string, string>
}

Exemple complet

Complete Example
const invoice = await itz.invoices.create({
  amount: 24000,
  currency: 'MAD',
  customer: {
    ice: '002345678901234',
    name: 'Client SARL',
    address: '12 Rue Allal Ben Abdellah, Rabat',
    email: 'finance@client.ma'
  },
  lines: [
    {
      description: 'Licence logiciel annuelle',
      quantity: 2,
      unit_price: 10000,
      tax_rate: 20
    },
    {
      description: 'Formation (exonéré TVA)',
      quantity: 1,
      unit_price: 4000,
      tax_rate: 0
    }
  ],
  metadata: {
    internal_id: 'INV-2026-043',
    client_ref: 'CLI-789'
  }
})

console.log(invoice.id)            // "inv_01H..."
console.log(invoice.clientSecret)  // "inv_01H..._secret_xxx" - à passer au frontend
console.log(invoice.status)        // "pending"

itz.invoices.retrieve(id)

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

if (invoice.status === 'accepted') {
  console.log('DGI ID :', invoice.dgiId)
  console.log('XML signé :', invoice.xmlUrl)
  console.log('PDF signé :', invoice.pdfUrl)
}

if (invoice.status === 'rejected') {
  console.log('Motif de rejet :', invoice.rejectionReason)
}

itz.invoices.list(params?)

List Invoices
const invoices = await itz.invoices.list({
  status: 'accepted',
  limit: 50,
  createdAfter: '2026-04-01T00:00:00Z'
})

for (const invoice of invoices.data) {
  console.log(invoice.id, invoice.metadata?.internal_id)
}

// Pagination
if (invoices.hasMore) {
  const nextPage = await itz.invoices.list({
    startingAfter: invoices.nextCursor
  })
}

itz.webhooks.constructEvent(payload, signature, secret)

Webhook Event Construction
const event = itz.webhooks.constructEvent(
  rawBody,       // string - body brut de la requête (ne pas parser en JSON avant)
  signature,     // string - header 'Itzenata-Signature'
  webhookSecret  // string - ITZENATA_WEBHOOK_SECRET
)

// event.type est discriminé
switch (event.type) {
  case 'invoice.accepted': {
    const { dgiId, xmlUrl, pdfUrl, metadata } = event.data
    break
  }
  case 'invoice.rejected': {
    const { rejectionReason, metadata } = event.data
    break
  }
}

Lève SignatureVerificationErrorsi la signature est invalide ou si le timestamp est trop ancien (> 5 minutes).

Gestion des erreurs

Le SDK lève des erreurs typées. Toujours envelopper les appels dans un try/catch :

Error Handling
import {
  ItzenataError,
  AuthenticationError,
  InvalidRequestError,
  APIError
} from '@itzenata/efact-node'

try {
  const invoice = await itz.invoices.create({ ... })
} catch (error) {
  if (error instanceof AuthenticationError) {
    // Clé API invalide - code 401
    console.error('Clé API invalide :', error.message)
  } else if (error instanceof InvalidRequestError) {
    // Payload invalide - code 400 ou 422
    console.error('Champ invalide :', error.param, error.message)
  } else if (error instanceof APIError) {
    // Erreur EFact - code 5xx
    console.error('Erreur serveur EFact :', error.status, error.message)
  } else {
    throw error
  }
}

Hiérarchie des erreurs

ClasseCode HTTPQuand
AuthenticationError401Clé API invalide ou manquante
PermissionError403Scope insuffisant
NotFoundError404Ressource introuvable
InvalidRequestError400 / 422Payload invalide
IdempotencyError409Conflit de clé d'idempotency
RateLimitError429Rate limit dépassé
APIError5xxErreur côté EFact
NetworkError-Timeout ou erreur réseau

Intégration Next.js App Router

API Route pour créer une Invoice

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

const itz = new Itzenata(process.env.ITZENATA_SECRET_KEY!)

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

  const invoice = await itz.invoices.create({
    amount: body.amount,
    currency: 'MAD',
    customer: body.customer,
    lines: body.lines,
    metadata: { internal_id: body.invoiceId }
  })

  return NextResponse.json({ clientSecret: invoice.clientSecret })
}

API Route pour les webhooks

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

const itz = new Itzenata(process.env.ITZENATA_SECRET_KEY!)

export async function POST(req: NextRequest) {
  const payload = await req.text()
  const sig = req.headers.get('itzenata-signature') ?? ''

  const event = itz.webhooks.constructEvent(
    payload, sig, process.env.ITZENATA_WEBHOOK_SECRET!
  )

  if (event.type === 'invoice.accepted') {
    // mettre à jour la base de données
  }

  return NextResponse.json({ received: true })
}

Types TypeScript exportés

Exported Types
import type {
  Invoice,
  InvoiceCreateParams,
  InvoiceListParams,
  WebhookEvent,
  InvoiceAcceptedEvent,
  InvoiceRejectedEvent,
  Customer,
  InvoiceLine,
} from '@itzenata/efact-node'