はじめに
ネットバンキングの「総合振込」「一括振込」機能で使われる全銀フォーマット(全銀協規定フォーマット)。
仕様書は全国銀行協会から公開されていますが、実際に実装しようとすると細かい罠が多いフォーマットです。この記事では仕様を解説しつつ、Node.js(TypeScript)での実装例を紹介します。
全銀フォーマットとは
全国銀行協会(全銀協)が1976年に制定した、振込データの交換用フォーマットです。正式名称は「全銀協規定フォーマット(全銀フォーマット)」。
50年近く現役で使われている化石のような仕様ですが、ほぼすべての銀行のネットバンキングがこのフォーマットでの振込データ取り込みに対応しています。
基本仕様
| 項目 | 仕様 |
|---|---|
| 文字コード | Shift-JIS(JIS X 0201) |
| レコード長 | 120バイト固定長 |
| 改行コード | CR+LF(\r\n) |
| ファイル拡張子 |
.txt(CSVではない) |
| カナ | 半角カタカナ |
レコード構成
ファイルは4種類のレコードで構成されます:
┌────────────────────────────────┐
│ ヘッダーレコード(1行) │ ← 振込依頼人情報
├────────────────────────────────┤
│ データレコード(N行) │ ← 振込先情報(1件1行)
│ データレコード │
│ データレコード │
│ ... │
├────────────────────────────────┤
│ トレーラレコード(1行) │ ← 合計件数・合計金額
├────────────────────────────────┤
│ エンドレコード(1行) │ ← 終了マーカー
└────────────────────────────────┘
ヘッダーレコード(120バイト)
振込依頼人(自社)の情報を格納します。
| 位置 | バイト数 | 内容 | 備考 |
|---|---|---|---|
| 1 | 1 | データ区分 | 固定値 1
|
| 2-3 | 2 | 種別コード |
21(総合振込) |
| 4 | 1 | コード区分 |
0(JIS) |
| 5-14 | 10 | 委託者コード | 右スペース埋め |
| 15-54 | 40 | 委託者名(カナ) | 半角カナ、右スペース埋め |
| 55-58 | 4 | 振込指定日 | MMDD形式 |
| 59-62 | 4 | 仕向銀行番号 | 左ゼロ埋め |
| 63-77 | 15 | 仕向銀行名 | 半角カナ、右スペース埋め |
| 78-80 | 3 | 仕向支店番号 | 左ゼロ埋め |
| 81-95 | 15 | 仕向支店名 | 半角カナ、右スペース埋め |
| 96 | 1 | 預金種目 |
1=普通, 2=当座 |
| 97-103 | 7 | 口座番号 | 左ゼロ埋め |
| 104-120 | 17 | ダミー | スペース |
実装
interface ZenginHeaderInfo {
clientCode: string; // 委託者コード 10桁
clientName: string; // 委託者名 カナ40桁
transferDate: string; // MMDD
bankCode: string; // 仕向銀行 4桁
branchCode: string; // 仕向支店 3桁
accountType: "普通" | "当座";
accountNumber: string; // 7桁
}
function generateHeader(header: ZenginHeaderInfo): string {
const parts = [
"1", // データ区分 (1)
"21", // 種別コード (2)
"0", // コード区分 (1)
padRight(header.clientCode, 10), // 委託者コード (10)
padRight(header.clientName, 40), // 委託者名 (40)
header.transferDate.padStart(4, "0"), // 振込指定日 (4)
padLeft(header.bankCode, 4), // 仕向銀行番号 (4)
padRight("", 15), // 仕向銀行名 (15)
padLeft(header.branchCode, 3), // 仕向支店番号 (3)
padRight("", 15), // 仕向支店名 (15)
accountTypeCode(header.accountType), // 預金種目 (1)
padLeft(header.accountNumber, 7), // 口座番号 (7)
padRight("", 17), // ダミー (17)
];
return parts.join("");
}
データレコード(120バイト)
振込先1件につき1行。
| 位置 | バイト数 | 内容 | 備考 |
|---|---|---|---|
| 1 | 1 | データ区分 | 固定値 2
|
| 2-5 | 4 | 銀行番号 | 左ゼロ埋め |
| 6-20 | 15 | 銀行名(カナ) | 半角カナ、右スペース埋め |
| 21-23 | 3 | 支店番号 | 左ゼロ埋め |
| 24-38 | 15 | 支店名(カナ) | 半角カナ、右スペース埋め |
| 39-42 | 4 | 手形交換所番号 | 0000 |
| 43 | 1 | 預金種目 |
1=普通, 2=当座 |
| 44-50 | 7 | 口座番号 | 左ゼロ埋め |
| 51-80 | 30 | 受取人名(カナ) | 半角カナ、右スペース埋め |
| 81-90 | 10 | 金額 | 左ゼロ埋め |
| 91 | 1 | 新規コード | 0 |
| 92-101 | 10 | 顧客コード1 | スペース |
| 102-111 | 10 | 顧客コード2 | スペース |
| 112 | 1 | 振込区分 |
0(電信扱い) |
| 113-120 | 8 | ダミー | スペース |
実装
interface ZenginTransferRecord {
bankCode: string; // 4桁
branchCode: string; // 3桁
accountType: "普通" | "当座";
accountNumber: string; // 7桁
accountNameKana: string;// カナ30桁
amount: number;
}
function generateDataRecord(record: ZenginTransferRecord): string {
const parts = [
"2", // データ区分 (1)
padLeft(record.bankCode, 4), // 銀行番号 (4)
padRight("", 15), // 銀行名 (15)
padLeft(record.branchCode, 3), // 支店番号 (3)
padRight("", 15), // 支店名 (15)
"0000", // 手形交換所番号 (4)
accountTypeCode(record.accountType), // 預金種目 (1)
padLeft(record.accountNumber, 7), // 口座番号 (7)
padRight(record.accountNameKana, 30), // 受取人名 (30)
padLeft(record.amount, 10), // 金額 (10)
"0", // 新規コード (1)
padRight("", 10), // 顧客コード1 (10)
padRight("", 10), // 顧客コード2 (10)
"0", // 振込区分 (1)
padRight("", 8), // ダミー (8)
];
return parts.join("");
}
トレーラレコード(120バイト)
| 位置 | バイト数 | 内容 | 備考 |
|---|---|---|---|
| 1 | 1 | データ区分 | 固定値 8
|
| 2-7 | 6 | 合計件数 | 左ゼロ埋め |
| 8-19 | 12 | 合計金額 | 左ゼロ埋め |
| 20-120 | 101 | ダミー | スペース |
エンドレコード(120バイト)
| 位置 | バイト数 | 内容 | 備考 |
|---|---|---|---|
| 1 | 1 | データ区分 | 固定値 9
|
| 2-120 | 119 | ダミー | スペース |
ユーティリティ関数
半角カナ変換
全銀フォーマットでは半角カタカナを使います。全角カタカナ→半角カタカナの変換が必要です。
const KANA_MAP: Record<string, string> = {
"ア": "ア", "イ": "イ", "ウ": "ウ", "エ": "エ", "オ": "オ",
"カ": "カ", "キ": "キ", "ク": "ク", "ケ": "ケ", "コ": "コ",
"サ": "サ", "シ": "シ", "ス": "ス", "セ": "セ", "ソ": "ソ",
"タ": "タ", "チ": "チ", "ツ": "ツ", "テ": "テ", "ト": "ト",
// ... 濁音・半濁音・小書き文字も対応
"ガ": "ガ", "ギ": "ギ", "グ": "グ", // ...
"パ": "パ", "ピ": "ピ", "プ": "プ", // ...
"ッ": "ッ", "ャ": "ャ", "ュ": "ュ", "ョ": "ョ",
"ー": "ー",
};
function toHankakuKana(str: string): string {
let result = "";
for (const ch of str) {
result += KANA_MAP[ch] ?? ch;
}
return result;
}
注意: 濁音(ガ、ギ...)と半濁音(パ、ピ...)は半角カナだと2文字になります(例: ガ → ガ)。バイト数計算時にこれを考慮しないと120バイトを超えるバグが発生します。
右スペース埋め・左ゼロ埋め
// 文字列を指定桁数に右スペースパディング
function padRight(str: string, len: number): string {
const hankaku = toHankakuKana(str);
if (hankaku.length >= len) return hankaku.slice(0, len);
return hankaku + " ".repeat(len - hankaku.length);
}
// 数値を指定桁数に左ゼロパディング
function padLeft(num: string | number, len: number): string {
const s = String(num);
if (s.length >= len) return s.slice(0, len);
return "0".repeat(len - s.length) + s;
}
ファイル全体の生成
function generateZenginFile(
header: ZenginHeaderInfo,
records: ZenginTransferRecord[]
): string {
const lines: string[] = [];
lines.push(generateHeader(header));
for (const record of records) {
lines.push(generateDataRecord(record));
}
const totalAmount = records.reduce((sum, r) => sum + r.amount, 0);
lines.push(generateTrailer(records.length, totalAmount));
lines.push(generateEnd());
return lines.join("\r\n"); // CR+LF
}
出力例
121001234567890カブシキガイシヤ テスト 053100050 001 10123456
20005 012 000011234567カブシキガイシヤ サンプル 00001540000 00
8000001000001540000
9
実装時のハマりポイント
1. Shift-JISエンコード
Node.jsのデフォルトはUTF-8なので、最終的にShift-JISに変換する必要があります:
import iconv from "iconv-lite";
const utf8Content = generateZenginFile(header, records);
const sjisBuffer = iconv.encode(utf8Content, "Shift_JIS");
2. 半角カナの濁音バイト数
半角カナの濁音「ガ」はShift-JISで2バイトです。全角カナ「ガ」は2バイト。変換後のバイト数が変わらないケースが多いですが、文字数は変わるので注意。
3. 銀行名・支店名フィールドは省略可
データレコードの銀行名(15バイト)と支店名(15バイト)は、銀行番号・支店番号があればスペース埋めで省略可能です。実装をシンプルにできます。
4. 金額の上限
金額フィールドは10桁(左ゼロ埋め)なので、最大9,999,999,999円(約100億円)まで。通常の請求書では問題になりませんが、バリデーションは入れておくと安心です。
5. ゆうちょ銀行の特殊対応
ゆうちょ銀行は独自の口座体系(記号5桁 + 番号7〜8桁)を持っています。全銀フォーマットでは以下のように変換が必要です:
-
金融機関コード:
9900(固定) -
支店コード: 記号の2〜3桁目 +
8(例:10080→008) - 口座番号: 番号の末尾1桁を除いた数字
テスト
固定長フォーマットはテストが不可欠です。1バイトずれると全体が崩れます:
describe("全銀フォーマット生成", () => {
test("ヘッダーレコードは120バイト", () => {
const header = generateHeader(testHeaderInfo);
expect(header.length).toBe(120);
});
test("データレコードは120バイト", () => {
const record = generateDataRecord(testRecord);
expect(record.length).toBe(120);
});
test("トレーラの合計金額が正しい", () => {
const trailer = generateTrailer(3, 462000);
expect(trailer.substring(7, 19)).toBe("000000462000");
});
});
まとめ
- 全銀フォーマットは1976年制定の古い仕様だが、今もネットバンキングで現役
- Shift-JIS・120バイト固定長・半角カナという、現代のWeb開発とは対極の仕様
- 濁音の文字数変化、バイト数計算、ゆうちょ変換が主なハマりポイント
- テストを書かないと確実にバグる
この仕様で実際にサービスを作りました。請求書をアップロードするだけで全銀フォーマットのファイルを自動生成します:
トルカ 振込アシスト — https://toruca.app
全銀フォーマットの実装で困っている方の参考になれば幸いです。