Sheba and bank card

Validate Sheba (IBAN) numbers with ISO 7064 mod-97-10 and bank card numbers with the Luhn checksum, and look up the issuing bank. The page says where the bank data comes from.

On this page
  1. Quick start
  2. Sheba (IBAN)
    1. Format
    2. Cleaning the input
    3. What is checked
  3. Bank card number
    1. Format and checks
  4. Bank lookup data
  5. Practical notes

Quick start#

<?php
require 'vendor/autoload.php';

use RtlyKit\Validation\BankCard;
use RtlyKit\Validation\Sheba;
use function RtlyKit\is_bank_card;
use function RtlyKit\is_sheba;

var_dump(Sheba::isValid('IR800170000000000000000123'));   // bool(true)
var_dump(is_sheba('800170000000000000000123'));           // bool(true), "IR" added for you
var_dump(BankCard::isValid('6037-9912-3456-7893'));       // bool(true)
var_dump(is_bank_card('۶۰۳۷۹۹۱۲۳۴۵۶۷۸۹۳'));               // bool(true), Persian digits

echo Sheba::getBankName('IR800170000000000000000123'), "\n";   // بانک ملی ایران
echo BankCard::getBankName('6219861234567898'), "\n";          // بانک سامان

The numbers in these examples are made up. They pass the checksum but belong to no real account. Both classes implement the shared Validator contract and never throw for bad input. They also have the helpers validate_sheba() and validate_bank_card().

Sheba (IBAN)#

Format#

A Sheba number is the Iranian IBAN. It is IR, two check digits and a 22-digit BBAN, 26 characters in all. The first three digits of the BBAN identify the bank.

IR 80 017 0000000000000000123
│  │  │   └─ rest of the BBAN (19 digits)
│  │  └──── bank code (3 digits)
│  └─────── check digits
└────────── country code

Cleaning the input#

Sheba::normalize() upper-cases the text, converts Persian and Arabic digits, and removes spaces, hyphens and half-spaces. A bare string of exactly 24 digits, the form that banks often print without IR, gets the IR prefix. Any other input stays as typed and then fails the format check:

echo Sheba::normalize('800170000000000000000123');        // IR800170000000000000000123
echo Sheba::normalize('ir80 0170 0000');                   // IR8001700000
echo json_encode(Sheba::validate('ir80-0170-0000-0000-0000-0001-23')->isValid());   // true

What is checked#

  1. Type and size (invalid_type, input_too_long).
  2. Format: IR followed by exactly 24 digits, otherwise invalid_format.
  3. Checksum: ISO 7064 mod-97-10. The first four characters move to the end, I and R become 18 and 27, and the resulting number modulo 97 must equal 1. Otherwise the error is invalid_checksum.

The bank lookup runs before the checksum. So the bank code is in details() even for a number with a wrong check digit:

$r = Sheba::validate('IR060170000000000000000123');
var_dump($r->isValid());       // bool(false)
print_r($r->errors());         // Array ( [0] => invalid_checksum )
echo $r->details()['bank_name']; // بانک ملی ایران

$ok = Sheba::validate('IR800170000000000000000123');
echo json_encode($ok->details(), JSON_UNESCAPED_UNICODE);
// {"normalized":"IR800170000000000000000123","bank_code":"017","bank_name":"بانک ملی ایران"}

$unknown = Sheba::validate('IR049990000000000000000123');
var_dump($unknown->isValid(), $unknown->details()['bank_name']);   // bool(true)  NULL

Details: normalized, bank_code (a three-digit string, or null when the format failed) and bank_name (or null when the code is not in the table). A Sheba with a valid checksum and an unknown bank code is valid. Only the name is missing.

Bank names. A name is the name of the issuing bank when the code was assigned. Codes of banks that later merged keep their original name.

Bank card number#

Format and checks#

An Iranian bank card has 16 digits. BankCard::normalize() keeps only the digits after converting Persian and Arabic ones, so spaces, hyphens and other separators are fine. The checks run in this order:

  1. Type and size.
  2. Exactly 16 digits, otherwise invalid_format.
  3. All 16 digits the same (for example 0000000000000000) gives repeated_digits.
  4. The Luhn checksum, otherwise invalid_checksum. Starting from the right, every second digit is doubled (subtract 9 when the result is above 9). The digit sum must divide by 10.
$ok = BankCard::validate('6037-9912 3456 7893');
echo json_encode($ok->details(), JSON_UNESCAPED_UNICODE);
// {"normalized":"6037991234567893","bin":"603799","bank_name":"بانک ملی ایران"}

print_r(BankCard::validate('6037991234567890')->errors());   // [invalid_checksum]
print_r(BankCard::validate('0000000000000000')->errors());   // [repeated_digits]
print_r(BankCard::validate('1234')->errors());               // [invalid_format]

$x = BankCard::validate('9999991234567893');   // checksum ok, BIN not in the table
var_dump($x->isValid(), $x->details()['bank_name']);   // bool(true)  NULL

Details: normalized, bin (the first six digits, or null if the format failed) and bank_name. As with Sheba, the BIN lookup runs before the repeated-digit and checksum checks.

BankCard::getBankName() and Sheba::getBankName() are shortcuts. They return the name only for a valid number, and null otherwise:

var_dump(BankCard::getBankName('6037991234567890'));   // NULL, because the checksum fails
var_dump(BankCard::getBankName('6037991234567893'));   // string(26) "بانک ملی ایران"

Bank lookup data#

TableKeyEntries
Card BINsfirst 6 digits of the card39
Sheba bank codes3 digits after IRkk38

An entry is included only when at least two independent sources agree on it (public bank-prefix tables and community datasets). Entries found in one source, or with conflicting sources, are left out.

What we checked on 2026-10-08:

  • The Central Bank of Iran's national IBAN specification lists 19 bank identifiers. All 19 match our table. The list also settles code 051: it belongs to the credit institution Tose'e.
  • Card BINs and the other Sheba codes agree with several public pages. For BIN 585983 (Tejarat) we also have the bank's own announcement as reported by two news sites.
Good to know. The tables are small. Many valid cards come from a bank that is not listed, and then bank_name is null. No official BIN registry was reachable, and the Central Bank list is dated 2017, so newer codes rely on public pages. Banks merge, rename and reassign ranges, so show the name as a hint and do not use it for legal or financial decisions. Details are in Accuracy and data.

Validity never depends on these tables. A number with a valid checksum is valid whether or not its bank is known.

Practical notes#

  • The checksum catches typos. It cannot tell you that a card exists or that an account belongs to someone.
  • Check the card number for shape only. Never log full card numbers, and do not store the CVV2 or the second password.
  • Store the cleaned value, so 6037-9912-3456-7893 and 6037991234567893 are one record.
  • Keep card numbers and Sheba numbers as strings. A 16-digit number can be too big for a JSON number or float to hold exactly.

The size cap and type rules are the same as for every validator. See Input handling.