tn-einvoice-validator
v1.0.0Laravel 12 & 13, or no framework at all

TEIF invoices, validated without Java.

TTN publishes the El Fatoora schema in XSD 1.1, which PHP cannot read. This package ships a schema PHP can read, re-implements what the conversion removes, and adds the rules the schema never checked.

composer require ayoubgaouet/tn-einvoice-validator
PHP 8.2+Laravel 12 & 13415 testsen / fr / arMIT

Why this needed writing

libxml does not ignore XSD 1.1. It refuses.

TTN’s schema uses four XSD 1.1 constructs — two xs:assert and two xs:alternative. PHP’s libxml implements XSD 1.0, and it does not skip what it cannot parse: it declines to build the schema at all. That is why validating a TEIF invoice has meant a Java toolchain.

The four constructs are stripped by a script, not a hand edit, so a future revision can be re-processed and the delta against the official file stays auditable. What the strip removes is re-implemented as PHP rules — nothing is quietly lost.

You can also point the config straight at TTN’s original XSD 1.1 file. It is converted in memory, cached, and its remote signature import is redirected to a vendored copy so validation never touches the network.

TTN's schema, handed to libxml as published
// DOMDocument::schemaValidate() against the
// official facture_INVOIC_V1.8.8 file:

[3033] Element 'xs:element': The content is
       not valid. Expected is (annotation?,
       ((simpleType | complexType)?, …)).

[3033] Element 'xs:extension': The content is
       not valid. …

// Three schema-parse errors. Nothing in the
// invoice was examined at all.

// With this package, the same file:
XmlValidator::validate($xml)->isValid(); // true

Three layers, one pass

Every problem at once, not one per run.

A bare Xerces validator throws on the first SAXParseException, so a document with eight problems takes eight round trips. Here every layer that can run does run, and findings come back ordered by line — a fix list, read top to bottom.

  1. Well-formedness and encoding

    Is it XML at all, and is it UTF-8? If parsing fails nothing else can run, and the result says only that.

  2. The TEIF schema

    56 complex types, 30 simple types, 161 code lists, every length and occurrence constraint — enforced by libxml against the converted schema.

  3. Business rules

    Eight rule classes covering what the schema cannot express or does not check. Each names the clause of the specification it comes from.

The whole API

Three entry points and a result object.

A string, a file, or a document you have already parsed. The locale is per call, so a French API and an Arabic dashboard can share one validator.

anywhere
use TnEfacture\XmlValidator\Facades\XmlValidator;

$result = XmlValidator::validate($xml);
$result = XmlValidator::validateFile($path);
$result = XmlValidator::validateDocument($dom);

// Signed invoices use the other schema profile.
XmlValidator::validate($xml, 'signed', locale: 'ar');

Laravel is optional

Without a framework, use XmlValidator::make(). The service provider and the facade are the only classes that touch Illuminate\, and a test asserts it stays that way.

37 rules, each traced to a clause

A schema-valid invoice can still be wrong.

Much of the specification is written in the Guide and absent from the XSD. A validator that only runs the schema cannot see any of it.

both of these pass the official schema
<DateText functionCode="I-31" format="ddMMyy">HELLO!</DateText>
<PaiMeansCode>ZZZ</PaiMeansCode>

// This package:
// E_DATE_001  The date "HELLO!" does not match the
//             declared format "ddMMyy".
// E_REF_003   The payment means code "ZZZ" is not in
//             the official referential I-13.

Where the official documents disagree

Your data can be right and still be rejected.

The Guide describes the matricule fiscal in five parts. On three of them the schema is narrower than the Guide that documents it.

A forfaitaire taxpayer, or a secondary establishment, therefore holds a legitimate identifier that TTN’s own schema refuses. The error still stands — TTN runs the schema, so the invoice really will come back — but a warning names the exact part that diverges, so you know the problem is not your data.

PartGuide §5.2.1.1XSD 1.8.8
Identifier7 digits7 digits
Control keyA–Z except I, O, UA–Z except I, O, U
VAT codeA P B F NA B D N P
Category codeM P C N EC M N P
Establishment no.000, 001, 002…000 only
W_MF_002warning

“The identifier 1427326WFM000 conforms to the Guide d’Implémentation TEIF (§5.2.1.1) but is rejected by TTN’s XSD 1.8.8, which does not accept the VAT code “F”. TTN will reject this invoice.”

English, French, Arabic

The code never changes. Only the sentence does.

Even the schema errors are translated: libxml only speaks English, and in XML Schema vocabulary, so its wording is parsed back into structured parameters and re-rendered. The French follows the specification’s own terms; the Arabic follows the ones used officially in Tunisia.

enE_REF_003

The payment means code “ZZZ” is not in the official referential I-13 (Payment Means).

frE_REF_003

Le code de mode de paiement « ZZZ » ne figure pas dans le référentiel officiel I-13 (Payment Means).

arE_REF_003

رمز وسيلة الخلاص «ZZZ» لا يوجد في المرجعية الرسمية I-13 (Payment Means).

What it does not do

The limits are documented, not buried.

This runs against invoices that carry tax liabilities and go to a government platform. Where the specification is silent or contradicts itself, the package says so rather than guessing — and a rule that is only inferred is a warning, never an error.

  • Signatures are checked structurally, not cryptographically

    The identifiers, algorithms and required blocks are verified. Digests are not recomputed and the certificate chain is not validated — that needs canonicalisation and revocation checking, and TTN performs it on receipt. A document can pass here and still carry an invalid signature.

  • The amount checks are inferred, so they only warn

    The Guide defines what each amount type means but states no formula anywhere — no sum, no total to reconcile, no rounding method. The arithmetic checks are derived from those definitions and hold exactly on TTN's published example, but nothing mandates them, so they never make a document invalid.

  • It is stricter than TTN's schema, on purpose

    Fourteen of the fifty-five test fixtures are rejected here and accepted by a schema-only validator. Each one is a rule the Guide states in prose, listed with its clause in the parity manifest. If you need bare schema parity, the rules are configurable.

  • One documented rule is deliberately not enforced

    Tableau 4 of the Guide marks LinDtm mandatory. The schema makes it optional and TTN's own published example omits it entirely, so enforcing it — even as a warning — would fire on conforming invoices.

Auto-discovered. Nothing to register.

The service provider and the XmlValidator facade register themselves through Laravel package discovery. The schemas and the three languages ship inside the package; publishing anything is optional.

composer require ayoubgaouet/tn-einvoice-validator