From 5a4ed169eae419476d9fe3a99ea2744d22b691c0 Mon Sep 17 00:00:00 2001 From: Michael Hachen Date: Fri, 9 May 2025 08:21:52 +0200 Subject: [PATCH] - Add new method overload for QRBill.DecodeQrCodeText to allow invalid amount value (currently only "NULL" / "null") --- Core/QRBill.cs | 21 +++++++++- Core/QRCodeText.cs | 9 ++++- CoreTest/CoreTest.csproj | 5 +-- CoreTest/DecodedTextTest.cs | 62 ++++++++++++++++++++++++++++++ CoreTest/SampleData.cs | 76 +++++++++++++++++++++++++++++++++++++ 5 files changed, 167 insertions(+), 6 deletions(-) diff --git a/Core/QRBill.cs b/Core/QRBill.cs index 5e2b035..727cb49 100644 --- a/Core/QRBill.cs +++ b/Core/QRBill.cs @@ -312,7 +312,26 @@ public static string EncodeQrCodeText(Bill bill) /// If he text could not be decoded or the decoded bill data is invalid. public static Bill DecodeQrCodeText(string text) { - return QRCodeText.Decode(text); + return QRCodeText.Decode(text, false); + } + + /// + /// Decodes the text from a QR code and fills it into a data structure + /// + /// A subset of the validations related to embedded QR code text is run. It the + /// validation fails, a is thrown containing + /// the validation result. See the error messages marked with a dagger in + /// Bill data + /// validation. + /// + /// + /// The text to decode. + /// If true is passed for this parameter, invalid values for the amount are also accepted (currently only "NULL" / "null"). In this case, the amount is set to null. + /// The decoded bill data. + /// If he text could not be decoded or the decoded bill data is invalid. + public static Bill DecodeQrCodeText(string text, bool allowInvalidAmount) + { + return QRCodeText.Decode(text, allowInvalidAmount); } private static ICanvas CreateCanvas(BillFormat format) diff --git a/Core/QRCodeText.cs b/Core/QRCodeText.cs index 9bd1a86..86a5bf0 100644 --- a/Core/QRCodeText.cs +++ b/Core/QRCodeText.cs @@ -127,7 +127,7 @@ private static string FormatAmountForCode(decimal amount) } private static readonly Regex ValidVersion = new Regex(@"^02\d\d$", RegexOptions.Compiled); - + /// /// Decodes the specified text and returns the bill data. /// @@ -139,10 +139,11 @@ private static string FormatAmountForCode(decimal amount) /// /// /// The text to decode. + /// If true is passed for this parameter, invalid values for the amount are also accepted (currently only "NULL" / "null"). In this case, the amount is set to null. /// The decoded bill data. /// The text is in an invalid format. [System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S1066:Mergeable \"if\" statements should be combined", Justification = "Easier to read the way it is")] - public static Bill Decode(string text) + public static Bill Decode(string text, bool allowInvalidAmount) { var lines = SplitLines(text); if (lines.Count < 31 || lines.Count > 34) @@ -186,6 +187,10 @@ public static Bill Decode(string text) { billData.Amount = amount; } + else if (allowInvalidAmount && lines[18].Equals("NULL", StringComparison.OrdinalIgnoreCase)) + { + billData.Amount = null; + } else { ThrowSingleValidationError(ValidationConstants.FieldAmount, ValidationConstants.KeyNumberInvalid); diff --git a/CoreTest/CoreTest.csproj b/CoreTest/CoreTest.csproj index f5ff91b..c97a258 100644 --- a/CoreTest/CoreTest.csproj +++ b/CoreTest/CoreTest.csproj @@ -40,7 +40,7 @@ - + @@ -52,7 +52,6 @@ - - + diff --git a/CoreTest/DecodedTextTest.cs b/CoreTest/DecodedTextTest.cs index 5c5e6e9..d6c4c54 100644 --- a/CoreTest/DecodedTextTest.cs +++ b/CoreTest/DecodedTextTest.cs @@ -176,6 +176,68 @@ public void DecodeMissingEpd() () => QRBill.DecodeQrCodeText(invalidText)); TestHelper.AssertSingleError(err.Result, ValidationConstants.KeyDataStructureInvalid, ValidationConstants.FieldTrailer); } + + [Fact] + public void DecodeMethodShouldReturnValidBillWhenQrCodeTextIsValid() + { + var bill = QRBill.DecodeQrCodeText(SampleData.CreateQrCode1()); + Assert.NotNull(bill); + Assert.Equal(Bill.QrBillStandardVersion.V2_0, bill.Version); + Assert.Equal(2500.00m, bill.Amount); + Assert.Equal("CHF", bill.Currency); + Assert.Equal("CH1234567890123456789", bill.Account); + Assert.Equal("Steuerverwaltung der Stadt Bern", bill.Creditor.Name); + Assert.Equal("Bundesgasse", bill.Creditor.Street); + Assert.Equal("33", bill.Creditor.HouseNo); + Assert.Equal("3011", bill.Creditor.PostalCode); + Assert.Equal("Bern", bill.Creditor.Town); + Assert.Equal("CH", bill.Creditor.CountryCode); + Assert.Equal("Martina Muster", bill.Debtor.Name); + Assert.Equal("Bubenbergplatz", bill.Debtor.Street); + Assert.Equal("1", bill.Debtor.HouseNo); + Assert.Equal("3011", bill.Debtor.PostalCode); + Assert.Equal("Bern", bill.Debtor.Town); + Assert.Equal("CH", bill.Debtor.CountryCode); + Assert.Equal("QRR", bill.ReferenceType); + Assert.Equal("123456789012345678901234567", bill.Reference); + Assert.Equal("1. Steuerrate 2020", bill.UnstructuredMessage); + Assert.Equal("//S1/11/200627/30/115140892/31/200627/32/7.7/40/0:30", bill.BillInformation); + } + + [Fact] + public void DecodeMethodShouldHandleInvalidAmountIfParameterIsSupplied() + { + var bill = QRBill.DecodeQrCodeText(SampleData.CreateInvalidQrCode1(), true); + Assert.NotNull(bill); + Assert.Equal(Bill.QrBillStandardVersion.V2_0, bill.Version); + Assert.Null(bill.Amount); + Assert.Equal("CHF", bill.Currency); + Assert.Equal("CH8430000001800003797", bill.Account); + Assert.Equal("AXA Versicherungen AG", bill.Creditor.Name); + Assert.Equal("General-Guisan-Str.", bill.Creditor.Street); + Assert.Equal("40", bill.Creditor.HouseNo); + Assert.Equal("8401", bill.Creditor.PostalCode); + Assert.Equal("Winterthur", bill.Creditor.Town); + Assert.Equal("CH", bill.Creditor.CountryCode); + Assert.Equal("Testfirma AG", bill.Debtor.Name); + Assert.Equal("Hauptstrasse", bill.Debtor.Street); + Assert.Equal("61", bill.Debtor.HouseNo); + Assert.Equal("6210", bill.Debtor.PostalCode); + Assert.Equal("Sursee", bill.Debtor.Town); + Assert.Equal("CH", bill.Debtor.CountryCode); + Assert.Equal("QRR", bill.ReferenceType); + Assert.Equal("000000001000285497220812814", bill.Reference); + Assert.Equal("", bill.UnstructuredMessage); + Assert.Equal("", bill.BillInformation); + } + + [Fact] + public void DecodeMethodShouldFailIfAmountIsInvalidValue() + { + QRBillValidationException err = Assert.Throws( + () => QRBill.DecodeQrCodeText(SampleData.CreateInvalidQrCode1())); + TestHelper.AssertSingleError(err.Result, ValidationConstants.KeyNumberInvalid, ValidationConstants.FieldAmount); + } private class NewLineTheoryData : TheoryData { diff --git a/CoreTest/SampleData.cs b/CoreTest/SampleData.cs index aa19f5d..9ed3a71 100644 --- a/CoreTest/SampleData.cs +++ b/CoreTest/SampleData.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Numerics; using System.Security.Principal; +using System.Text; namespace Codecrete.SwissQRBill.CoreTest @@ -265,5 +266,80 @@ public static Bill CreateExample8() return bill; } + + public static string CreateQrCode1() + { + var qrCodeText = new StringBuilder(); + qrCodeText.AppendLine("SPC"); + qrCodeText.AppendLine("0200"); + qrCodeText.AppendLine("1"); + qrCodeText.AppendLine("CH1234567890123456789"); + qrCodeText.AppendLine("S"); + qrCodeText.AppendLine("Steuerverwaltung der Stadt Bern"); + qrCodeText.AppendLine("Bundesgasse"); + qrCodeText.AppendLine("33"); + qrCodeText.AppendLine("3011"); + qrCodeText.AppendLine("Bern"); + qrCodeText.AppendLine("CH"); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine("2500.00"); + qrCodeText.AppendLine("CHF"); + qrCodeText.AppendLine("S"); + qrCodeText.AppendLine("Martina Muster"); + qrCodeText.AppendLine("Bubenbergplatz"); + qrCodeText.AppendLine("1"); + qrCodeText.AppendLine("3011"); + qrCodeText.AppendLine("Bern"); + qrCodeText.AppendLine("CH"); + qrCodeText.AppendLine("QRR"); + qrCodeText.AppendLine("123456789012345678901234567"); + qrCodeText.AppendLine("1. Steuerrate 2020"); + qrCodeText.AppendLine("EPD"); + qrCodeText.AppendLine("//S1/11/200627/30/115140892/31/200627/32/7.7/40/0:30"); + return qrCodeText.ToString(); + } + + public static string CreateInvalidQrCode1() + { + var qrCodeText = new StringBuilder(); + qrCodeText.AppendLine("SPC"); + qrCodeText.AppendLine("0200"); + qrCodeText.AppendLine("1"); + qrCodeText.AppendLine("CH8430000001800003797"); + qrCodeText.AppendLine("S"); + qrCodeText.AppendLine("AXA Versicherungen AG"); + qrCodeText.AppendLine("General-Guisan-Str."); + qrCodeText.AppendLine("40"); + qrCodeText.AppendLine("8401"); + qrCodeText.AppendLine("Winterthur"); + qrCodeText.AppendLine("CH"); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine("null"); + qrCodeText.AppendLine("CHF"); + qrCodeText.AppendLine("S"); + qrCodeText.AppendLine("Testfirma AG"); + qrCodeText.AppendLine("Hauptstrasse"); + qrCodeText.AppendLine("61"); + qrCodeText.AppendLine("6210"); + qrCodeText.AppendLine("Sursee"); + qrCodeText.AppendLine("CH"); + qrCodeText.AppendLine("QRR"); + qrCodeText.AppendLine("000000001000285497220812814"); + qrCodeText.AppendLine(""); + qrCodeText.AppendLine("EPD"); + return qrCodeText.ToString(); + } } }