diff --git a/QRCoder/PayloadGenerator/Girocode.cs b/QRCoder/PayloadGenerator/Girocode.cs index fedd794d..100daf74 100644 --- a/QRCoder/PayloadGenerator/Girocode.cs +++ b/QRCoder/PayloadGenerator/Girocode.cs @@ -12,7 +12,7 @@ public class Girocode : Payload private readonly string _br = "\n"; private readonly string _iban, _bic, _name, _purposeOfCreditTransfer, _remittanceInformation, _messageToGirocodeUser; - private readonly decimal _amount; + private readonly decimal? _amount; private readonly GirocodeVersion _version; private readonly GirocodeEncoding _encoding; private readonly TypeOfRemittance _typeOfRemittance; @@ -28,7 +28,7 @@ public class Girocode : Payload /// Account number of the Beneficiary. Only IBAN is allowed. /// BIC of the Beneficiary Bank. /// Name of the Beneficiary. - /// Amount of the Credit Transfer in Euro. (Amount must be more than 0.01 and less than 999999999.99) + /// Amount of the Credit Transfer in Euro, from 0.01 to 999999999.99 inclusive, with at most two decimal places. /// Remittance Information (Purpose-/reference text). (optional) /// Type of remittance information. Either structured (e.g. ISO 11649 RF Creditor Reference) and max. 35 chars or unstructured and max. 140 chars. /// Purpose of the Credit Transfer (optional) @@ -37,6 +37,25 @@ public class Girocode : Payload /// 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) + : this(iban, bic, name, (decimal?)amount, remittanceInformation, typeOfRemittance, purposeOfCreditTransfer, messageToGirocodeUser, version, encoding) + { + } + + /// + /// Initializes a new instance of the class, which contains a payload for a Girocode (QR-Code with credit transfer information). + /// + /// Account number of the Beneficiary. Only IBAN is allowed. + /// BIC of the Beneficiary Bank. + /// Name of the Beneficiary. + /// Amount of the Credit Transfer in Euro, from 0.01 to 999999999.99 inclusive, with at most two decimal places. Null or omitted leaves the amount empty for the payer to enter. + /// Remittance Information (Purpose-/reference text). (optional) + /// Type of remittance information. Either structured (e.g. ISO 11649 RF Creditor Reference) and max. 35 chars or unstructured and max. 140 chars. + /// Purpose of the Credit Transfer (optional) + /// Beneficiary to originator information. (optional) + /// 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 = null, string remittanceInformation = "", TypeOfRemittance typeOfRemittance = TypeOfRemittance.Unstructured, string purposeOfCreditTransfer = "", string messageToGirocodeUser = "", GirocodeVersion version = GirocodeVersion.Version1, GirocodeEncoding encoding = GirocodeEncoding.ISO_8859_1) { _version = version; _encoding = encoding; @@ -49,7 +68,7 @@ public Girocode(string iban, string? bic, string name, decimal amount, string re if (name.Length > 70) throw new GirocodeException("(Payee-)Name must be shorter than 71 chars."); _name = name; - if (amount.ToString(CultureInfo.InvariantCulture).Contains('.') && amount.ToString(CultureInfo.InvariantCulture).Split('.')[1].TrimEnd('0').Length > 2) + if (amount.HasValue && amount.Value.ToString(CultureInfo.InvariantCulture).Contains('.') && amount.Value.ToString(CultureInfo.InvariantCulture).Split('.')[1].TrimEnd('0').Length > 2) throw new GirocodeException("Amount must have less than 3 digits after decimal point."); if (amount < 0.01m || amount > 999999999.99m) throw new GirocodeException("Amount has to be at least 0.01 and must be smaller or equal to 999999999.99."); @@ -81,7 +100,7 @@ public override string ToString() girocodePayload += _bic + _br; girocodePayload += _name + _br; girocodePayload += _iban + _br; - girocodePayload += string.Format(CultureInfo.InvariantCulture, "EUR{0:0.00}", _amount) + _br; + girocodePayload += (_amount.HasValue ? string.Format(CultureInfo.InvariantCulture, "EUR{0:0.00}", _amount.Value) : string.Empty) + _br; girocodePayload += _purposeOfCreditTransfer + _br; girocodePayload += ((_typeOfRemittance == TypeOfRemittance.Structured) ? _remittanceInformation @@ -91,6 +110,8 @@ public override string ToString() : string.Empty) + _br; girocodePayload += _messageToGirocodeUser; + girocodePayload = girocodePayload.TrimEnd('\n'); + return ConvertStringToEncoding(girocodePayload, _encoding.ToString().Replace("_", "-")); } diff --git a/QRCoderApiTests/net35+net40+net50+netstandard20+netstandard21/QRCoder.approved.txt b/QRCoderApiTests/net35+net40+net50+netstandard20+netstandard21/QRCoder.approved.txt index fa119ea8..47de8dd3 100644 --- a/QRCoderApiTests/net35+net40+net50+netstandard20+netstandard21/QRCoder.approved.txt +++ b/QRCoderApiTests/net35+net40+net50+netstandard20+netstandard21/QRCoder.approved.txt @@ -457,6 +457,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 = default, 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 c0599c3a..cbf67f98 100644 --- a/QRCoderApiTests/net60/QRCoder.approved.txt +++ b/QRCoderApiTests/net60/QRCoder.approved.txt @@ -462,6 +462,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 = default, 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 f127e9ef..7602755a 100644 --- a/QRCoderApiTests/netstandard13/QRCoder.approved.txt +++ b/QRCoderApiTests/netstandard13/QRCoder.approved.txt @@ -400,6 +400,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 = default, 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/QRCoderTests/PayloadGeneratorTests/GirocodeTests.cs b/QRCoderTests/PayloadGeneratorTests/GirocodeTests.cs index 95d40c6d..81a88bd6 100644 --- a/QRCoderTests/PayloadGeneratorTests/GirocodeTests.cs +++ b/QRCoderTests/PayloadGeneratorTests/GirocodeTests.cs @@ -16,7 +16,7 @@ public void girocode_generator_can_generate_payload_minimal() generator .ToString() - .ShouldBe("BCD\n001\n2\nSCT\nBFSWDE33BER\nWikimedia Fördergesellschaft\nDE33100205000001194700\nEUR10.00\n\n\n\n"); + .ShouldBe("BCD\n001\n2\nSCT\nBFSWDE33BER\nWikimedia Fördergesellschaft\nDE33100205000001194700\nEUR10.00"); } [Fact] @@ -341,7 +341,7 @@ public void girocode_generator_version2_with_null_bic_should_succeed() payload .ToString() - .ShouldBe("BCD\n002\n1\nSCT\n\na name\nNL86INGB0002445588\nEUR1337.99\n\n\nsome info\n"); + .ShouldBe("BCD\n002\n1\nSCT\n\na name\nNL86INGB0002445588\nEUR1337.99\n\n\nsome info"); } [Fact] @@ -363,7 +363,7 @@ public void girocode_generator_version2_with_empty_bic_should_succeed() payload .ToString() - .ShouldBe("BCD\n002\n1\nSCT\n\na name\nNL86INGB0002445588\nEUR1337.99\n\n\nsome info\n"); + .ShouldBe("BCD\n002\n1\nSCT\n\na name\nNL86INGB0002445588\nEUR1337.99\n\n\nsome info"); } [Fact] @@ -386,7 +386,7 @@ public void girocode_generator_version2_with_valid_bic_should_succeed() payload .ToString() - .ShouldBe("BCD\n002\n1\nSCT\nINGBNL2A\na name\nNL86INGB0002445588\nEUR1337.99\n\n\nsome info\n"); + .ShouldBe("BCD\n002\n1\nSCT\nINGBNL2A\na name\nNL86INGB0002445588\nEUR1337.99\n\n\nsome info"); } [Fact] @@ -455,4 +455,144 @@ public void girocode_generator_version1_with_empty_bic_should_throw_exception() Assert.IsType(exception); exception.Message.ShouldBe("The BIC entered isn't valid."); } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void girocode_generator_can_generate_payload_without_amount(bool omitAmount) + { + var iban = "DE33100205000001194700"; + var bic = "BFSWDE33BER"; + var name = "Payee"; + + var generator = omitAmount + ? new PayloadGenerator.Girocode(iban, bic, name) + : new PayloadGenerator.Girocode(iban, bic, name, amount: null); + + generator + .ToString() + .ShouldBe("BCD\n001\n2\nSCT\nBFSWDE33BER\nPayee\nDE33100205000001194700"); + } + + [Theory] + [InlineData(false, "CHAR", "", PayloadGenerator.Girocode.TypeOfRemittance.Unstructured, "", "\n\nCHAR")] + [InlineData(false, "", "Reference", PayloadGenerator.Girocode.TypeOfRemittance.Structured, "", "\n\n\nReference")] + [InlineData(false, "", "Reference", PayloadGenerator.Girocode.TypeOfRemittance.Unstructured, "", "\n\n\n\nReference")] + [InlineData(false, "", "", PayloadGenerator.Girocode.TypeOfRemittance.Unstructured, "Thank you ", "\n\n\n\n\nThank you ")] + [InlineData(true, "CHAR", "", PayloadGenerator.Girocode.TypeOfRemittance.Unstructured, "", "\nEUR10.00\nCHAR")] + [InlineData(true, "", "Reference", PayloadGenerator.Girocode.TypeOfRemittance.Structured, "", "\nEUR10.00\n\nReference")] + [InlineData(true, "", "Reference", PayloadGenerator.Girocode.TypeOfRemittance.Unstructured, "", "\nEUR10.00\n\n\nReference")] + [InlineData(true, "", "", PayloadGenerator.Girocode.TypeOfRemittance.Unstructured, "Thank you ", "\nEUR10.00\n\n\n\nThank you ")] + public void girocode_generator_should_end_payload_at_last_populated_field( + bool hasAmount, + string purposeOfCreditTransfer, + string remittanceInformation, + PayloadGenerator.Girocode.TypeOfRemittance typeOfRemittance, + string messageToGirocodeUser, + string expectedSuffix) + { + var iban = "DE33100205000001194700"; + var bic = "BFSWDE33BER"; + var name = "Payee"; + + var generator = new PayloadGenerator.Girocode(iban, bic, name, amount: hasAmount ? 10m : (decimal?)null, + remittanceInformation: remittanceInformation, typeOfRemittance: typeOfRemittance, + purposeOfCreditTransfer: purposeOfCreditTransfer, messageToGirocodeUser: messageToGirocodeUser); + + generator + .ToString() + .ShouldBe("BCD\n001\n2\nSCT\nBFSWDE33BER\nPayee\nDE33100205000001194700" + expectedSuffix); + } + + [Theory] + [InlineData(false, PayloadGenerator.Girocode.GirocodeVersion.Version1, PayloadGenerator.Girocode.TypeOfRemittance.Structured)] + [InlineData(false, PayloadGenerator.Girocode.GirocodeVersion.Version1, PayloadGenerator.Girocode.TypeOfRemittance.Unstructured)] + [InlineData(false, PayloadGenerator.Girocode.GirocodeVersion.Version2, PayloadGenerator.Girocode.TypeOfRemittance.Structured)] + [InlineData(false, PayloadGenerator.Girocode.GirocodeVersion.Version2, PayloadGenerator.Girocode.TypeOfRemittance.Unstructured)] + [InlineData(true, PayloadGenerator.Girocode.GirocodeVersion.Version1, PayloadGenerator.Girocode.TypeOfRemittance.Structured)] + [InlineData(true, PayloadGenerator.Girocode.GirocodeVersion.Version1, PayloadGenerator.Girocode.TypeOfRemittance.Unstructured)] + [InlineData(true, PayloadGenerator.Girocode.GirocodeVersion.Version2, PayloadGenerator.Girocode.TypeOfRemittance.Structured)] + [InlineData(true, PayloadGenerator.Girocode.GirocodeVersion.Version2, PayloadGenerator.Girocode.TypeOfRemittance.Unstructured)] + public void girocode_generator_should_preserve_field_positions_without_amount( + bool omitAmount, + PayloadGenerator.Girocode.GirocodeVersion version, + PayloadGenerator.Girocode.TypeOfRemittance typeOfRemittance) + { + var iban = "DE33100205000001194700"; + var bic = version == PayloadGenerator.Girocode.GirocodeVersion.Version1 ? "BFSWDE33BER" : null; + var name = "Payee"; + var remittanceInformation = "Reference"; + var purposeOfCreditTransfer = "CHAR"; + var messageToGirocodeUser = "Thank you"; + + var generator = omitAmount + ? new PayloadGenerator.Girocode(iban, bic, name, remittanceInformation: remittanceInformation, + typeOfRemittance: typeOfRemittance, purposeOfCreditTransfer: purposeOfCreditTransfer, + messageToGirocodeUser: messageToGirocodeUser, version: version) + : new PayloadGenerator.Girocode(iban, bic, name, amount: null, remittanceInformation: remittanceInformation, + typeOfRemittance: typeOfRemittance, purposeOfCreditTransfer: purposeOfCreditTransfer, + messageToGirocodeUser: messageToGirocodeUser, version: version); + + generator + .ToString() + .Split('\n') + .ShouldBe(new[] + { + "BCD", + version == PayloadGenerator.Girocode.GirocodeVersion.Version1 ? "001" : "002", + "2", + "SCT", + bic ?? string.Empty, + "Payee", + iban, + string.Empty, + "CHAR", + typeOfRemittance == PayloadGenerator.Girocode.TypeOfRemittance.Structured ? "Reference" : string.Empty, + typeOfRemittance == PayloadGenerator.Girocode.TypeOfRemittance.Unstructured ? "Reference" : string.Empty, + "Thank you", + }); + } + + [Theory] + [InlineData("0.01", "EUR0.01")] + [InlineData("999999999.99", "EUR999999999.99")] + [InlineData("12", "EUR12.00")] + [InlineData("12.3400", "EUR12.34")] + public void girocode_generator_should_format_specified_amount(string amountText, string expectedAmount) + { + var iban = "DE33100205000001194700"; + var bic = "BFSWDE33BER"; + var name = "Payee"; + var amount = decimal.Parse(amountText, CultureInfo.InvariantCulture); + + var generator = new PayloadGenerator.Girocode(iban, bic, name, amount); + var nullableGenerator = new PayloadGenerator.Girocode(iban, bic, name, (decimal?)amount); + + generator + .ToString() + .ShouldBe($"BCD\n001\n2\nSCT\nBFSWDE33BER\nPayee\nDE33100205000001194700\n{expectedAmount}"); + nullableGenerator + .ToString() + .ShouldBe(generator.ToString()); + } + + [Theory] + [InlineData("0", "Amount has to be at least 0.01 and must be smaller or equal to 999999999.99.")] + [InlineData("-0.01", "Amount has to be at least 0.01 and must be smaller or equal to 999999999.99.")] + [InlineData("1000000000", "Amount has to be at least 0.01 and must be smaller or equal to 999999999.99.")] + [InlineData("0.001", "Amount must have less than 3 digits after decimal point.")] + [InlineData("10.521", "Amount must have less than 3 digits after decimal point.")] + public void girocode_generator_should_throw_invalid_amount_exception(string amountText, string expectedMessage) + { + var iban = "DE33100205000001194700"; + var bic = "BFSWDE33BER"; + var name = "Payee"; + var amount = decimal.Parse(amountText, CultureInfo.InvariantCulture); + + var exception = Should.Throw(() => new PayloadGenerator.Girocode(iban, bic, name, amount)); + var nullableException = Should.Throw(() => new PayloadGenerator.Girocode(iban, bic, name, (decimal?)amount)); + + exception.Message.ShouldBe(expectedMessage); + nullableException.Message.ShouldBe(expectedMessage); + } }