tn-validate
v1.1.0Now supports Laravel 13

Tunisian data validation, checked properly.

Ten validators for Tunisian phone numbers, identity documents and bank accounts — as native Laravel Rule objects, on a core that does not import Laravel at all.

composer require ayoub-gaouet/tn-validate
PHP 8.3+Laravel 11, 12 & 13583 testsMIT

The part most libraries skip

A regex cannot tell you a RIB is wrong.

Every Tunisian RIB carries a two-digit key. It is an ISO 7064 MOD 97-10 check on the eighteen digits before it, which means a valid RIB is divisible by 97 — and that a single mistyped or transposed digit stops being valid.

This package computes it. That is also why every genuine Tunisian IBAN begins TN59: once the domestic part is divisible by 97, the IBAN check digits are forced to 59. The rule was cross-checked against five independently published, checksum-valid Tunisian IBANs.

a single wrong digit
// Real account structure, valid key.
TnValidate::validateRIB('08003000000000000026'); // true

// Same account, last digit off by one.
TnValidate::validateRIB('08003000000000000027'); // false

// Structure alone would have accepted both.
// The IBAN check runs twice over: ISO 7064
// on the IBAN, then the domestic RIB key.
TnValidate::validateIBAN('TN5908003000000000000026'); // true

Three ways in

Pick whichever fits the rules array you already have.

The modern Laravel idiom. Options are constructor arguments, so they are typed and discoverable.

app/Http/Requests/StoreClientRequest.php
use AyoubGaouet\TnValidate\Rules\TunisianCin;
use AyoubGaouet\TnValidate\Rules\TunisianRib;

$request->validate([
    'phone' => ['required', new TunisianPhone],
    'cin'   => ['required', new TunisianCin],
    'rib'   => ['required', new TunisianRib(requireKnownBank: true)],
]);

Laravel is optional

Every validator also exists as a plain class under Support/ with no framework dependency, so the same logic works in a console script or a non-Laravel project. That the core never imports Illuminate\ is asserted by a test, so it cannot quietly rot.

Ten validators

Each one available three ways, with messages in three languages.

  • Phone

    8-digit mobile numbers, +216 / 216 / 00216 prefixes, formatting and range-holder detection.

    TunisianPhone
  • CIN

    8 digits, first digit 0 or 1.

    TunisianCin
  • RIB

    checksum

    20 digits with the MOD 97-10 key actually computed, plus bank lookup and component parsing.

    TunisianRib
  • IBAN

    checksum

    TN + 22 digits, verified against the ISO 7064 checksum and the domestic RIB key.

    TunisianIban
  • Car plate

    Standard 123 تونس 4567 and special-series RS 123 تونس, with component parsing.

    TunisianCarPlate
  • Postal code

    4 digits against the known governorate prefixes, with governorate lookup.

    TunisianPostalCode
  • Tax ID

    7 digits + check letter, with the optional category/type/establishment suffix.

    TunisianTaxId
  • RNE

    The same base identifier as the matricule fiscal, without the suffix.

    TunisianRne
  • Passport

    The modern letter + 6–7 digit format and the legacy 8-digit one.

    TunisianPassport
  • e-Dinar / CCP

    La Poste account numbers, expandable into a full RIB or IBAN.

    TunisianEDinar

Error messages ship in English, French and Arabic, and follow the application locale — including a setLocale() call made after the container has booted.

What it does not do

The limits are documented, not buried.

These validators run against people’s identity documents and bank accounts. A rule that is wrong in the strict direction rejects real data from real people, so where a Tunisian format is not publicly specified this package validates shape only and says so.

  • Check characters that are not public

    The CIN, tax ID, RNE and passport check characters have no published algorithm, so those validators check shape only. A well-formed value is not proof the document exists.

  • Postal codes are prefix-level

    Codes are validated by governorate prefix, not against the full ~680-code list. 3099 passes because 30 is Sfax, even if no such office exists — chosen to avoid false negatives on legitimate but uncommon codes.

  • The bank table is not exhaustive

    New codes get assigned over time, so an unrecognised code is reported as an unknown bank rather than an invalid RIB — unless you opt in with requireKnownBank.

  • CCP length is inferred

    La Poste publishes no account-number length. The accepted range comes from their own documented example and the width of the RIB account field.

Auto-discovered. Nothing to register.

The service provider and the TnValidate facade register themselves through Laravel package discovery. Publishing the config and translations is optional.

composer require ayoub-gaouet/tn-validate