Intégrer l'API TTN dans Votre Système : Guide Technique Développeur

Ce guide s'adresse aux développeurs et architectes techniques chargés d'intégrer la facturation électronique TTN dans une application existante. Nous couvrons l'architecture complète, les trois stratégies d'intégration, et fournissons des exemples de code concrets en Node.js/TypeScript pour chaque étape clé : génération du XML TEIF, signature TUNTRUST, soumission API et gestion des réponses.
Ce tutoriel est le septième épisode de notre série Facturation Électronique en Tunisie. Il suppose que vous avez déjà lu l'épisode 5 : Format TEIF et Spécifications Techniques et que votre entreprise est inscrite sur la plateforme El Fatoora.
Architecture globale du système
Avant d'écrire la première ligne de code, il est essentiel de comprendre comment votre système s'articule avec les infrastructures de TTN et de la DGI.
┌─────────────────────────────────────────────────┐
│ VOTRE SYSTÈME │
│ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ ERP/CRM │──►│ Générateur│──►│ Module TTN │ │
│ │ (données)│ │ XML TEIF │ │ (signature │ │
│ └──────────┘ └──────────┘ │ + soumission│ │
│ └──────┬────────┘ │
└────────────────────────────────────────│───────────┘
│ HTTPS/REST
▼
┌──────────────────────────┐
│ API TTN (El Fatoora) │
│ api.elfatoora.tn │
│ ┌──────────────────────┐ │
│ │ Validation TEIF │ │
│ │ Vérification signature│ │
│ │ Horodatage TTN │ │
│ │ Archivage │ │
│ └──────────┬───────────┘ │
└─────────────│──────────────┘
│ Transmission automatique
▼
┌──────────────────────────┐
│ DGI (Impôts) │
│ Déclaration TVA │
│ Suivi fiscal │
└──────────────────────────┘
Points d'interaction clés :
- Votre système génère le XML TEIF à partir de vos données métier
- La signature numérique avec le certificat TUNTRUST est effectuée côté client (votre serveur)
- L'API TTN valide le format, la signature, et archive la facture
- TTN transmet automatiquement les données à la DGI — vous n'avez pas à interagir directement avec la DGI pour chaque facture
Configuration et authentification
Prérequis techniques
Avant de commencer l'intégration, assurez-vous de disposer de :
- Node.js v18+ (v20 recommandé)
- Certificat PKCS#12 (
.p12) délivré par TUNTRUST, avec son mot de passe - Identifiants API TTN (client_id et client_secret) obtenus après l'inscription en mode EDI
- Accès au sandbox TTN :
https://api-sandbox.elfatoora.tn
Installation des dépendances
npm install xmlbuilder2 node-forge axios dotenv qrcode
npm install --save-dev @types/node typescript tsxxmlbuilder2: construction de documents XMLnode-forge: manipulation de certificats PKCS#12 et signature XMLaxios: client HTTP pour l'API TTNqrcode: génération de QR codes
Variables d'environnement
# .env
TTN_API_BASE_URL=https://api-sandbox.elfatoora.tn
TTN_CLIENT_ID=votre_client_id_ttn
TTN_CLIENT_SECRET=votre_client_secret_ttn
# Certificat TUNTRUST
TUNTRUST_CERT_PATH=/path/to/votre-certificat.p12
TUNTRUST_CERT_PASSWORD=votre_mot_de_passe_certificat
# Infos émetteur
SUPPLIER_TAX_ID=12345678A000000
SUPPLIER_NAME=Votre Société SARLOption 1 : Intégration directe API TTN
L'intégration directe est la solution la plus flexible mais aussi la plus complexe à mettre en oeuvre. Vous gérez entièrement le cycle de vie de la facture : génération XML, signature, soumission et gestion des réponses.
Étape 1 : Authentification OAuth2
L'API TTN utilise OAuth2 avec le flux client_credentials :
// src/ttn/auth.ts
import axios from "axios";
interface TTNTokenResponse {
access_token: string;
token_type: string;
expires_in: number;
}
let cachedToken: { token: string; expiresAt: number } | null = null;
export async function getTTNAccessToken(): Promise<string> {
// Réutiliser le token s'il est encore valide (avec marge de 60s)
if (cachedToken && Date.now() < cachedToken.expiresAt - 60000) {
return cachedToken.token;
}
const response = await axios.post<TTNTokenResponse>(
`${process.env.TTN_API_BASE_URL}/api/v1/auth/token`,
new URLSearchParams({
grant_type: "client_credentials",
client_id: process.env.TTN_CLIENT_ID!,
client_secret: process.env.TTN_CLIENT_SECRET!,
scope: "invoices:write invoices:read",
}),
{ headers: { "Content-Type": "application/x-www-form-urlencoded" } }
);
cachedToken = {
token: response.data.access_token,
expiresAt: Date.now() + response.data.expires_in * 1000,
};
return cachedToken.token;
}Étape 2 : Génération du XML TEIF
// src/ttn/teif-generator.ts
import { create } from "xmlbuilder2";
export interface InvoiceLine {
id: number;
description: string;
quantity: number;
unitCode: string;
unitPrice: number; // HT
vatRate: number; // ex: 19 pour 19%
}
export interface InvoiceData {
invoiceNumber: string;
issueDate: string; // YYYY-MM-DD
dueDate: string;
supplierTaxId: string;
supplierName: string;
supplierAddress: string;
supplierCity: string;
supplierPostalCode: string;
customerTaxId: string;
customerName: string;
customerAddress: string;
customerCity: string;
customerPostalCode: string;
lines: InvoiceLine[];
notes?: string;
}
function formatAmount(amount: number): string {
return amount.toFixed(3);
}
export function generateTEIF(invoice: InvoiceData): string {
// Calculs des totaux
const lineCalculations = invoice.lines.map((line) => {
const lineTotal = line.quantity * line.unitPrice;
const vatAmount = (lineTotal * line.vatRate) / 100;
return { ...line, lineTotal, vatAmount };
});
const subtotalHT = lineCalculations.reduce((s, l) => s + l.lineTotal, 0);
const totalVAT = lineCalculations.reduce((s, l) => s + l.vatAmount, 0);
const totalTTC = subtotalHT + totalVAT;
// Construction du document XML
const doc = create({ version: "1.0", encoding: "UTF-8" })
.ele("Invoice", {
xmlns: "urn:tn:gov:dgi:teif:1.8",
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
"xsi:schemaLocation":
"urn:tn:gov:dgi:teif:1.8 TEIF_v1.8.7.xsd",
});
// Header
const header = doc.ele("Header");
header.ele("InvoiceID").txt(invoice.invoiceNumber);
header.ele("IssueDate").txt(invoice.issueDate);
header.ele("IssueTime").txt(new Date().toTimeString().split(" ")[0]);
header.ele("InvoiceTypeCode").txt("380");
header.ele("DocumentCurrencyCode").txt("TND");
header.ele("TaxCurrencyCode").txt("TND");
header.ele("DueDate").txt(invoice.dueDate);
if (invoice.notes) {
header.ele("Note").txt(invoice.notes);
}
// Parties
const parties = doc.ele("Parties");
const supplier = parties.ele("Supplier");
supplier.ele("PartyIdentification").ele("ID", { schemeID: "TN_MF" })
.txt(invoice.supplierTaxId);
supplier.ele("PartyName").ele("Name").txt(invoice.supplierName);
const supplierAddr = supplier.ele("PostalAddress");
supplierAddr.ele("StreetName").txt(invoice.supplierAddress);
supplierAddr.ele("CityName").txt(invoice.supplierCity);
supplierAddr.ele("PostalZone").txt(invoice.supplierPostalCode);
supplierAddr.ele("Country").ele("IdentificationCode").txt("TN");
const customer = parties.ele("Customer");
customer.ele("PartyIdentification").ele("ID", { schemeID: "TN_MF" })
.txt(invoice.customerTaxId);
customer.ele("PartyName").ele("Name").txt(invoice.customerName);
const customerAddr = customer.ele("PostalAddress");
customerAddr.ele("StreetName").txt(invoice.customerAddress);
customerAddr.ele("CityName").txt(invoice.customerCity);
customerAddr.ele("PostalZone").txt(invoice.customerPostalCode);
customerAddr.ele("Country").ele("IdentificationCode").txt("TN");
// Lignes de facturation
const lines = doc.ele("InvoiceLines");
for (const line of lineCalculations) {
const invoiceLine = lines.ele("InvoiceLine");
invoiceLine.ele("ID").txt(String(line.id));
invoiceLine.ele("InvoicedQuantity", { unitCode: line.unitCode })
.txt(String(line.quantity));
invoiceLine.ele("LineExtensionAmount", { currencyID: "TND" })
.txt(formatAmount(line.lineTotal));
const item = invoiceLine.ele("Item");
item.ele("Name").txt(line.description);
const taxCat = item.ele("ClassifiedTaxCategory");
taxCat.ele("ID").txt("S");
taxCat.ele("Percent").txt(String(line.vatRate));
taxCat.ele("TaxScheme").ele("ID").txt("TVA");
const price = invoiceLine.ele("Price");
price.ele("PriceAmount", { currencyID: "TND" })
.txt(formatAmount(line.unitPrice));
price.ele("BaseQuantity", { unitCode: line.unitCode }).txt("1");
}
// Totaux TVA
const taxTotal = doc.ele("TaxTotal");
taxTotal.ele("TaxAmount", { currencyID: "TND" }).txt(formatAmount(totalVAT));
const taxSubtotal = taxTotal.ele("TaxSubtotal");
taxSubtotal.ele("TaxableAmount", { currencyID: "TND" })
.txt(formatAmount(subtotalHT));
taxSubtotal.ele("TaxAmount", { currencyID: "TND" })
.txt(formatAmount(totalVAT));
const taxCategory = taxSubtotal.ele("TaxCategory");
taxCategory.ele("ID").txt("S");
taxCategory.ele("Percent").txt("19");
taxCategory.ele("TaxScheme").ele("ID").txt("TVA");
// Montants globaux
const monetary = doc.ele("LegalMonetaryTotal");
monetary.ele("LineExtensionAmount", { currencyID: "TND" })
.txt(formatAmount(subtotalHT));
monetary.ele("TaxExclusiveAmount", { currencyID: "TND" })
.txt(formatAmount(subtotalHT));
monetary.ele("TaxInclusiveAmount", { currencyID: "TND" })
.txt(formatAmount(totalTTC));
monetary.ele("PayableAmount", { currencyID: "TND" })
.txt(formatAmount(totalTTC));
return doc.end({ prettyPrint: true });
}Étape 3 : Signature avec le certificat TUNTRUST
// src/ttn/signer.ts
import * as forge from "node-forge";
import * as fs from "fs";
import { create } from "xmlbuilder2";
interface SigningResult {
signedXml: string;
signatureValue: string;
certificateBase64: string;
}
export function signTEIF(xmlContent: string): SigningResult {
// Charger le certificat PKCS#12
const p12Buffer = fs.readFileSync(process.env.TUNTRUST_CERT_PATH!);
const p12Der = forge.util.createBuffer(p12Buffer.toString("binary"));
const p12Asn1 = forge.asn1.fromDer(p12Der);
const p12 = forge.pkcs12.pkcs12FromAsn1(
p12Asn1,
process.env.TUNTRUST_CERT_PASSWORD!
);
// Extraire la clé privée et le certificat
const bags = p12.getBags({
bagType: forge.pki.oids.pkcs8ShroudedKeyBag,
});
const keyBag = bags[forge.pki.oids.pkcs8ShroudedKeyBag]?.[0];
if (!keyBag?.key) throw new Error("Clé privée introuvable dans le PKCS#12");
const privateKey = keyBag.key;
const certBags = p12.getBags({ bagType: forge.pki.oids.certBag });
const certBag = certBags[forge.pki.oids.certBag]?.[0];
if (!certBag?.cert) throw new Error("Certificat introuvable dans le PKCS#12");
const certificate = certBag.cert;
// Canonicaliser le XML (C14N simplifié pour cet exemple)
const xmlBuffer = Buffer.from(xmlContent, "utf8");
// Calculer le digest SHA-256 du document
const md = forge.md.sha256.create();
md.update(xmlBuffer.toString("binary"));
const digestValue = Buffer.from(md.digest().bytes(), "binary").toString(
"base64"
);
// Signer le digest avec la clé privée RSA
const signatureMd = forge.md.sha256.create();
signatureMd.update(xmlBuffer.toString("binary"));
const signatureBytes = privateKey.sign(signatureMd);
const signatureValue = Buffer.from(signatureBytes, "binary").toString(
"base64"
);
// Encoder le certificat en Base64
const certDer = forge.asn1.toDer(forge.pki.certificateToAsn1(certificate));
const certificateBase64 = Buffer.from(certDer.bytes(), "binary").toString(
"base64"
);
// Construire la section Signature XML et l'injecter dans le document
const signatureXml = `
<Signature Id="TEIF-SIG-001" xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>${digestValue}</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>${signatureValue}</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>${certificateBase64}</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>`;
// Insérer la signature avant la fermeture de <Invoice>
const signedXml = xmlContent.replace(
"</Invoice>",
`${signatureXml}\n</Invoice>`
);
return { signedXml, signatureValue, certificateBase64 };
}Étape 4 : Génération du QR Code
// src/ttn/qrcode-generator.ts
import QRCode from "qrcode";
import crypto from "crypto";
export async function generateInvoiceQRCode(params: {
invoiceId: string;
supplierTaxId: string;
issueDate: string;
totalAmount: number;
signatureValue: string;
}): Promise<string> {
// Générer un hash court pour le paramètre sig (8 premiers caractères du hash SHA256)
const sigHash = crypto
.createHash("sha256")
.update(params.signatureValue)
.digest("hex")
.substring(0, 8);
// Générer l'identifiant TTN de la facture (hash de l'ID facture + matricule)
const iid = crypto
.createHash("sha256")
.update(`${params.invoiceId}:${params.supplierTaxId}`)
.digest("hex")
.substring(0, 12);
// Formater la date pour le QR code (YYYYMMDDHHMMSS)
const dt = params.issueDate.replace(/-/g, "") + "000000";
const verifyUrl = new URL("https://verify.elfatoora.tn/v");
verifyUrl.searchParams.set("iid", iid);
verifyUrl.searchParams.set("sid", params.supplierTaxId);
verifyUrl.searchParams.set("dt", dt);
verifyUrl.searchParams.set("amt", params.totalAmount.toFixed(3));
verifyUrl.searchParams.set("sig", sigHash);
// Générer le QR code en Base64 (pour intégration dans XML ou PDF)
const qrCodeDataUrl = await QRCode.toDataURL(verifyUrl.toString(), {
errorCorrectionLevel: "M",
type: "image/png",
width: 200,
margin: 1,
});
// Retourner seulement la partie Base64 (sans le préfixe data:image/png;base64,)
return qrCodeDataUrl.replace("data:image/png;base64,", "");
}Étape 5 : Soumission à l'API TTN et gestion des réponses
// src/ttn/client.ts
import axios, { AxiosError } from "axios";
import { getTTNAccessToken } from "./auth";
export interface TTNSubmitResponse {
invoiceId: string; // ID TTN interne
status: "SUBMITTED" | "VALID" | "INVALID";
validationErrors?: Array<{
code: string;
field: string;
message: string;
}>;
timestamp: string;
acknowledgementUrl?: string;
}
export class TTNApiError extends Error {
constructor(
public readonly code: string,
public readonly details: string,
public readonly httpStatus: number
) {
super(`TTN API Error [${code}]: ${details}`);
}
}
export async function submitInvoice(
signedXml: string
): Promise<TTNSubmitResponse> {
const token = await getTTNAccessToken();
try {
const response = await axios.post<TTNSubmitResponse>(
`${process.env.TTN_API_BASE_URL}/api/v1/invoices`,
signedXml,
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/xml",
Accept: "application/json",
},
timeout: 30000, // 30 secondes
}
);
return response.data;
} catch (error) {
if (error instanceof AxiosError && error.response) {
const { status, data } = error.response;
// Erreurs de validation TEIF (400)
if (status === 400) {
throw new TTNApiError(
data.code || "VALIDATION_ERROR",
JSON.stringify(data.errors || data.message),
status
);
}
// Erreur d'authentification (401)
if (status === 401) {
// Invalider le cache du token et réessayer
throw new TTNApiError("AUTH_EXPIRED", "Token expiré, réessayez", 401);
}
// Quota dépassé (429)
if (status === 429) {
const retryAfter = error.response.headers["retry-after"] || "60";
throw new TTNApiError(
"RATE_LIMIT",
`Quota API dépassé. Réessayer dans ${retryAfter}s`,
429
);
}
throw new TTNApiError(
data.code || "API_ERROR",
data.message || "Erreur API inconnue",
status
);
}
throw error;
}
}
export async function getInvoiceStatus(
ttnInvoiceId: string
): Promise<{ status: string; updatedAt: string }> {
const token = await getTTNAccessToken();
const response = await axios.get(
`${process.env.TTN_API_BASE_URL}/api/v1/invoices/${ttnInvoiceId}`,
{ headers: { Authorization: `Bearer ${token}` } }
);
return response.data;
}Orchestration : le service complet
// src/ttn/invoice-service.ts
import { generateTEIF, InvoiceData } from "./teif-generator";
import { signTEIF } from "./signer";
import { generateInvoiceQRCode } from "./qrcode-generator";
import { submitInvoice, TTNApiError } from "./client";
export interface InvoiceSubmissionResult {
ttnInvoiceId: string;
status: string;
submittedAt: string;
signatureValue: string;
qrCodeBase64: string;
}
export async function createAndSubmitInvoice(
invoiceData: InvoiceData
): Promise<InvoiceSubmissionResult> {
console.log(`[TTN] Génération de la facture ${invoiceData.invoiceNumber}...`);
// 1. Générer le XML TEIF
const xmlContent = generateTEIF(invoiceData);
// 2. Signer le document
console.log("[TTN] Signature du document XML...");
const { signedXml, signatureValue } = signTEIF(xmlContent);
// 3. Calculer le total TTC pour le QR code
const totalTTC = invoiceData.lines.reduce((sum, line) => {
const lineTotal = line.quantity * line.unitPrice;
return sum + lineTotal + (lineTotal * line.vatRate) / 100;
}, 0);
// 4. Générer le QR code
console.log("[TTN] Génération du QR code...");
const qrCodeBase64 = await generateInvoiceQRCode({
invoiceId: invoiceData.invoiceNumber,
supplierTaxId: invoiceData.supplierTaxId,
issueDate: invoiceData.issueDate,
totalAmount: totalTTC,
signatureValue,
});
// 5. Soumettre à l'API TTN avec retry automatique
let lastError: Error | null = null;
for (let attempt = 1; attempt <= 3; attempt++) {
try {
console.log(`[TTN] Soumission à l'API (tentative ${attempt}/3)...`);
const response = await submitInvoice(signedXml);
console.log(
`[TTN] Facture soumise avec succès. ID TTN: ${response.invoiceId}`
);
return {
ttnInvoiceId: response.invoiceId,
status: response.status,
submittedAt: response.timestamp,
signatureValue,
qrCodeBase64,
};
} catch (error) {
if (error instanceof TTNApiError) {
// Erreurs non récupérables — ne pas retenter
if (error.httpStatus === 400 || error.httpStatus === 401) {
throw error;
}
// Erreur 429 ou 5xx — attendre et retenter
lastError = error;
const delay = attempt * 2000; // 2s, 4s, 6s
console.warn(
`[TTN] Erreur ${error.code}, nouvel essai dans ${delay}ms...`
);
await new Promise((resolve) => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
throw lastError || new Error("Échec de soumission après 3 tentatives");
}Option 2 : Middleware NGSign API
Si vous ne souhaitez pas gérer la cryptographie de signature directement, TTN propose un service intermédiaire appelé NGSign API. Ce middleware prend en charge la signature numérique et simplifie considérablement l'intégration.
Avantages :
- Pas de gestion locale du certificat PKCS#12
- Mise à jour automatique lors des changements de standards de signature
- Service SaaS géré par TTN
Flux NGSign :
Votre système
│ (XML TEIF non signé)
▼
NGSign API (TTN)
│ (signature + horodatage)
▼
API El Fatoora (TTN)
│ (validation + archivage)
▼
Réponse à votre système
// Soumission via NGSign (sans gestion locale de la signature)
async function submitViaNGSign(xmlContent: string): Promise<string> {
const token = await getTTNAccessToken();
const response = await axios.post(
`${process.env.TTN_API_BASE_URL}/api/v1/ngsign/submit`,
{
xmlContent: Buffer.from(xmlContent).toString("base64"),
certificateAlias: process.env.TUNTRUST_CERT_ALIAS, // Alias pré-configuré dans NGSign
signingMode: "SERVER", // TTN signe avec le certificat stocké chez eux
},
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
}
);
return response.data.invoiceId;
}Note : NGSign nécessite de déposer votre certificat TUNTRUST auprès de TTN, ce qui pose des questions de sécurité (la clé privée quitte votre contrôle). Évaluez ce compromis selon votre politique de sécurité.
Option 3 : Développement personnalisé complet
Pour les grandes entreprises avec des besoins spécifiques (multi-entités, intégration ERP complexe, volumes très élevés), une architecture personnalisée peut s'imposer.
Architecture type :
ERP / Système source
│ (événements de création de factures)
▼
Message Queue (Redis / RabbitMQ)
│
▼
Worker Pool TTN (Node.js / microservice)
├── Générateur TEIF
├── Module de signature (HSM ou PKCS#12)
├── Client API TTN
└── Gestionnaire d'erreurs / retry
│
▼
Base de données (PostgreSQL)
├── Statuts des factures
├── Accusés de réception TTN
└── Logs d'audit
Cette architecture permet de traiter des milliers de factures par jour avec une fiabilité maximale, une traçabilité complète et une reprise sur erreur automatique.
Tests avec le sandbox TTN
Avant de basculer en production, vous devez impérativement valider votre intégration sur l'environnement de test TTN.
Configuration du sandbox
// src/config.ts
export const TTN_CONFIG = {
baseUrl:
process.env.NODE_ENV === "production"
? "https://api.elfatoora.tn"
: "https://api-sandbox.elfatoora.tn",
timeoutMs: 30000,
maxRetries: 3,
};Scénarios de test obligatoires
TTN exige de valider au minimum les scénarios suivants en sandbox avant l'activation de la production :
// src/tests/ttn-sandbox.test.ts
import { createAndSubmitInvoice } from "../ttn/invoice-service";
const baseInvoiceData = {
supplierTaxId: process.env.SUPPLIER_TAX_ID!,
supplierName: process.env.SUPPLIER_NAME!,
supplierAddress: "Adresse Test",
supplierCity: "Tunis",
supplierPostalCode: "1000",
};
// Test 1 : Facture standard avec TVA 19%
async function testStandardInvoice() {
const result = await createAndSubmitInvoice({
...baseInvoiceData,
invoiceNumber: `TEST-${Date.now()}`,
issueDate: new Date().toISOString().split("T")[0],
dueDate: new Date(Date.now() + 30 * 86400000).toISOString().split("T")[0],
customerTaxId: "99999999A000TEST",
customerName: "Client Test Sandbox",
customerAddress: "Adresse Client Test",
customerCity: "Sfax",
customerPostalCode: "3000",
lines: [
{
id: 1,
description: "Service Test",
quantity: 1,
unitCode: "C62",
unitPrice: 100.0,
vatRate: 19,
},
],
});
console.assert(result.status === "VALID", "La facture test doit être VALID");
console.log("Test 1 réussi :", result.ttnInvoiceId);
}
// Test 2 : Avoir (note de crédit)
async function testCreditNote() {
// Similaire mais avec InvoiceTypeCode = "381"
// ...
}
// Test 3 : Facture multi-lignes avec TVA mixte
async function testMixedVATInvoice() {
// Lignes avec TVA 19% et 7%
// ...
}
// Exécuter tous les tests
(async () => {
await testStandardInvoice();
await testCreditNote();
await testMixedVATInvoice();
console.log("Tous les tests sandbox réussis !");
})();Checklist de déploiement en production
Avant le passage en production, validez chaque point de cette liste :
Sécurité :
- Le certificat TUNTRUST est stocké dans un coffre-fort sécurisé (AWS Secrets Manager, HashiCorp Vault ou equivalent)
- Le mot de passe du certificat n'est jamais en clair dans le code source ou les logs
- Les identifiants API TTN sont dans des variables d'environnement, hors du dépôt Git
- Les accès au service de facturation sont protégés par authentification interne
Technique :
- Tous les scénarios de test sandbox sont passés avec le statut
VALID - La gestion des erreurs couvre les codes 400, 401, 429, 500, 503
- Le mécanisme de retry avec backoff exponentiel est implémenté
- Les logs incluent l'ID TTN de chaque facture soumise
- La base de données conserve le statut et l'accusé de réception pour chaque facture
Légal et conformité :
- L'accès production TTN a été activé par TTN après validation des tests
- La déclaration d'adhésion DGI a été déposée
- La stratégie d'archivage (5 ans minimum) est en place
Opérationnel :
- Les alertes sont configurées pour les erreurs de soumission
- Un tableau de bord de suivi des factures est disponible pour le service comptabilité
- La procédure de régularisation en cas d'erreur est documentée
Navigation dans la série
- Épisode précédent : Intégration dans votre logiciel de comptabilité (à venir)
- Épisode suivant : Archivage et obligations légales post-facturation (à venir)
Pour revenir aux fondamentaux techniques, consultez l'épisode 5 : Format TEIF et Spécifications Techniques.
Votre entreprise souhaite intégrer la facturation électronique TTN sans mobiliser vos ressources de développement internes ? Noqta.tn propose une solution clé en main — de l'analyse de votre système existant jusqu'au déploiement en production, en passant par la formation de vos équipes. Nous avons accompagné des dizaines d'entreprises tunisiennes dans cette transition.
Contactez notre équipe technique pour un diagnostic gratuit de votre système et une estimation de votre projet d'intégration.
Discutez de votre projet avec nous
Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.
Trouvons les meilleures solutions pour vos besoins.
Articles connexes

Format TEIF et Spécifications Techniques de la Facturation Électronique en Tunisie
Guide technique du format TEIF (Tunisian Electronic Invoice Format) : structure XML/XSD, champs obligatoires, signature numérique, QR code et modes d'intégration Web et EDI.

Inscription à El Fatoora : Guide Pratique Étape par Étape
Guide complet étape par étape pour s'inscrire à la plateforme El Fatoora de Tunisie TradeNet : prérequis, documents nécessaires, portail d'adhésion et validation technique.

Construire un Agent IA Autonome avec Agentic RAG et Next.js
Apprenez a construire un agent IA qui decide de maniere autonome quand et comment recuperer des informations depuis des bases de donnees vectorielles. Un guide pratique complet avec Vercel AI SDK et Next.js, accompagne d'exemples executables.