C#で全銀フォーマット(120バイト固定長)の振込データを作る

C#・.NET

精算金の振込を、銀行の画面で1件ずつ入力していませんか

立替精算の精算金や外注先への支払いを振り込むとき、インターネットバンキングの画面で1件ずつ入力していると、件数が増えるほど時間がかかり、入力の間違いも起きやすくなります。

毎月20人分の振込先と金額を、精算表を見ながら銀行の画面に打ち込んでいる

法人向けのインターネットバンキングの多くは、「総合振込」のファイルを取り込んで、まとめて振り込めるようになっています。そのファイルの形が、全国銀行協会(全銀協)が決めた全銀フォーマットです。

本記事では、C#で全銀フォーマットの総合振込データを作る方法を紹介します。自作の立替精算システムの「振込データ」の画面で使っている部品です。

立替精算システムで使ったところ

私が自作した立替精算システムでは、経理確認が済んだ申請を選んで「全銀データを作成する」を押すと、総合振込のファイルを作ります。同じ社員の申請は、1件の振込にまとめています。

立替精算システムの振込データの画面。取組日と、振り込む申請の一覧、全銀データを作成するボタンがある

振込データの画面です。取組日(振込指定日)を決めて、振り込む申請に印を付けて作ります。振込先の口座と受取人名(カナ)は、社員ごとに登録しておきます。

全銀フォーマットの形

全銀フォーマットは、1行(1レコード)が120バイトの固定長のテキストです。文字コードはShift_JIS、改行はCRLFで、次の4種類のレコードをこの順に並べます。

データ区分レコード入れるもの
1ヘッダー依頼人(自社)のコードと名前、取組日、お金を出す自社の口座
2データ振込1件ぶん(振込先の銀行・支店・口座、受取人名、金額)。件数ぶん並べる
8トレーラ件数と合計金額
9エンドファイルの終わり

項目ごとに桁数が決まっていて、数字の項目は右に寄せて前を0で埋め、文字の項目は左に寄せて後ろを空白で埋めます。使える文字は、半角の英数字・カナと一部の記号だけです。全角の文字が1文字でも混ざると、Shift_JISで2バイトになって行の長さがずれ、銀行の取り込みでファイルごとエラーになります。

C#で全銀フォーマットの振込データを作るサンプルコード

動作確認は Windows 11・.NET 10 です(.NET 8 以降で動きます)。Visual Studio でコンソールアプリを作り、Program.cs に貼り付けて実行してください。.NET 10 なら、ファイルに保存して dotnet run ファイル名.cs でも実行できます。

using System.Text;

// Shift_JIS(CP932)を使えるようにする(.NET Core 以降は最初に 1 回だけ必要)
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

// 依頼人(自社)の情報
var irainin = new Irainin(
    IraininCd: "1234567890",
    IraininKana: "カ)サンプルショウジ",
    GinkouCd: "9999", GinkouKana: "サンプルギンコウ",
    ShitenCd: "001", ShitenKana: "ホンテン",
    YokinSyumoku: 1, KouzaBangou: "1234567");

// 振り込む先(社員の立替精算)
var meisaiList = new List<Meisai>
{
    new("9999", "サンプルギンコウ", "012", "ナゴヤシテン", 1, "7654321", "オタメシ ジョウチョウ", 33880, "3000"),
    new("9999", "サンプルギンコウ", "034", "オオサカシテン", 1, "1234567", "キッチョウ ショウタ", 23720, "4000"),
};

var bytes = ToBytes(irainin, meisaiList, new DateOnly(2026, 10, 16));
File.WriteAllBytes("FURIKOMI_20261016.txt", bytes);

// 1 行ずつ、バイト数を確かめながら表示する
var sjis = Encoding.GetEncoding(932);
foreach (var gyou in sjis.GetString(bytes).Split("\r\n", StringSplitOptions.RemoveEmptyEntries))
{
    Console.WriteLine($"{sjis.GetByteCount(gyou),3} バイト |{gyou}|");
}

Console.WriteLine();
Console.WriteLine($"「キッチョウ ショウタ」→「{Hankaku("キッチョウ ショウタ")}」(小書きのカナは大文字に)");
Console.WriteLine($"「1234567」→「{Hankaku("1234567")}」(全角の数字は半角に)");
Console.WriteLine($"「お試し上長」→「{Hankaku("お試し上長")}」(漢字・ひらがなは載せられないので落とす)");


/// <summary>全銀データのバイト列(Shift_JIS・1 レコード 120 バイト・改行 CRLF)。</summary>
static byte[] ToBytes(Irainin irainin, IReadOnlyList<Meisai> meisaiList, DateOnly torikumiBi)
{
    var sb = new StringBuilder();
    sb.Append(HeaderRecord(irainin, torikumiBi)).Append("\r\n");
    foreach (var meisai in meisaiList)
    {
        sb.Append(DataRecord(meisai)).Append("\r\n");
    }

    sb.Append(TrailerRecord(meisaiList)).Append("\r\n");
    sb.Append(EndRecord()).Append("\r\n");
    return Encoding.GetEncoding(932).GetBytes(sb.ToString());
}

/// <summary>ヘッダーレコード(データ区分 1)。</summary>
static string HeaderRecord(Irainin x, DateOnly torikumiBi) =>
    Kumitate(
        ("1", 1, false),                            // データ区分
        ("21", 2, false),                           // 種別コード(総合振込)
        ("0", 1, false),                            // コード区分(JIS)
        (x.IraininCd, 10, true),                    // 依頼人コード
        (x.IraininKana, 40, false),                 // 依頼人名
        (torikumiBi.ToString("MMdd"), 4, false),    // 取組日
        (x.GinkouCd, 4, true),                      // 仕向金融機関番号
        (x.GinkouKana, 15, false),                  // 仕向金融機関名
        (x.ShitenCd, 3, true),                      // 仕向支店番号
        (x.ShitenKana, 15, false),                  // 仕向支店名
        (x.YokinSyumoku.ToString(), 1, false),      // 預金種目
        (x.KouzaBangou, 7, true),                   // 口座番号
        (null, 17, false));                         // ダミー

/// <summary>データレコード(データ区分 2)。振込 1 件ぶん。</summary>
static string DataRecord(Meisai x) =>
    Kumitate(
        ("2", 1, false),                            // データ区分
        (x.GinkouCd, 4, true),                      // 被仕向金融機関番号
        (x.GinkouKana, 15, false),                  // 被仕向金融機関名
        (x.ShitenCd, 3, true),                      // 被仕向支店番号
        (x.ShitenKana, 15, false),                  // 被仕向支店名
        (null, 4, false),                           // 手形交換所番号
        (x.YokinSyumoku.ToString(), 1, false),      // 預金種目
        (x.KouzaBangou, 7, true),                   // 口座番号
        (x.UketorininKana, 30, false),              // 受取人名
        (x.Kingaku.ToString(), 10, true),           // 振込金額
        ("0", 1, false),                            // 新規コード
        (x.KokyakuCd, 10, false),                   // 顧客コード1(社員番号)
        (null, 10, false),                          // 顧客コード2
        ("7", 1, false),                            // 振込指定区分(テレ振込)
        (null, 1, false),                           // 識別表示
        (null, 7, false));                          // ダミー

/// <summary>トレーラレコード(データ区分 8)。件数と合計金額。</summary>
static string TrailerRecord(IReadOnlyList<Meisai> meisaiList) =>
    Kumitate(
        ("8", 1, false),
        (meisaiList.Count.ToString(), 6, true),
        (meisaiList.Sum(x => x.Kingaku).ToString(), 12, true),
        (null, 101, false));

/// <summary>エンドレコード(データ区分 9)。</summary>
static string EndRecord() =>
    Kumitate(("9", 1, false), (null, 119, false));

/// <summary>項目を並べて 1 レコードにする。120 バイトにならなければ止める。</summary>
static string Kumitate(params (string? Value, int Length, bool Suuji)[] koumoku)
{
    var sb = new StringBuilder(120);
    foreach (var (value, length, suuji) in koumoku)
    {
        sb.Append(Seikei(value, length, suuji));
    }

    var record = sb.ToString();
    return record.Length == 120
        ? record
        : throw new InvalidOperationException($"全銀レコードの長さが {record.Length} バイトになりました。");
}

/// <summary>1 項目を桁数に合わせる。数字は右詰めゼロ埋め、それ以外は左詰め空白埋め。</summary>
static string Seikei(string? value, int length, bool suuji)
{
    var moji = Hankaku(value ?? string.Empty);
    if (moji.Length > length)
    {
        moji = moji[..length];
    }

    return suuji ? moji.PadLeft(length, '0') : moji.PadRight(length, ' ');
}

/// <summary>全銀データに載せられない文字を直す・落とす。</summary>
static string Hankaku(string value)
{
    var sb = new StringBuilder(value.Length);
    foreach (var c in value)
    {
        if (Henkan(c) is { } moji)
        {
            sb.Append(moji);
        }
    }

    return sb.ToString();
}

static string? Henkan(char c)
{
    // 小書きのカナは大文字へ(全銀では使えない)
    var kogaki = "ァィゥェォャュョッ".IndexOf(c);
    if (kogaki >= 0)
    {
        return "アイウエオヤユヨツ"[kogaki].ToString();
    }

    // 半角カナ・数字・英大文字・使える記号はそのまま
    if (c is >= '。' and <= '゚' or >= '0' and <= '9' or >= 'A' and <= 'Z'
        or ' ' or '.' or '-' or '/' or '(' or ')' or '¥' or ',')
    {
        return c.ToString();
    }

    // 英小文字は大文字へ
    if (c is >= 'a' and <= 'z')
    {
        return char.ToUpperInvariant(c).ToString();
    }

    // 全角の英数字は半角へ寄せてから判定し直す
    if (c is >= '0' and <= '9' or >= 'A' and <= 'Z' or >= 'a' and <= 'z')
    {
        return Henkan((char)(c - 0xFEE0));
    }

    if (c == ' ')
    {
        return " ";
    }

    // 全角カナ・漢字・ひらがなは載せられないので落とす
    return null;
}

/// <summary>依頼人(自社)。</summary>
record Irainin(string IraininCd, string IraininKana, string GinkouCd, string? GinkouKana,
    string ShitenCd, string? ShitenKana, int YokinSyumoku, string KouzaBangou);

/// <summary>振込 1 件。</summary>
record Meisai(string GinkouCd, string? GinkouKana, string ShitenCd, string? ShitenKana,
    int YokinSyumoku, string KouzaBangou, string UketorininKana, long Kingaku, string KokyakuCd);

実行結果

120 バイト |12101234567890カ)サンプルシヨウジ                            10169999サンプルギンコウ     001ホンテン           11234567                 |
120 バイト |29999サンプルギンコウ     012ナゴヤシテン            17654321オタメシ ジヨウチヨウ                  000003388003000                7        |
120 バイト |29999サンプルギンコウ     034オオサカシテン            11234567キツチヨウ シヨウタ                    000002372004000                7        |
120 バイト |8000002000000057600                                                                                                     |
120 バイト |9                                                                                                                       |

「キッチョウ ショウタ」→「キツチヨウ シヨウタ」(小書きのカナは大文字に)
「1234567」→「1234567」(全角の数字は半角に)
「お試し上長」→「」(漢字・ひらがなは載せられないので落とす)

5行とも120バイトになっています。トレーラの 000002 と 000000057600 は、件数2件・合計57,600円です。受取人名の「ョ」「ッ」は大文字の「ヨ」「ツ」に、全角で入っていた口座番号は半角に直っています。

銀行コード(9999)・依頼人コードなどは、説明のための架空の値です。

コードの説明

  • Encoding.RegisterProvider(CodePagesEncodingProvider.Instance) で、Shift_JIS(CP932)を使えるようにしています。.NET Core 以降の.NETでは、最初に1回だけ必要です。
  • Kumitate は、項目を「値・桁数・数字かどうか」の組で並べるだけで、1レコードを組み立てます。最後に120バイトになったかを確かめ、ならなければ例外にしています。桁のずれたファイルを銀行に渡すより、作る時点で止めた方が安全だからです。
  • Seikei は、数字の項目を PadLeft(桁数, '0') で右に寄せて0で埋め、文字の項目を PadRight(桁数, ' ') で左に寄せて空白で埋めます。長すぎる値は桁数で切ります。
  • Hankaku は、全銀データに載せられない文字を直したり落としたりします。小書きのカナ(ァィゥェォャュョッ)は大文字へ、全角の英数字は半角へ、英小文字は大文字へ寄せ、全角カナ・漢字・ひらがなは落とします。
  • Hankaku を通したあとは、半角の文字(Shift_JISで1バイト)だけになるので、「文字数=バイト数」になります。そのため、桁数を string.Length で数えられます。

受取人名を登録するときにも確かめる

振込データを作るときに受取人名が全部落ちてしまうと、振込先の名前が空のデータになってしまいます。立替精算システムでは、社員の振込先を登録するときにも同じ Hankaku を通して、載せられる文字が1文字も残らない場合はエラーにしています。

// 入力はあるのに、全銀データに載せられる文字が残らない
if (!string.IsNullOrWhiteSpace(value) && string.IsNullOrWhiteSpace(Hankaku(value)))
{
    ModelState.AddModelError(
        "Input.UketorininKana",
        "振込データに使える文字がありません。半角カナ・半角英数字で入力してください"
        + "(漢字とひらがなは使えません)。");
}

保存するときも Hankaku を通した値で保存しているので、振込データを作るときに文字の問題は起きません。

注意点

  • 細かいところは銀行ごとに違います。依頼人コードの桁、使える記号、ファイルの最後の改行や終わりの記号の扱いなどは、取引銀行の仕様書で確認してください。立替精算システムでは、変更が1か所で済むように、全銀データの組み立てを1つのクラスにまとめています。
  • 初めて使うときは、少ない件数で銀行の取り込み画面のチェックを通るか確かめてから、本番の振込に使ってください。
  • 作ったファイルには口座番号と金額が入っています。保存する場所や受け渡しの方法に気をつけてください。
  • 受取人名は、口座の名義と同じカナにしてください。社員の氏名と名義が違う場合(旧姓のままの口座など)は、名義の方に合わせます。

最後に

以上が、C#で全銀フォーマットの振込データを作る方法です。

決まりは「1行120バイト」「数字は右詰め0埋め、文字は左詰め空白埋め」「半角の文字だけ」の3つです。項目を並べるだけでレコードを組み立てられる部品にしておくと、項目の位置を間違えにくく、銀行ごとの違いにも1か所で対応できます。

この部品を使っている立替精算システムは、こちらで紹介しています。

C#でCSVをExcelで文字化けせずに出力する方法は、こちらで紹介しています。

コメント

タイトルとURLをコピーしました