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-nodeInitialisation
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
| Classe | Code HTTP | Quand |
|---|---|---|
AuthenticationError | 401 | Clé API invalide ou manquante |
PermissionError | 403 | Scope insuffisant |
NotFoundError | 404 | Ressource introuvable |
InvalidRequestError | 400 / 422 | Payload invalide |
IdempotencyError | 409 | Conflit de clé d'idempotency |
RateLimitError | 429 | Rate limit dépassé |
APIError | 5xx | Erreur 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'