From bc6a9531681e88c15e068e3e24b3c39e350daa7b Mon Sep 17 00:00:00 2001 From: Stanislav Smetanin Date: Mon, 16 Jun 2025 20:28:47 +1000 Subject: [PATCH 1/2] Changes related with custom user/product fields. User fields: - EntityUseCode -> AvalaraEntityUseCode (displayed name Avalara Entity Use Code) - ExemptionNumber -> AvalaraExemptionNumber (displayed name Avalara Exemption Number) Product fields: - ItemCode -> AvalaraItemCode (displayed names "Avalara Item Code") - TaxCode-> AvalaraTaxCode (displayed names "Avalara Tax Code") API changes: CustomerUsageType is outdated, replaced by EntityUseCode request field. --- src/AvalaraTaxProvider.cs | 79 ++++++++++--------- ...rce.TaxProviders.AvalaraTaxProvider.csproj | 6 +- .../CreateTransactionRequest.cs | 9 +-- src/Service/PrepareTransactionHelper.cs | 10 +-- 4 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/AvalaraTaxProvider.cs b/src/AvalaraTaxProvider.cs index 1dc1179..1dad87e 100644 --- a/src/AvalaraTaxProvider.cs +++ b/src/AvalaraTaxProvider.cs @@ -29,10 +29,10 @@ public class AvalaraTaxProvider : TaxProvider, IParameterOptions /// /// Gets the names for ItemCode and TaxCode field. /// - internal const string ItemCodeFieldName = "ItemCode"; - internal const string TaxCodeFieldName = "TaxCode"; - internal const string ExemptionNumberFieldName = "ExemptionNumber"; - internal const string EntityUseCodeFieldName = "EntityUseCode"; + internal const string ItemCodeFieldName = "AvalaraItemCode"; + internal const string TaxCodeFieldName = "AvalaraTaxCode"; + internal const string ExemptionNumberFieldName = "AvalaraExemptionNumber"; + internal const string EntityUseCodeFieldName = "AvalaraEntityUseCode"; public const string BeforeTaxCalculation = "Ecom7CartBeforeTaxCalculation"; public const string BeforeTaxCommit = "Ecom7CartBeforeTaxCommit"; public const string OnGetCustomerCode = "Ecom7CartAvalaraOnGetCustomerCode"; @@ -509,10 +509,10 @@ public static void VerifyCustomFields() var itemCodeColl = ProductField.FindProductFieldsBySystemName(ItemCodeFieldName); var taxCodeColl = ProductField.FindProductFieldsBySystemName(TaxCodeFieldName); - if (itemCodeColl.Count() == 0) + if (!itemCodeColl.Any()) { var productField = new ProductField(); - productField.Name = ItemCodeFieldName; + productField.Name = "Avalara Item Code"; productField.SystemName = ItemCodeFieldName; productField.TemplateName = ItemCodeFieldName; productField.TypeId = 1; @@ -520,10 +520,10 @@ public static void VerifyCustomFields() productField.Save(ItemCodeFieldName); } - if (taxCodeColl.Count() == 0) + if (!taxCodeColl.Any()) { var productField = new ProductField(); - productField.Name = TaxCodeFieldName; + productField.Name = "Avalara Tax Code"; productField.SystemName = TaxCodeFieldName; productField.TemplateName = TaxCodeFieldName; productField.TypeId = 15; @@ -533,40 +533,41 @@ public static void VerifyCustomFields() } string tableName = "AccessUser"; - var systemFields = SystemField.GetSystemFields(tableName); + var customFields = CustomField.GetCustomFields(tableName); - // ExemptionNumber - { - SystemField exemptionNumberField = new SystemField(ExemptionNumberFieldName, tableName, Types.Text, ExemptionNumberFieldName); - if (!systemFields.ContainsSystemField(exemptionNumberField)) - exemptionNumberField.Save(); - } + // ExemptionNumber + var exemptionNumberField = new CustomField(ExemptionNumberFieldName, tableName, Types.Text); + exemptionNumberField.Name = "Avalara Exemption Number"; + if (!customFields.ContainsCustomField(exemptionNumberField)) + exemptionNumberField.Save(); + + // EntityUseCode + var entityUseCodeField = new CustomField(EntityUseCodeFieldName, tableName, Types.DropDown); + entityUseCodeField.Name = "Avalara Entity Use Code"; - // EntityUseCode + if (!customFields.ContainsCustomField(entityUseCodeField)) { - SystemField entityUseCodeField = new SystemField(EntityUseCodeFieldName, tableName, Types.DropDown, EntityUseCodeFieldName); - if (!systemFields.ContainsSystemField(entityUseCodeField)) - { - var options = new CustomFieldOptions(); - options.DataType = Types.Text; - options.Add(new KeyValuePair("", "")); - options.Add(new KeyValuePair("Federal government", "A")); - options.Add(new KeyValuePair("State government", "B")); - options.Add(new KeyValuePair("Tribe / Status Indian / Indian Band", "C")); - options.Add(new KeyValuePair("Foreign diplomat", "D")); - options.Add(new KeyValuePair("Charitable or benevolent org", "E")); - options.Add(new KeyValuePair("Religious org", "F")); - options.Add(new KeyValuePair("Education org", "M")); - options.Add(new KeyValuePair("Resale", "G")); - options.Add(new KeyValuePair("Commercial agricultural production", "H")); - options.Add(new KeyValuePair("Industrial production / manufacturer", "I")); - options.Add(new KeyValuePair("Direct pay permit", "J")); - options.Add(new KeyValuePair("Direct mail", "K")); - options.Add(new KeyValuePair("Other (requires Exempt Reason Desc)", "L")); - options.Add(new KeyValuePair("Local government", "N")); - entityUseCodeField.Options = options; - entityUseCodeField.Save(); - } + var options = new CustomFieldOptions(); + options.DataType = Types.Text; + options.Add(new KeyValuePair("", "")); + options.Add(new KeyValuePair("Federal government", "A")); + options.Add(new KeyValuePair("State government", "B")); + options.Add(new KeyValuePair("Tribe / Status Indian / Indian Band", "C")); + options.Add(new KeyValuePair("Foreign diplomat", "D")); + options.Add(new KeyValuePair("Charitable or benevolent org", "E")); + options.Add(new KeyValuePair("Religious org", "F")); + options.Add(new KeyValuePair("Education org", "M")); + options.Add(new KeyValuePair("Resale", "G")); + options.Add(new KeyValuePair("Commercial agricultural production", "H")); + options.Add(new KeyValuePair("Industrial production / manufacturer", "I")); + options.Add(new KeyValuePair("Direct pay permit", "J")); + options.Add(new KeyValuePair("Direct mail", "K")); + options.Add(new KeyValuePair("Other (requires Exempt Reason Desc)", "L")); + options.Add(new KeyValuePair("Local government", "N")); + options.Add(new KeyValuePair("Non-Exempt taxable customer", "TAXABLE")); + + entityUseCodeField.Options = options; + entityUseCodeField.Save(); } } } diff --git a/src/Dynamicweb.Ecommerce.TaxProviders.AvalaraTaxProvider.csproj b/src/Dynamicweb.Ecommerce.TaxProviders.AvalaraTaxProvider.csproj index 0ef7b17..b5e036d 100644 --- a/src/Dynamicweb.Ecommerce.TaxProviders.AvalaraTaxProvider.csproj +++ b/src/Dynamicweb.Ecommerce.TaxProviders.AvalaraTaxProvider.csproj @@ -1,6 +1,6 @@  - 10.15.0 + 10.16.0 1.0.0.0 Avalara Avalara tax provider @@ -24,8 +24,8 @@ Avalara-logo.png - - + + diff --git a/src/Model/CreateTransactionRequest/CreateTransactionRequest.cs b/src/Model/CreateTransactionRequest/CreateTransactionRequest.cs index 8e4e4f8..509d291 100644 --- a/src/Model/CreateTransactionRequest/CreateTransactionRequest.cs +++ b/src/Model/CreateTransactionRequest/CreateTransactionRequest.cs @@ -1,5 +1,4 @@ -using Dynamicweb.Ecommerce.TaxProviders.AvalaraTaxProvider.Model.Enums; -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; @@ -23,15 +22,15 @@ internal sealed class CreateTransactionRequest [DataMember(Name = "customerCode", IsRequired = true)] public string CustomerCode { get; set; } - [DataMember(Name = "customerUsageType", EmitDefaultValue = false)] - public string CustomerUsageType { get; set; } - [DataMember(Name = "discount", EmitDefaultValue = false)] public double? Discount { get; set; } [DataMember(Name = "exemptionNo", EmitDefaultValue = false)] public string ExemptionNumber { get; set; } + [DataMember(Name = "entityUseCode", EmitDefaultValue = false)] + public string EntityUseCode { get; set; } + [DataMember(Name = "addresses", EmitDefaultValue = false)] public Addresses Addresses { get; set; } diff --git a/src/Service/PrepareTransactionHelper.cs b/src/Service/PrepareTransactionHelper.cs index 6036142..522c714 100644 --- a/src/Service/PrepareTransactionHelper.cs +++ b/src/Service/PrepareTransactionHelper.cs @@ -8,7 +8,7 @@ using Dynamicweb.Ecommerce.TaxProviders.AvalaraTaxProvider.Notifications; using Dynamicweb.Extensibility.Notifications; using Dynamicweb.Security.UserManagement; -using Dynamicweb.Security.UserManagement.Common.SystemFields; +using Dynamicweb.Security.UserManagement.Common.CustomFields; using System; namespace Dynamicweb.Ecommerce.TaxProviders.AvalaraTaxProvider.Service; @@ -156,12 +156,12 @@ private void SetCustomerExemptionData(CreateTransactionRequest request) if (UserManagementServices.Users.GetUserById(Order.CustomerAccessUserId) is not User customer) return; - foreach (SystemFieldValue fieldValue in customer.SystemFieldValues) + foreach (CustomFieldValue fieldValue in customer.CustomFieldValues) { - if (string.Equals(fieldValue.SystemField.Name, AvalaraTaxProvider.ExemptionNumberFieldName, StringComparison.OrdinalIgnoreCase) && fieldValue.Value is not null) + if (string.Equals(fieldValue.CustomField.SystemName, AvalaraTaxProvider.ExemptionNumberFieldName, StringComparison.OrdinalIgnoreCase) && fieldValue.Value is not null) request.ExemptionNumber = fieldValue.Value.ToString(); - else if (string.Equals(fieldValue.SystemField.Name, AvalaraTaxProvider.EntityUseCodeFieldName, StringComparison.OrdinalIgnoreCase) && fieldValue.Value is not null) - request.CustomerUsageType = fieldValue.Value.ToString(); + else if (string.Equals(fieldValue.CustomField.SystemName, AvalaraTaxProvider.EntityUseCodeFieldName, StringComparison.OrdinalIgnoreCase) && fieldValue.Value is not null) + request.EntityUseCode = fieldValue.Value.ToString(); } } From 07ed06805a1ae86d43f0ece990af8b572ca4f0f0 Mon Sep 17 00:00:00 2001 From: Stanislav Smetanin Date: Thu, 7 Aug 2025 17:27:07 +1000 Subject: [PATCH 2/2] Added improved logging of each request/response if DebugMode is enabled. Updated Dynamicweb/Ecommerce version. --- ...rce.TaxProviders.AvalaraTaxProvider.csproj | 6 +- src/Service/AvalaraException.cs | 26 +++ src/Service/AvalaraRequest.cs | 194 ++++++++++-------- src/Service/AvalaraRequestLogger.cs | 131 ++++++++++++ 4 files changed, 266 insertions(+), 91 deletions(-) create mode 100644 src/Service/AvalaraException.cs create mode 100644 src/Service/AvalaraRequestLogger.cs diff --git a/src/Dynamicweb.Ecommerce.TaxProviders.AvalaraTaxProvider.csproj b/src/Dynamicweb.Ecommerce.TaxProviders.AvalaraTaxProvider.csproj index b5e036d..c11b362 100644 --- a/src/Dynamicweb.Ecommerce.TaxProviders.AvalaraTaxProvider.csproj +++ b/src/Dynamicweb.Ecommerce.TaxProviders.AvalaraTaxProvider.csproj @@ -1,6 +1,6 @@  - 10.16.0 + 10.17.0 1.0.0.0 Avalara Avalara tax provider @@ -24,8 +24,8 @@ Avalara-logo.png - - + + diff --git a/src/Service/AvalaraException.cs b/src/Service/AvalaraException.cs new file mode 100644 index 0000000..30612c5 --- /dev/null +++ b/src/Service/AvalaraException.cs @@ -0,0 +1,26 @@ +using System; +using System.Net; + +namespace Dynamicweb.Ecommerce.TaxProviders.AvalaraTaxProvider.Service; + +/// +/// Custom exception for Avalara API errors +/// +internal sealed class AvalaraException : Exception +{ + public HttpStatusCode? StatusCode { get; } + public ApiCommand Command { get; } + + public AvalaraException(string message, ApiCommand command, HttpStatusCode? statusCode) + : base(message) + { + Command = command; + StatusCode = statusCode; + } + + public AvalaraException(string message, ApiCommand command, Exception innerException) + : base(message, innerException) + { + Command = command; + } +} diff --git a/src/Service/AvalaraRequest.cs b/src/Service/AvalaraRequest.cs index a737d62..69fd365 100644 --- a/src/Service/AvalaraRequest.cs +++ b/src/Service/AvalaraRequest.cs @@ -1,7 +1,7 @@ using Dynamicweb.Core; -using Dynamicweb.Logging; using System; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Net; using System.Net.Http; @@ -16,130 +16,148 @@ namespace Dynamicweb.Ecommerce.TaxProviders.AvalaraTaxProvider.Service; /// internal static class AvalaraRequest { + private const string JsonMediaType = "application/json"; + public static string SendRequest(string accountId, string licenseKey, string apiUrl, CommandConfiguration configuration) { - using var messageHandler = GetMessageHandler(); - using var client = new HttpClient(messageHandler); + using HttpMessageHandler messageHandler = CreateMessageHandler(); + using HttpClient client = CreateHttpClient(messageHandler, apiUrl, accountId, licenseKey); - client.BaseAddress = new Uri(apiUrl); - client.Timeout = new TimeSpan(0, 0, 0, 90); - client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var logger = new AvalaraRequestLogger(configuration.DebugLog); + logger.InitializeLog(apiUrl); - string authenticationParameter = Convert.ToBase64String(Encoding.Default.GetBytes($"{accountId}:{licenseKey}")); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authenticationParameter); + try + { + return ExecuteRequest(client, configuration, logger); + } + catch (HttpRequestException requestException) + { + logger.LogHttpRequestException(requestException); + throw; + } + catch (Exception ex) + { + logger.LogUnhandledException(ex); + throw; + } + finally + { + logger.FinalizeLog(configuration.CommandType); + } + } + + private static HttpMessageHandler CreateMessageHandler() => new HttpClientHandler + { + AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip + }; - string apiCommand = GetCommandLink( - apiUrl, - configuration.CommandType, - configuration.OperatorId, - configuration.OperatorSecondId, - configuration.QueryStringParameters - ); + private static string ExecuteRequest(HttpClient client, CommandConfiguration configuration, AvalaraRequestLogger logger) + { + string baseAddress = client.BaseAddress.ToString().TrimEnd('/'); + string apiCommand = GetCommandLink(baseAddress, configuration, true); + LogRequestInfo(baseAddress, configuration, logger); Task requestTask = configuration.CommandType switch { - //GET ApiCommand.ResolveAddress => client.GetAsync(apiCommand), - //POST ApiCommand.CreateTransaction or - ApiCommand.VoidTransaction => client.PostAsync(apiCommand, GetStringContent(configuration)), + ApiCommand.VoidTransaction => client.PostAsync(apiCommand, GetStringContent(configuration, logger)), _ => throw new NotImplementedException($"Unknown operation was used. The operation code: {configuration.CommandType}.") }; - try - { - using HttpResponseMessage response = requestTask.GetAwaiter().GetResult(); - - string responseText = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); - if (configuration.DebugLog) - { - var logText = new StringBuilder("Remote server response:"); - logText.AppendLine($"HttpStatusCode = {response.StatusCode}"); - logText.AppendLine($"HttpStatusDescription = {response.ReasonPhrase}"); - logText.AppendLine($"Response text: {responseText}"); + using HttpResponseMessage response = requestTask.GetAwaiter().GetResult(); + string responseText = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); - Log(logText.ToString(), false, configuration.CommandType); - } + logger.LogResponse(response, responseText); + ValidateResponse(response, configuration, logger); - if (!response.IsSuccessStatusCode) - { - string errorMessage = $"Unhandled exception. Operation failed: {response.ReasonPhrase}. Response text: ${responseText}"; - Log(errorMessage, false, configuration.CommandType); - - throw new Exception(errorMessage); - } + return responseText; + } - return responseText; - } - catch (HttpRequestException requestException) - { - string errorMessage = $"An error occurred during Avalara request. Error code: {requestException.StatusCode}"; - Log(errorMessage, false, configuration.CommandType); - throw new Exception(errorMessage); - } + private static void LogRequestInfo(string baseAddress, CommandConfiguration configuration, AvalaraRequestLogger logger) + { + if (!configuration.DebugLog) + return; - HttpMessageHandler GetMessageHandler() => new HttpClientHandler() - { - AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip - }; + string readableUrl = GetCommandLink(baseAddress, configuration, false); + logger.LogRequestInfo(readableUrl); } - private static HttpContent GetStringContent(CommandConfiguration configuration) + private static void ValidateResponse(HttpResponseMessage response, CommandConfiguration configuration, AvalaraRequestLogger logger) { - string content = Converter.SerializeCompact(configuration.Data); + if (response.IsSuccessStatusCode) + return; - if (configuration.DebugLog) - Log($"Request data: {content}", true, configuration.CommandType); + string errorMessage = $"Command {configuration.CommandType} failed: {response.ReasonPhrase}."; + logger.LogError(errorMessage); - return new StringContent(content, Encoding.UTF8, "application/json"); + throw new AvalaraException(errorMessage, configuration.CommandType, response.StatusCode); } - private static string GetCommandLink(string baseAddress, ApiCommand command, string operatorId, string operatorSecondId, Dictionary queryParameters) + private static HttpClient CreateHttpClient(HttpMessageHandler handler, string apiUrl, string accountId, string licenseKey) { - return command switch - { - ApiCommand.CreateTransaction => GetCommandLink("transactions/create"), - ApiCommand.ResolveAddress => GetCommandLink("addresses/resolve", queryParameters), - ApiCommand.VoidTransaction => GetCommandLink($"companies/{operatorId}/transactions/{operatorSecondId}/void"), - _ => throw new NotImplementedException($"The api command is not supported. Command: {command}") - }; + var client = new HttpClient(handler); - string GetCommandLink(string gateway, Dictionary queryParameters = null) - { - string link = $"{baseAddress}/{gateway}"; + client.BaseAddress = new Uri(apiUrl); + client.Timeout = TimeSpan.FromSeconds(90); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(JsonMediaType)); - if (queryParameters?.Count is 0 or null) - return link; + string authenticationParameter = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{accountId}:{licenseKey}")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authenticationParameter); - string parameters = string.Join("&", queryParameters.Select(parameter => $"{parameter.Key}={parameter.Value}")); + return client; + } - return $"{link}?{parameters}"; + private static HttpContent GetStringContent(CommandConfiguration configuration, AvalaraRequestLogger logger) + { + if (configuration.DebugLog) + { + string serializedData = Converter.Serialize(configuration.Data); + logger.LogRequestData(serializedData); } + + string content = Converter.SerializeCompact(configuration.Data); + return new StringContent(content, Encoding.UTF8, JsonMediaType); } - private static void Log(string message, bool isRequest, ApiCommand commandType) + private static string GetCommandLink(string baseAddress, CommandConfiguration configuration, bool escapeParameters) { - string type = isRequest ? "Request" : "Response"; - var errorMessage = new StringBuilder($"{type} for command: '{commandType}'."); - errorMessage.AppendLine(message); - - if (commandType is ApiCommand.ResolveAddress) - LogAddressValidator(message); - else - LogAvalara(message); + string gateway = configuration.CommandType switch + { + ApiCommand.CreateTransaction => "transactions/create", + ApiCommand.ResolveAddress => "addresses/resolve", + ApiCommand.VoidTransaction => $"companies/{configuration.OperatorId}/transactions/{configuration.OperatorSecondId}/void", + _ => throw new NotImplementedException($"The api command is not supported. Command: {configuration.CommandType}") + }; + + string link = $"{baseAddress}/{gateway}"; + + return AppendQueryParameters(link, configuration.QueryStringParameters, escapeParameters); } - private static void LogAvalara(string message) + private static string AppendQueryParameters(string link, Dictionary queryParameters, bool escapeParameters) { - string fullName = typeof(AvalaraTaxProvider).FullName; - LogManager.Current.GetLogger($"/eCom/TaxProvider/{fullName}").Info(message); - LogManager.System.GetLogger("Provider", fullName).Info(message); + if (queryParameters?.Any() is not true) + return link; + + var validParameters = queryParameters + .Where(parameter => !string.IsNullOrWhiteSpace(parameter.Value)) + .ToDictionary(parameter => parameter.Key, parameter => parameter.Value); + + if (!validParameters.Any()) + return link; + + IEnumerable parameterStrings = validParameters.Select(param => FormatQueryParameter(param, escapeParameters)); + string queryString = string.Join("&", parameterStrings); + + return $"{link}?{queryString}"; } - private static void LogAddressValidator(string message) + private static string FormatQueryParameter(KeyValuePair parameter, bool escapeParameters) { - string name = typeof(AvalaraAddressValidatorProvider).FullName ?? "AddressValidationProvider"; - LogManager.Current.GetLogger(string.Format("/eCom/AddressValidatorProvider/{0}", name)).Info(message); - LogManager.System.GetLogger(LogCategory.Provider, name).Info(message); + if (escapeParameters) + return $"{Uri.EscapeDataString(parameter.Key)}={Uri.EscapeDataString(parameter.Value)}"; + + return $"{parameter.Key}={parameter.Value}"; } -} +} \ No newline at end of file diff --git a/src/Service/AvalaraRequestLogger.cs b/src/Service/AvalaraRequestLogger.cs new file mode 100644 index 0000000..ca1f285 --- /dev/null +++ b/src/Service/AvalaraRequestLogger.cs @@ -0,0 +1,131 @@ +using Dynamicweb.Logging; +using System; +using System.Net.Http; +using System.Text; + +namespace Dynamicweb.Ecommerce.TaxProviders.AvalaraTaxProvider.Service; + +/// +/// Handles logging for Avalara requests and responses +/// +internal sealed class AvalaraRequestLogger +{ + private readonly StringBuilder _logBuilder; + private readonly bool _debugEnabled; + + public AvalaraRequestLogger(bool debugEnabled) + { + _debugEnabled = debugEnabled; + _logBuilder = debugEnabled ? new StringBuilder() : null; + } + + public void InitializeLog(string apiUrl) + { + if (!_debugEnabled) + return; + + _logBuilder.AppendLine("Avalara Interaction Log:"); + _logBuilder.AppendLine(); + _logBuilder.AppendLine("--- BASE SERVICE URL ---"); + _logBuilder.AppendLine(); + _logBuilder.AppendLine($"URL: {apiUrl}"); + } + + public void LogRequestInfo(string url) + { + if (!_debugEnabled) + return; + + _logBuilder.AppendLine(); + _logBuilder.AppendLine("--- REQUEST ---"); + _logBuilder.AppendLine(); + _logBuilder.AppendLine($"URL: {url}"); + } + + public void LogRequestData(string data) + { + if (!_debugEnabled) + return; + + _logBuilder.AppendLine(); + _logBuilder.AppendLine("--- REQUEST DATA ---"); + _logBuilder.AppendLine(); + _logBuilder.AppendLine(data); + } + + public void LogResponse(HttpResponseMessage response, string responseText) + { + if (!_debugEnabled) + return; + + _logBuilder.AppendLine(); + _logBuilder.AppendLine("--- RESPONSE ---"); + _logBuilder.AppendLine(); + _logBuilder.AppendLine($"HttpStatusCode: {response.StatusCode} ({response.ReasonPhrase})"); + _logBuilder.AppendLine(); + _logBuilder.AppendLine($"Response Text: {responseText}"); + } + + public void LogError(string errorMessage) + { + if (!_debugEnabled) + return; + + _logBuilder.AppendLine(); + _logBuilder.AppendLine("--- HTTP ERROR ---"); + _logBuilder.AppendLine(); + _logBuilder.AppendLine(errorMessage); + } + + public void LogHttpRequestException(HttpRequestException requestException) + { + if (!_debugEnabled) + return; + + _logBuilder.AppendLine(); + _logBuilder.AppendLine("--- EXCEPTION CAUGHT (HttpRequestException) ---"); + _logBuilder.AppendLine(); + _logBuilder.AppendLine($"Message: {requestException.Message}"); + + if (requestException.StatusCode.HasValue) + _logBuilder.AppendLine($"StatusCode: {requestException.StatusCode}"); + + _logBuilder.AppendLine($"Stack Trace: {requestException.StackTrace}"); + } + + public void LogUnhandledException(Exception exception) + { + if (!_debugEnabled) + return; + + _logBuilder.AppendLine(); + _logBuilder.AppendLine($"--- UNEXPECTED EXCEPTION CAUGHT ({exception.GetType().Name}) ---"); + _logBuilder.AppendLine(); + _logBuilder.AppendLine($"Message: {exception.Message}"); + _logBuilder.AppendLine($"Stack Trace: {exception.StackTrace}"); + } + + public void FinalizeLog(ApiCommand commandType) + { + if (!_debugEnabled) + return; + + _logBuilder.AppendLine(); + _logBuilder.AppendLine("--- END OF INTERACTION ---"); + + string message = _logBuilder.ToString(); + + if (commandType is not ApiCommand.ResolveAddress) + { + string fullName = typeof(AvalaraTaxProvider).FullName; + LogManager.Current.GetLogger($"/eCom/TaxProvider/{fullName}").Info(message); + LogManager.System.GetLogger("Provider", fullName).Info(message); + } + else + { + string fullName = typeof(AvalaraAddressValidatorProvider).FullName; + LogManager.Current.GetLogger(string.Format("/eCom/AddressValidatorProvider/{0}", fullName)).Info(message); + LogManager.System.GetLogger(LogCategory.Provider, fullName).Info(message); + } + } +}