買掛金処理でよくある失敗:スキャンした請求書から OCR が読み取った IBAN が DE89 3704 0044 0532 O130 00 になる。後ろから 2 番目のグループにある英字の O は見落としやすく、そのまま送金すると銀行に拒否されるか返金される。
たった一度の mod-97 検証で止められる話です。IBAN 標準はまさにこの場面のために設計されています。
IBAN とは結局何なのか
IBAN(International Bank Account Number)は ISO 13616 で定義され、SWIFT が IBAN Registry を通じて運用維持しています。各 IBAN は一つの文字列に 4 つの要素を詰め込みます:
| 部位 | 長さ | 出典 |
|---|---|---|
| 国コード | 2 文字 | ISO 3166-1 alpha-2 |
| チェックデジット | 2 桁の数字 | 残り部分の mod-97 |
| BBAN(Basic Bank Account Number) | 11〜30 文字 | 各国の標準 |
| 全長 | 15〜34 文字 | 国別に固定 |
最短はノルウェーの 15 文字、最長はセントルシアと マルタの 32・31 文字。チェックデジットは国コードの直後に位置するため、英国の IBAN は GB82 WEST… と始まります——GB が国、82 がチェックサム、WEST 以降が BBAN です。
世界中で個人向けに IBAN を発行する「世界機関」は存在しません。各国がこの標準を採用したうえで、自国の BBAN 構造(銀行コードの位置、口座番号の長さ、各位置の文字種)を定義し、SWIFT IBAN Registry に公開します。この構造こそが、すべての検証器が実装する契約です。
mod-97 の仕組みと、なぜ巧妙なのか
チェックデジットはランダムではありません。文字を数値に置換した後の IBAN 全体を 97 で割ったとき余りが 1 になるよう設計されています。アルゴリズムは 5 ステップで、ブラウザ内ではマイクロ秒で完了します:
- 正規化。 空白を取り除き、すべて大文字化する。
- 回転。 先頭 4 文字(国コード + チェックデジット)を末尾に移動。
- 文字置換。 各文字を 2 桁の数字へ:
A → 10、B → 11、…、Z → 35。 - 剰余。 得られた数字列を 1 つの巨大整数とみなし、
n mod 97を計算。 - 比較。 余りが
1ならチェックサム成立。
function isValidIban(raw) {
const s = raw.toUpperCase().replace(/[^A-Z0-9]/g, '');
if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/.test(s)) return false;
const rearranged = s.slice(4) + s.slice(0, 4);
const numeric = [...rearranged]
.map(c => (c >= 'A' ? (c.charCodeAt(0) - 55).toString() : c))
.join('');
// BigInt は長い IBAN(ロシアは 33 文字)で 53 ビット浮動小数の上限を超えるのを回避。
return BigInt(numeric) % 97n === 1n;
}
なぜ 97 なのか?理由は 3 つあります:
- 素数。 素数の剰余を取ることで、「任意の桁を 1 つ変えたら余りが変わる」確率が最大化される——どの小さな約数も誤差を吸収できないから。
- 2 桁に収まる。 97 で割った余りは必ず 2 桁に収まり、標準がチェックデジットに割り当てた長さとちょうど一致する。
- よくある入力ミスを広く検出。 IBAN が使う ISO/IEC 7064 MOD 97-10 方式は、1 文字の置換と隣接する 2 文字の入れ替えをすべて検出するよう設計されている。
ステップ 2 の回転は、チェックデジットを ISO/IEC 7064 が想定する末尾へ移す。国コードも割られる数に含まれるので、DE を FR にしたりチェックデジットを打ち間違えたりしても計算が合わなくなる。
BBAN こそが各国の独創性が出る場所
国コードとチェックデジットの後に続く BBAN のフォーマットは国ごとに定義されます。SWIFT IBAN Registry が各国のレイアウトをコード化しています。バリエーションを少しだけ紹介:
| 国 | 長さ | BBAN 構造 |
|---|---|---|
| ノルウェー(NO) | 15 | 4 桁の銀行 + 6 桁の口座 + 1 桁の国内チェックデジット |
| ベルギー(BE) | 16 | 3 桁の銀行 + 7 桁の口座 + 2 桁の国内チェックデジット |
| オランダ(NL) | 18 | 4 文字の銀行(ABNA、RABO、INGB…)+ 10 桁の口座 |
| ドイツ(DE) | 22 | 8 桁の銀行 + 10 桁の口座(支店なし) |
| 英国(GB) | 22 | 4 文字の銀行 + 6 桁の sort code + 8 桁の口座 |
| フランス(FR) | 27 | 5 桁の銀行 + 5 桁の支店 + 11 文字の口座 + 2 桁の国内チェックデジット |
| イタリア(IT) | 27 | 1 文字の国内チェック + 5 桁の銀行 + 5 桁の支店 + 12 文字の口座 |
| サウジアラビア(SA) | 24 | 2 桁の銀行 + 18 文字の口座 |
| ブラジル(BR) | 29 | 8 桁の銀行 + 5 桁の支店 + 10 桁の口座 + 1 文字の口座種別 + 1 文字の保有者 |
| モーリシャス(MU) | 30 | 6 文字の銀行コード(英字 4 + 数字 2)+ 2 桁の支店 + 12 桁の口座 + 000 + 英字 3 文字の通貨 |
注目すべきパターンが 2 つあります:
ラテン語圏グループ(FR、IT、BE、MC、MR、PT、SM、ST、TN) はすべて、BBAN の中に IBAN レベルの mod-97 とは別の国内チェックデジットを持つ。フランスの RIB key は銀行 + 支店 + 口座に対する別の mod-97 で、イタリアの BBAN 先頭は位置重み付きの表で計算する CIN 文字(A–Z)だ。IBAN の mod-97 は 1 文字の置換をすべて検出するので、国内チェックが効くのは別の場面だ。国内の口座番号を入力してシステムが IBAN を生成する場合、1 桁間違えてもチェックデジットの正しい IBAN ができてしまい、それを止められるのは国内チェックだけになる。
英語圏グループ(GB、IE、MT) は 文字で銀行コード を表します。これらの文字は銀行短縮名の冒頭(WEST、BARC、LOYD、HSBC)で、GB IBAN を解析するとドイツのそれより可読性の高い情報が得られます:テーブルなしでも BARC が Barclays だと推測できます。
現実の IBAN エラーはどこから来るか
IBAN のミスは金額そのものより、跳ね返ったあとの後始末が高くつきます——銀行は数週間かけて差し戻すことすらあります。本当のコストは、返金手数料(1 件あたり 5〜30 EUR)、業務側のフォローアップ工数、サプライヤーとの関係に生まれる摩擦です。防ぐべき類型は次のとおり:
OCR の誤認識
スキャンされた請求書の失敗パターンは予測可能です。最も頻発する文字置換:
| 誤 | 正 | 理由 |
|---|---|---|
O | 0 | 字母 O と数字 0 が低品質フォントでほぼ同じ形に |
I / l / 1 | 1 | サンセリフ系で字形が重なる |
S | 5 | イタリックや装飾系フォントでよくある誤認 |
B | 8 | 銀行明細の圧縮出力で起きやすい |
Z | 2 | 欧州大陸の手書き習慣 |
どれも 1 文字の置換なので、mod-97 はすべて検出できる。ZeroTool のバリデーターは「チェックサム失敗 (mod 97 ≠ 1)。チェックデジットまたは口座本文に誤りがあります。」と表示するが、どの文字が誤りかは示さない。上の表の見た目が似た文字を探してほしい。
空白と幅ゼロ文字
PDF や Outlook からのコピーペーストは、ノンブレイクスペース(U+00A0)、ゼロ幅接合子(U+200D)、たまに BOM を持ち込みます。ASCII の空白しか取り除かないスクリプトはここで失敗します。ZeroTool の正規化は、入力を大文字にしたうえで A–Z、0–9 以外の文字をすべて取り除いてから検証します。ISO 13616 は区切りのない電子形式と、4 文字ごとに空白で区切る印刷形式を定めているので、空白を除くのは想定どおりです。それ以外の文字を除くのは便宜上の処理で、紛れ込んだ記号も見えなくなります。
先頭ゼロの消失
IBAN 全体は英字で始まるので、スプレッドシートは文字列のまま保持する。壊れるのは分割した部分だ。別々の列に入れた国内の口座番号や銀行コードは、Excel が数値に変換すると先頭のゼロを失い(0123 が 123 になる)、その列から組み立て直した IBAN は長さが合わなくなる。どの部分も文字列で保存すること。
国コードの取り違え
DE(ドイツ)と DK(デンマーク)は 1 文字違いで、IBAN の長さは 4 文字違う。ドイツの IBAN 本体をデンマークの接頭辞の後ろに付けると、長さチェックですぐ失敗する。バリデーターは「Denmark の IBAN 長さが正しくありません: 期待 18, 実際 22。」という具体的なエラーを出す。
小文字
仕様は大文字のみです。一部の銀行は可読性のために大文字小文字混在で印字し、メールでは IBAN をハイパーリンクで包む処理が小文字化することもあります。正規化が処理しますが、「これは通るはず」というレポートを追うときには頭に置いておく価値があります。
IBAN 検証器が 教えてくれない こと
mod-97 が通れば検証器は「正しい」と呼びたくなりますが、そうではありません。3 つのことは常に守備範囲外です:
- 口座が存在するか。 銀行が先週解約したかもしれない。mod-97 には知りようがない。
- 口座が利用可能か。 凍結・休眠・停止のいずれでも、振込指示は受け付け、決済段階で跳ね返す。
- IBAN と名義が一致するか。 EU の PSD2 と SEPA スキームは、これを受け取り側銀行に押し付けます。送る側のあなたは検証しない。英国の Confirmation of Payee や、欧州で 2024〜2025 に段階展開された Verification of Payee はまさにこれを対象にしていますが、銀行層の話であり、あなたのフォーム検証の話ではありません。
正しい心構え:IBAN 検証は必要な第一フィルターであって十分条件ではない。クライアント側で安く誤入力を捕まえ、残りは銀行 API(あるいは実際の送金試行)に任せる。
チェックアウトフォームへの検証組み込み
典型的なパターン:入力中に検証して、成功時は緑のチェック、失敗時はインラインエラーを出し、mod-97 に通らない限り送信させない。バニラ JS の骨格:
<label for="iban">IBAN</label>
<input
id="iban"
type="text"
inputmode="text"
autocapitalize="characters"
spellcheck="false"
autocomplete="off"
aria-describedby="iban-msg"
/>
<p id="iban-msg" role="status" aria-live="polite"></p>
<script>
const input = document.getElementById('iban');
const msg = document.getElementById('iban-msg');
input.addEventListener('input', () => {
const result = isValidIban(input.value); // 上の関数
msg.textContent = result ? 'Valid IBAN.' : 'IBAN checksum invalid.';
msg.className = result ? 'ok' : 'err';
});
</script>
実運用で効く細部 3 つ:
autocapitalize="characters"は iOS のオートコレクトがgB82…を送るのを防ぎます——それでは format regex でいきなり落ちる。aria-live="polite"はスクリーンリーダー利用者にフォーカスを奪わず検証結果を読み上げます。- 送信時だけでなく、入力中に検証する。 300ms の submit round-trip を待たず
n回目のキーストロークでエラーを捕まえる——これがすべての要点です。
検証成功時に有料 IBAN API(国内チェックデジットと口座存在性照会つき)も呼ぶなら debounce を入れましょう:mod-97 は速すぎてキーストロークごとに走らせても問題ないので、API は 300〜500ms の debounce が無難です。
無料の選択肢を比較する
IBAN 検証を置く場所は 3 つ:クライアントライブラリ、SaaS API、ブラウザツール。
| 選択肢 | カバレッジ | コスト | プライバシー |
|---|---|---|---|
iban(npm) | mod-97 + 長さ | 無料 | クライアントサイド |
| ZeroTool IBAN Validator & Parser | mod-97 + 長さ + BBAN 分解 | 無料 | クライアントサイド |
| iban.com REST API | mod-97 + 銀行検索 + IBAN→BIC | 従量課金 | サーバー間 |
| openiban.com REST API | mod-97 + 長さ | 無料、レート制限 | サーバー間 |
正解は「あなたが解きたい問題」によります。個人プロジェクトの無料フォーム——npm ライブラリでも ZeroTool ページでも十分。本番でお金を動かす決済処理——API ティアの国内チェックデジットと IBAN→BIC マッピングが、自前検証で逃したエラーを 1 回拾えば元が取れる。
ZeroTool のツールは「観察」のユースケースに最適化されています:IBAN を持っていて、どう分解されるか知りたい、誰にも送りたくない。クライアントに組み込むのと同じ mod-97 エンジンに、標準的な npm ライブラリでは表に出ないことも多い国別 BBAN 分解を加えたものです。
範囲外(とその理由)
ZeroTool には以下がありません:
- IBAN ジェネレーター。 国と銀行コードを与えれば数学的にチェックデジットの正しい「形式上有効な IBAN」を作れる。これを公開しないのは、口座詐称詐欺のハードルを下げるからです。本物の IBAN は銀行から発行されるもので、それ以外の経路で生成する開発者の正当な需要はほぼありません。
- BIC / SWIFT 検索。 銀行コードを銀行名にマッピングするには、月次更新されるライセンス済みデータベースを再配布する必要があり、運用負担も継続的に高い。BIC 検索は SWIFT 公式ディレクトリか各国中央銀行のレジストリが正式な情報源です。
- SEPA 入金 QR コード。 欧州決済評議会の EPC069-12 形式は、銀行 App の振込画面を事前入力する QR を生成します。ZeroTool の QR コードジェネレーター で QR は作れますが、EPC069-12 ペイロードはご自身で組み立ててください——検証器の仕事は IBAN であって、決済指示ではありません。
これらは意図的な空白です。関連能力をすべて 1 つのツールに詰め込むと、ツールの目的と信頼モデルがどちらも薄まります。
参考資料
- ISO 13616-1:2020 — 正式標準、有料ダウンロードだが要約は無料閲覧可。
- SWIFT IBAN Registry — 各国 BBAN 構造の公開 PDF、定期更新。
- European Payments Council — SEPA Credit Transfer rulebook — IBAN を送ったあと銀行が実際にどう処理するか。
- Wikipedia: International Bank Account Number — 標準の可読版の歴史と各国の採用タイムライン。
ZeroTool 内部では、IBAN 検証器は URL パーサー(決済リダイレクト URL のトークン解析)、Cookie パーサー(決済ポータルが発行するセッション cookie のデバッグ)、QR コードジェネレーター(検証済み IBAN から SEPA EPC069-12 QR を構築)と自然に組み合わさります。
次に OCR で読んだ IBAN がついた請求書が来たら、決済画面に貼る前にまず検証器に貼ってください。mod-97 はマイクロ秒以内に「この送金が跳ね返るか」を教えてくれます。2 分の精査で、25 EUR の返金手数料を一度でも回避できれば即座に元が取れ、サプライヤーとの関係改善はそれ以上の隠れた配当を返してくれます。