From 30afd17683cb4368c0ac589917c694ab1d1448f6 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Thu, 2 Oct 2025 01:08:22 -0400 Subject: [PATCH 1/3] Fix: Make BIC optional for Girocode Version 2 - Modified IsValidBic to accept a 'required' parameter - BIC is now required for Version 1 (as per spec) - BIC is optional for Version 2 (as per spec) - Fixed NullReferenceException when BIC is null - Added comprehensive tests for all scenarios: - Version 2 with null BIC (passes) - Version 2 with empty BIC (passes) - Version 2 with valid BIC (passes) - Version 2 with invalid BIC (throws exception) - Version 1 with null BIC (throws exception) - Version 1 with empty BIC (throws exception) Fixes issue where Girocode constructor threw NullReferenceException when BIC was null with Version 2, which should be allowed per the Girocode specification. --- QRCoder/PayloadGenerator.cs | 12 ++ QRCoder/PayloadGenerator/Girocode.cs | 4 +- .../PayloadGeneratorTests/GirocodeTests.cs | 134 ++++++++++++++++++ 3 files changed, 148 insertions(+), 2 deletions(-) diff --git a/QRCoder/PayloadGenerator.cs b/QRCoder/PayloadGenerator.cs index a54efe31..3b90b21f 100644 --- a/QRCoder/PayloadGenerator.cs +++ b/QRCoder/PayloadGenerator.cs @@ -70,6 +70,18 @@ private static bool IsValidQRIban(string iban) private static bool IsValidBic(string bic) => Regex.IsMatch(bic.Replace(" ", ""), @"^([a-zA-Z]{4}[a-zA-Z]{2}[a-zA-Z0-9]{2}([a-zA-Z0-9]{3})?)$"); + /// + /// Validates the structure of a BIC with optional requirement check. + /// + /// The BIC to validate. + /// Whether the BIC is required. If false, null/empty values are considered valid. + /// True if the BIC is valid; otherwise, false. + private static bool IsValidBic(string bic, bool required) + { + if (string.IsNullOrEmpty(bic)) + return !required; + return IsValidBic(bic); + } /// /// Converts a string to a specified encoding. diff --git a/QRCoder/PayloadGenerator/Girocode.cs b/QRCoder/PayloadGenerator/Girocode.cs index 94c8b997..fbd71618 100644 --- a/QRCoder/PayloadGenerator/Girocode.cs +++ b/QRCoder/PayloadGenerator/Girocode.cs @@ -45,9 +45,9 @@ public Girocode(string iban, string bic, string name, decimal amount, string rem if (!IsValidIban(iban)) throw new GirocodeException("The IBAN entered isn't valid."); _iban = iban.Replace(" ", "").ToUpper(); - if (!IsValidBic(bic)) + if (!IsValidBic(bic, _version == GirocodeVersion.Version1)) throw new GirocodeException("The BIC entered isn't valid."); - _bic = bic.Replace(" ", "").ToUpper(); + _bic = bic?.Replace(" ", "").ToUpper() ?? string.Empty; if (name.Length > 70) throw new GirocodeException("(Payee-)Name must be shorter than 71 chars."); _name = name; diff --git a/QRCoderTests/PayloadGeneratorTests/GirocodeTests.cs b/QRCoderTests/PayloadGeneratorTests/GirocodeTests.cs index 63a8f22d..841fcd19 100644 --- a/QRCoderTests/PayloadGeneratorTests/GirocodeTests.cs +++ b/QRCoderTests/PayloadGeneratorTests/GirocodeTests.cs @@ -344,4 +344,138 @@ public void girocode_generator_sets_encoding_parameters() payload.EciMode.ShouldBe(EciMode.Default); payload.Version.ShouldBe(-1); } + + [Fact] + public void girocode_generator_version2_with_null_bic_should_succeed() + { + var iban = "NL86INGB0002445588"; + var name = "a name"; + var remittanceInformation = "some info"; + var amount = 1337.99m; + + var payload = new PayloadGenerator.Girocode( + iban: iban, + bic: null, + name: name, + amount: amount, + remittanceInformation: remittanceInformation, + version: PayloadGenerator.Girocode.GirocodeVersion.Version2, + encoding: PayloadGenerator.Girocode.GirocodeEncoding.UTF_8); + + payload + .ToString() + .ShouldBe("BCD\n002\n1\nSCT\n\na name\nNL86INGB0002445588\nEUR1337.99\n\n\nsome info\n"); + } + + [Fact] + public void girocode_generator_version2_with_empty_bic_should_succeed() + { + var iban = "NL86INGB0002445588"; + var name = "a name"; + var remittanceInformation = "some info"; + var amount = 1337.99m; + + var payload = new PayloadGenerator.Girocode( + iban: iban, + bic: string.Empty, + name: name, + amount: amount, + remittanceInformation: remittanceInformation, + version: PayloadGenerator.Girocode.GirocodeVersion.Version2, + encoding: PayloadGenerator.Girocode.GirocodeEncoding.UTF_8); + + payload + .ToString() + .ShouldBe("BCD\n002\n1\nSCT\n\na name\nNL86INGB0002445588\nEUR1337.99\n\n\nsome info\n"); + } + + [Fact] + public void girocode_generator_version2_with_valid_bic_should_succeed() + { + var iban = "NL86INGB0002445588"; + var bic = "INGBNL2A"; + var name = "a name"; + var remittanceInformation = "some info"; + var amount = 1337.99m; + + var payload = new PayloadGenerator.Girocode( + iban: iban, + bic: bic, + name: name, + amount: amount, + remittanceInformation: remittanceInformation, + version: PayloadGenerator.Girocode.GirocodeVersion.Version2, + encoding: PayloadGenerator.Girocode.GirocodeEncoding.UTF_8); + + payload + .ToString() + .ShouldBe("BCD\n002\n1\nSCT\nINGBNL2A\na name\nNL86INGB0002445588\nEUR1337.99\n\n\nsome info\n"); + } + + [Fact] + public void girocode_generator_version2_with_invalid_bic_should_throw_exception() + { + var iban = "NL86INGB0002445588"; + var bic = "INVALID"; + var name = "a name"; + var remittanceInformation = "some info"; + var amount = 1337.99m; + + var exception = Record.Exception(() => new PayloadGenerator.Girocode( + iban: iban, + bic: bic, + name: name, + amount: amount, + remittanceInformation: remittanceInformation, + version: PayloadGenerator.Girocode.GirocodeVersion.Version2, + encoding: PayloadGenerator.Girocode.GirocodeEncoding.UTF_8)); + + Assert.NotNull(exception); + Assert.IsType(exception); + exception.Message.ShouldBe("The BIC entered isn't valid."); + } + + [Fact] + public void girocode_generator_version1_with_null_bic_should_throw_exception() + { + var iban = "NL86INGB0002445588"; + var name = "a name"; + var remittanceInformation = "some info"; + var amount = 1337.99m; + + var exception = Record.Exception(() => new PayloadGenerator.Girocode( + iban: iban, + bic: null, + name: name, + amount: amount, + remittanceInformation: remittanceInformation, + version: PayloadGenerator.Girocode.GirocodeVersion.Version1, + encoding: PayloadGenerator.Girocode.GirocodeEncoding.UTF_8)); + + Assert.NotNull(exception); + Assert.IsType(exception); + exception.Message.ShouldBe("The BIC entered isn't valid."); + } + + [Fact] + public void girocode_generator_version1_with_empty_bic_should_throw_exception() + { + var iban = "NL86INGB0002445588"; + var name = "a name"; + var remittanceInformation = "some info"; + var amount = 1337.99m; + + var exception = Record.Exception(() => new PayloadGenerator.Girocode( + iban: iban, + bic: string.Empty, + name: name, + amount: amount, + remittanceInformation: remittanceInformation, + version: PayloadGenerator.Girocode.GirocodeVersion.Version1, + encoding: PayloadGenerator.Girocode.GirocodeEncoding.UTF_8)); + + Assert.NotNull(exception); + Assert.IsType(exception); + exception.Message.ShouldBe("The BIC entered isn't valid."); + } } From 03d774a58cdf4730f3aaddacf8d4138886c88379 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 5 Oct 2025 10:55:10 -0400 Subject: [PATCH 2/3] update --- QRCoder/PayloadGenerator.cs | 4 ++-- QRCoder/PayloadGenerator/Girocode.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/QRCoder/PayloadGenerator.cs b/QRCoder/PayloadGenerator.cs index 6dd044e9..ce465401 100644 --- a/QRCoder/PayloadGenerator.cs +++ b/QRCoder/PayloadGenerator.cs @@ -71,11 +71,11 @@ private static bool IsValidBic(string bic) /// The BIC to validate. /// Whether the BIC is required. If false, null/empty values are considered valid. /// True if the BIC is valid; otherwise, false. - private static bool IsValidBic(string bic, bool required) + private static bool IsValidBic(string? bic, bool required) { if (string.IsNullOrEmpty(bic)) return !required; - return IsValidBic(bic); + return IsValidBic(bic!); } /// diff --git a/QRCoder/PayloadGenerator/Girocode.cs b/QRCoder/PayloadGenerator/Girocode.cs index 00a0c3d0..54c06671 100644 --- a/QRCoder/PayloadGenerator/Girocode.cs +++ b/QRCoder/PayloadGenerator/Girocode.cs @@ -36,7 +36,7 @@ public class Girocode : Payload /// Girocode version. Either 001 or 002. Default: 001. /// Encoding of the Girocode payload. Default: ISO-8859-1 /// Thrown when the input values are not valid according to the Girocode specification. - public Girocode(string iban, string bic, string name, decimal amount, string remittanceInformation = "", TypeOfRemittance typeOfRemittance = TypeOfRemittance.Unstructured, string purposeOfCreditTransfer = "", string messageToGirocodeUser = "", GirocodeVersion version = GirocodeVersion.Version1, GirocodeEncoding encoding = GirocodeEncoding.ISO_8859_1) + public Girocode(string iban, string? bic, string name, decimal amount, string remittanceInformation = "", TypeOfRemittance typeOfRemittance = TypeOfRemittance.Unstructured, string purposeOfCreditTransfer = "", string messageToGirocodeUser = "", GirocodeVersion version = GirocodeVersion.Version1, GirocodeEncoding encoding = GirocodeEncoding.ISO_8859_1) { _version = version; _encoding = encoding; From 42d1be94b496d75db9e11e41204c0245abca69b6 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 5 Oct 2025 10:55:39 -0400 Subject: [PATCH 3/3] update api tests --- .../QRCoder.approved.txt | 2 +- QRCoderApiTests/net60-windows/QRCoder.approved.txt | 2 +- QRCoderApiTests/net60/QRCoder.approved.txt | 2 +- QRCoderApiTests/netstandard13/QRCoder.approved.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/QRCoderApiTests/net35+net40+net50+net50-windows+netstandard20+netstandard21/QRCoder.approved.txt b/QRCoderApiTests/net35+net40+net50+net50-windows+netstandard20+netstandard21/QRCoder.approved.txt index ef3ca5d7..a700b557 100644 --- a/QRCoderApiTests/net35+net40+net50+net50-windows+netstandard20+netstandard21/QRCoder.approved.txt +++ b/QRCoderApiTests/net35+net40+net50+net50-windows+netstandard20+netstandard21/QRCoder.approved.txt @@ -456,7 +456,7 @@ namespace QRCoder } public class Girocode : QRCoder.PayloadGenerator.Payload { - public Girocode(string iban, string bic, string name, decimal amount, string remittanceInformation = "", QRCoder.PayloadGenerator.Girocode.TypeOfRemittance typeOfRemittance = 1, string purposeOfCreditTransfer = "", string messageToGirocodeUser = "", QRCoder.PayloadGenerator.Girocode.GirocodeVersion version = 0, QRCoder.PayloadGenerator.Girocode.GirocodeEncoding encoding = 1) { } + public Girocode(string iban, string? bic, string name, decimal amount, string remittanceInformation = "", QRCoder.PayloadGenerator.Girocode.TypeOfRemittance typeOfRemittance = 1, string purposeOfCreditTransfer = "", string messageToGirocodeUser = "", QRCoder.PayloadGenerator.Girocode.GirocodeVersion version = 0, QRCoder.PayloadGenerator.Girocode.GirocodeEncoding encoding = 1) { } public override QRCoder.QRCodeGenerator.ECCLevel EccLevel { get; } public override string ToString() { } public enum GirocodeEncoding diff --git a/QRCoderApiTests/net60-windows/QRCoder.approved.txt b/QRCoderApiTests/net60-windows/QRCoder.approved.txt index 10d94672..d50a8826 100644 --- a/QRCoderApiTests/net60-windows/QRCoder.approved.txt +++ b/QRCoderApiTests/net60-windows/QRCoder.approved.txt @@ -461,7 +461,7 @@ namespace QRCoder } public class Girocode : QRCoder.PayloadGenerator.Payload { - public Girocode(string iban, string bic, string name, decimal amount, string remittanceInformation = "", QRCoder.PayloadGenerator.Girocode.TypeOfRemittance typeOfRemittance = 1, string purposeOfCreditTransfer = "", string messageToGirocodeUser = "", QRCoder.PayloadGenerator.Girocode.GirocodeVersion version = 0, QRCoder.PayloadGenerator.Girocode.GirocodeEncoding encoding = 1) { } + public Girocode(string iban, string? bic, string name, decimal amount, string remittanceInformation = "", QRCoder.PayloadGenerator.Girocode.TypeOfRemittance typeOfRemittance = 1, string purposeOfCreditTransfer = "", string messageToGirocodeUser = "", QRCoder.PayloadGenerator.Girocode.GirocodeVersion version = 0, QRCoder.PayloadGenerator.Girocode.GirocodeEncoding encoding = 1) { } public override QRCoder.QRCodeGenerator.ECCLevel EccLevel { get; } public override string ToString() { } public enum GirocodeEncoding diff --git a/QRCoderApiTests/net60/QRCoder.approved.txt b/QRCoderApiTests/net60/QRCoder.approved.txt index 76a2b1aa..1db2dc0a 100644 --- a/QRCoderApiTests/net60/QRCoder.approved.txt +++ b/QRCoderApiTests/net60/QRCoder.approved.txt @@ -419,7 +419,7 @@ namespace QRCoder } public class Girocode : QRCoder.PayloadGenerator.Payload { - public Girocode(string iban, string bic, string name, decimal amount, string remittanceInformation = "", QRCoder.PayloadGenerator.Girocode.TypeOfRemittance typeOfRemittance = 1, string purposeOfCreditTransfer = "", string messageToGirocodeUser = "", QRCoder.PayloadGenerator.Girocode.GirocodeVersion version = 0, QRCoder.PayloadGenerator.Girocode.GirocodeEncoding encoding = 1) { } + public Girocode(string iban, string? bic, string name, decimal amount, string remittanceInformation = "", QRCoder.PayloadGenerator.Girocode.TypeOfRemittance typeOfRemittance = 1, string purposeOfCreditTransfer = "", string messageToGirocodeUser = "", QRCoder.PayloadGenerator.Girocode.GirocodeVersion version = 0, QRCoder.PayloadGenerator.Girocode.GirocodeEncoding encoding = 1) { } public override QRCoder.QRCodeGenerator.ECCLevel EccLevel { get; } public override string ToString() { } public enum GirocodeEncoding diff --git a/QRCoderApiTests/netstandard13/QRCoder.approved.txt b/QRCoderApiTests/netstandard13/QRCoder.approved.txt index 5c13586a..d8a1d7e6 100644 --- a/QRCoderApiTests/netstandard13/QRCoder.approved.txt +++ b/QRCoderApiTests/netstandard13/QRCoder.approved.txt @@ -399,7 +399,7 @@ namespace QRCoder } public class Girocode : QRCoder.PayloadGenerator.Payload { - public Girocode(string iban, string bic, string name, decimal amount, string remittanceInformation = "", QRCoder.PayloadGenerator.Girocode.TypeOfRemittance typeOfRemittance = 1, string purposeOfCreditTransfer = "", string messageToGirocodeUser = "", QRCoder.PayloadGenerator.Girocode.GirocodeVersion version = 0, QRCoder.PayloadGenerator.Girocode.GirocodeEncoding encoding = 1) { } + public Girocode(string iban, string? bic, string name, decimal amount, string remittanceInformation = "", QRCoder.PayloadGenerator.Girocode.TypeOfRemittance typeOfRemittance = 1, string purposeOfCreditTransfer = "", string messageToGirocodeUser = "", QRCoder.PayloadGenerator.Girocode.GirocodeVersion version = 0, QRCoder.PayloadGenerator.Girocode.GirocodeEncoding encoding = 1) { } public override QRCoder.QRCodeGenerator.ECCLevel EccLevel { get; } public override string ToString() { } public enum GirocodeEncoding