0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

全銀フォーマット(全銀協規定フォーマット)の仕様を完全解説 — Node.jsで実装する

0
Posted at

はじめに

ネットバンキングの「総合振込」「一括振込」機能で使われる全銀フォーマット(全銀協規定フォーマット)。

仕様書は全国銀行協会から公開されていますが、実際に実装しようとすると細かい罠が多いフォーマットです。この記事では仕様を解説しつつ、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(例: 10080008
  • 口座番号: 番号の末尾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


全銀フォーマットの実装で困っている方の参考になれば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?