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-validateThe 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.
// 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'); // trueThree 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.
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)],
]);No imports needed. Parameters override the package defaults in both directions.
return [
'phone' => ['required', 'tn_phone'],
'rib' => ['required', 'tn_rib:known_bank'],
'plate' => ['required', 'tn_car_plate:strict'],
'postal_code' => ['required', 'tn_postal_code'],
];
// :strict / :loose and :known_bank / :any_bank
// both override config/tn-validate.php.For one-off checks outside form validation — and for the derivations the validators expose.
TnValidate::formatPhoneNumber('+21620123456');
// '+216 20 123 456'
TnValidate::getBankInfoFromRIB('08003…')->name;
// 'Banque Internationale Arabe de Tunisie (BIAT)'
TnValidate::ibanFromEDinar('123456');
// 'TN5917000000000012345617'
TnValidate::getGovernorateFromPostalCode('3000');
// 'Sfax'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.
TunisianPhoneCIN
8 digits, first digit 0 or 1.
TunisianCinRIB
checksum20 digits with the MOD 97-10 key actually computed, plus bank lookup and component parsing.
TunisianRibIBAN
checksumTN + 22 digits, verified against the ISO 7064 checksum and the domestic RIB key.
TunisianIbanCar plate
Standard 123 تونس 4567 and special-series RS 123 تونس, with component parsing.
TunisianCarPlatePostal code
4 digits against the known governorate prefixes, with governorate lookup.
TunisianPostalCodeTax ID
7 digits + check letter, with the optional category/type/establishment suffix.
TunisianTaxIdRNE
The same base identifier as the matricule fiscal, without the suffix.
TunisianRnePassport
The modern letter + 6–7 digit format and the legacy 8-digit one.
TunisianPassporte-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