From cc49c89431db4b7212ac1e187dffcf33273ac522 Mon Sep 17 00:00:00 2001 From: Petre Chitashvili Date: Mon, 29 Sep 2025 14:18:34 +0400 Subject: [PATCH 1/4] Minor --- .claude/settings.local.json | 10 ++++++++++ PROJECTNAME | 1 + 2 files changed, 11 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 PROJECTNAME diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..0d5cd31 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(unzip:*)", + "Bash(git pull:*)", + "Bash(python3:*)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/PROJECTNAME b/PROJECTNAME new file mode 100644 index 0000000..e7f6efc --- /dev/null +++ b/PROJECTNAME @@ -0,0 +1 @@ +TBC-IntegrationService \ No newline at end of file From b3dce0b8a5188ebfed9f578a9f593f1e3de524de Mon Sep 17 00:00:00 2001 From: Petre Chitashvili Date: Mon, 29 Sep 2025 15:54:05 +0400 Subject: [PATCH 2/4] feat: Add SOAP fault response handling and deserialization - Add SoapFaultResponse record with proper XML serialization attributes - Enhance TBCSoapCaller to parse SOAP faults on HTTP errors - Add TryParseSoapFault method for structured fault parsing - Provide meaningful error messages for fault codes like USER_IS_BLOCKED - Add comprehensive test coverage for SOAP fault scenarios *Collaboration by Claude* --- .../SoapInfrastructure/SoapFaultResponse.cs | 41 +++++++++ .../TBC_Services/TBCSoapCaller.cs | 33 ++++++- .../SoapFaultResponseTests.cs | 91 +++++++++++++++++++ 3 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 AppifySheets.TBC.IntegrationService.Client/SoapInfrastructure/SoapFaultResponse.cs create mode 100644 AppifySheets.TBC.IntegrationService.Tests/SoapFaultResponseTests.cs diff --git a/AppifySheets.TBC.IntegrationService.Client/SoapInfrastructure/SoapFaultResponse.cs b/AppifySheets.TBC.IntegrationService.Client/SoapInfrastructure/SoapFaultResponse.cs new file mode 100644 index 0000000..4ddc405 --- /dev/null +++ b/AppifySheets.TBC.IntegrationService.Client/SoapInfrastructure/SoapFaultResponse.cs @@ -0,0 +1,41 @@ +using System; +using System.Xml.Serialization; + +namespace AppifySheets.TBC.IntegrationService.Client.SoapInfrastructure; + +/// +/// Represents a SOAP fault response from TBC Bank API +/// Contains error code and message when API calls fail +/// +[XmlRoot("Fault", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")] +public sealed record SoapFaultResponse : ISoapResponse +{ + /// + /// The fault code indicating the type of error (e.g., "a:USER_IS_BLOCKED") + /// + [XmlElement("faultcode", Namespace = "")] + public string FaultCode { get; init; } = string.Empty; + + /// + /// Human-readable description of the error + /// + [XmlElement("faultstring", Namespace = "")] + public string FaultString { get; init; } = string.Empty; + + /// + /// Creates a SoapFaultResponse with the specified fault code and message + /// + public static SoapFaultResponse Create(string faultCode, string faultString) => + new() { FaultCode = faultCode, FaultString = faultString }; + + /// + /// Gets a formatted error message combining fault code and string + /// + public string FormattedError => $"SOAP Fault [{FaultCode}]: {FaultString}"; + + /// + /// Checks if this represents a specific fault code + /// + public bool IsFaultCode(string faultCode) => + string.Equals(FaultCode, faultCode, StringComparison.OrdinalIgnoreCase); +} \ No newline at end of file diff --git a/AppifySheets.TBC.IntegrationService.Client/TBC_Services/TBCSoapCaller.cs b/AppifySheets.TBC.IntegrationService.Client/TBC_Services/TBCSoapCaller.cs index 74ef1b0..ca277c2 100644 --- a/AppifySheets.TBC.IntegrationService.Client/TBC_Services/TBCSoapCaller.cs +++ b/AppifySheets.TBC.IntegrationService.Client/TBC_Services/TBCSoapCaller.cs @@ -100,8 +100,6 @@ public Task> CallTBCServiceAsync(RequestSoap> CallTBCServiceAsync(PerformedActionSoapEnvelope performedActionSoapEnvelope) { const string url = "https://secdbi.tbconline.ge/dbi/dbiService"; @@ -137,7 +135,11 @@ async Task> CallTBCServiceAsync(PerformedActionSoapEnvelope perfo } catch (Exception) { - return Result.Failure(responseContent.FormatXml()); + // Try to parse SOAP fault first, fallback to formatted XML + var faultParseResult = TryParseSoapFault(responseContent); + return Result.Failure(faultParseResult.IsSuccess + ? faultParseResult.Value.FormattedError + : responseContent.FormatXml()); } X509Certificate2Collection GetCertificates() @@ -147,4 +149,29 @@ X509Certificate2Collection GetCertificates() return collection; } } + + /// + /// Attempts to parse SOAP fault from response content + /// + static Result TryParseSoapFault(string responseContent) + { + try + { + var doc = new XmlDocument(); + doc.LoadXml(responseContent); + + var nsManager = new XmlNamespaceManager(doc.NameTable); + nsManager.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/"); + + var faultNode = doc.SelectSingleNode("//s:Fault", nsManager); + if (faultNode == null) + return Result.Failure("No SOAP fault found in response"); + + return faultNode.OuterXml.XmlDeserializeFromString(); + } + catch (Exception ex) + { + return Result.Failure($"Failed to parse SOAP fault: {ex.Message}"); + } + } } \ No newline at end of file diff --git a/AppifySheets.TBC.IntegrationService.Tests/SoapFaultResponseTests.cs b/AppifySheets.TBC.IntegrationService.Tests/SoapFaultResponseTests.cs new file mode 100644 index 0000000..1e7ba53 --- /dev/null +++ b/AppifySheets.TBC.IntegrationService.Tests/SoapFaultResponseTests.cs @@ -0,0 +1,91 @@ +using AppifySheets.TBC.IntegrationService.Client.SoapInfrastructure; +using AppifySheets.TBC.IntegrationService.Client.TBC_Services; +using CSharpFunctionalExtensions; +using Shouldly; +using Xunit; + +namespace AppifySheets.TBC.IntegrationService.Tests; + +public class SoapFaultResponseTests +{ + [Fact] + public void Should_Deserialize_SOAP_Fault_Response() + { + // Arrange + const string soapFaultXml = """ + + + + + + a:USER_IS_BLOCKED + User is blocked. + + + + """; + + // Act + var result = soapFaultXml.DeserializeInto(); + + // Assert - deserialization should fail with SOAP fault error message + result.IsFailure.ShouldBeTrue(); + result.Error.ShouldContain("USER_IS_BLOCKED"); + result.Error.ShouldContain("User is blocked"); + } + + + [Fact] + public void Should_Create_SoapFaultResponse_With_Static_Factory() + { + // Act + var fault = SoapFaultResponse.Create("a:USER_IS_BLOCKED", "User is blocked."); + + // Assert + fault.FaultCode.ShouldBe("a:USER_IS_BLOCKED"); + fault.FaultString.ShouldBe("User is blocked."); + fault.FormattedError.ShouldBe("SOAP Fault [a:USER_IS_BLOCKED]: User is blocked."); + } + + [Fact] + public void Should_Check_Fault_Code_Case_Insensitive() + { + // Arrange + var fault = SoapFaultResponse.Create("a:USER_IS_BLOCKED", "User is blocked."); + + // Act & Assert + fault.IsFaultCode("a:user_is_blocked").ShouldBeTrue(); + fault.IsFaultCode("A:USER_IS_BLOCKED").ShouldBeTrue(); + fault.IsFaultCode("a:USER_IS_BLOCKED").ShouldBeTrue(); + fault.IsFaultCode("different_code").ShouldBeFalse(); + } + + [Fact] + public void TryParseSoapFault_Should_Parse_Valid_Fault() + { + // Arrange + const string soapFaultXml = """ + + + + + + a:USER_IS_BLOCKED + User is blocked. + + + + """; + + // Act - use reflection to call the private static method + var method = typeof(TBCSoapCaller).GetMethod("TryParseSoapFault", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + var result = (Result)method!.Invoke(null, [soapFaultXml])!; + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.FaultCode.ShouldBe("a:USER_IS_BLOCKED"); + result.Value.FaultString.ShouldBe("User is blocked."); + result.Value.FormattedError.ShouldBe("SOAP Fault [a:USER_IS_BLOCKED]: User is blocked."); + } +} \ No newline at end of file From 5ef2fa4e1efe728a07b994a5755f640a96607c8f Mon Sep 17 00:00:00 2001 From: Petre Chitashvili Date: Mon, 29 Sep 2025 16:10:26 +0400 Subject: [PATCH 3/4] chore: Bump version to 2.1.1 - Add SOAP fault response handling and deserialization - Improve error messages for TBC Bank API failures *Collaboration by Claude* --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 50aea0e..7c32728 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1.0 \ No newline at end of file +2.1.1 \ No newline at end of file From bf99f000f2a8e3b05f51a250e710209b3dec0432 Mon Sep 17 00:00:00 2001 From: Petre Chitashvili Date: Tue, 30 Sep 2025 14:07:44 +0400 Subject: [PATCH 4/4] chore: Add PublicAPI annotation and update documentation - Add JetBrains.Annotations [PublicAPI] attribute to BankTransferCommonDetails - Fix password change documentation link in README - Update Claude Code settings to allow dotnet test command *Collaboration by Claude* --- .claude/settings.local.json | 3 ++- .../ImportSinglePaymentOrders/TransferTypeInterfaces.cs | 2 ++ README.md | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 0d5cd31..9f91dd3 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -3,7 +3,8 @@ "allow": [ "Bash(unzip:*)", "Bash(git pull:*)", - "Bash(python3:*)" + "Bash(python3:*)", + "Bash(dotnet test)" ], "deny": [] } diff --git a/AppifySheets.TBC.IntegrationService.Client/SoapInfrastructure/ImportSinglePaymentOrders/TransferTypeInterfaces.cs b/AppifySheets.TBC.IntegrationService.Client/SoapInfrastructure/ImportSinglePaymentOrders/TransferTypeInterfaces.cs index 1c9f610..8f72d80 100644 --- a/AppifySheets.TBC.IntegrationService.Client/SoapInfrastructure/ImportSinglePaymentOrders/TransferTypeInterfaces.cs +++ b/AppifySheets.TBC.IntegrationService.Client/SoapInfrastructure/ImportSinglePaymentOrders/TransferTypeInterfaces.cs @@ -1,7 +1,9 @@ using AppifySheets.Immutable.BankIntegrationTypes; +using JetBrains.Annotations; namespace AppifySheets.TBC.IntegrationService.Client.SoapInfrastructure.ImportSinglePaymentOrders; +[PublicAPI] public sealed record BankTransferCommonDetails { public required BankAccount SenderAccountWithCurrency { get; init; } diff --git a/README.md b/README.md index 05a685a..c670d0c 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ dotnet add package AppifySheets.TBC.IntegrationService.Client * Import Single Payment Orders - Execute various types of payment transfers * Get Account Movements - Retrieve account transaction history * Get Payment Order Status - Check status of submitted payment orders -* Change Password - Change API user password +* Change Password - Change API user password ## Usage Examples