diff --git a/src/Umbraco.Commerce.PaymentProviders.Nets/Easy/Api/Models/NetsErrorResponse.cs b/src/Umbraco.Commerce.PaymentProviders.Nets/Easy/Api/Models/NetsErrorResponse.cs new file mode 100644 index 0000000..cd4f1dd --- /dev/null +++ b/src/Umbraco.Commerce.PaymentProviders.Nets/Easy/Api/Models/NetsErrorResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Umbraco.Commerce.PaymentProviders.Api.Models +{ + public class NetsErrorResponse + { + [JsonPropertyName("errors")] + public Dictionary> Errors { get; set; } + } +} diff --git a/src/Umbraco.Commerce.PaymentProviders.Nets/Easy/Api/Models/NetsServerErrorResponse.cs b/src/Umbraco.Commerce.PaymentProviders.Nets/Easy/Api/Models/NetsServerErrorResponse.cs new file mode 100644 index 0000000..7b171dd --- /dev/null +++ b/src/Umbraco.Commerce.PaymentProviders.Nets/Easy/Api/Models/NetsServerErrorResponse.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Umbraco.Commerce.PaymentProviders.Api.Models +{ + public class NetsServerErrorResponse + { + [JsonPropertyName("message")] + public string Message { get; set; } + + [JsonPropertyName("code")] + public string Code { get; set; } + + [JsonPropertyName("source")] + public string Source { get; set; } + } +} diff --git a/src/Umbraco.Commerce.PaymentProviders.Nets/Easy/Api/NetsApiException.cs b/src/Umbraco.Commerce.PaymentProviders.Nets/Easy/Api/NetsApiException.cs new file mode 100644 index 0000000..ebbd8bb --- /dev/null +++ b/src/Umbraco.Commerce.PaymentProviders.Nets/Easy/Api/NetsApiException.cs @@ -0,0 +1,58 @@ +using System; +using System.Linq; +using System.Net; +using Umbraco.Commerce.PaymentProviders.Api.Models; + +namespace Umbraco.Commerce.PaymentProviders.Api +{ + public class NetsApiException : Exception + { + public HttpStatusCode StatusCode { get; } + public NetsErrorResponse ValidationErrors { get; } + public NetsServerErrorResponse ServerError { get; } + + public NetsApiException() { } + + public NetsApiException(string message) : base(message) { } + + public NetsApiException(string message, Exception innerException) : base(message, innerException) { } + + public NetsApiException(HttpStatusCode statusCode, NetsErrorResponse validationErrors) + : base(FormatValidationErrorMessage(validationErrors)) + { + StatusCode = statusCode; + ValidationErrors = validationErrors; + } + + public NetsApiException(HttpStatusCode statusCode, NetsServerErrorResponse serverError) + : base(FormatServerErrorMessage(serverError)) + { + StatusCode = statusCode; + ServerError = serverError; + } + + private static string FormatValidationErrorMessage(NetsErrorResponse errorResponse) + { + if (errorResponse?.Errors == null || errorResponse.Errors.Count == 0) + { + return "Validation failed, but no error details were provided."; + } + + var errors = string.Join(" ", errorResponse.Errors + .Where(e => e.Value is { Count: >0 }) + .Select(e => $"{e.Key}: {string.Join(", ", e.Value)}")); + + return $"Validation Errors: {errors}"; + } + + private static string FormatServerErrorMessage(NetsServerErrorResponse serverError) + { + if (serverError == null) + { + return "An unknown internal server error occurred."; + } + + return $"Internal Server Error: {serverError.Message} (Code: {serverError.Code}, Source: {serverError.Source})"; + } + } +} diff --git a/src/Umbraco.Commerce.PaymentProviders.Nets/Easy/Api/NetsEasyClient.cs b/src/Umbraco.Commerce.PaymentProviders.Nets/Easy/Api/NetsEasyClient.cs index 877ffba..681f87c 100644 --- a/src/Umbraco.Commerce.PaymentProviders.Nets/Easy/Api/NetsEasyClient.cs +++ b/src/Umbraco.Commerce.PaymentProviders.Nets/Easy/Api/NetsEasyClient.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Net; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; @@ -62,8 +64,6 @@ public async Task RefundPaymentAsync(string chargeId, object data, C private async Task RequestAsync(string url, Func> func, CancellationToken cancellationToken = default) { - var result = default(TResult); - try { var req = new FlurlRequest(_config.BaseUrl + url) @@ -78,14 +78,61 @@ private async Task RequestAsync(string url, Func> { { "Authorization", new List { "Unauthorized request - Missing or invalid secret." } } } + }); + } + + if (statusCode == HttpStatusCode.InternalServerError) + { + NetsServerErrorResponse netsServerErrorResponse = null; + try + { + netsServerErrorResponse = await ex.GetResponseJsonAsync().ConfigureAwait(false); + } + catch + { + netsServerErrorResponse = new NetsServerErrorResponse + { + Message = "An unexpected internal server error occurred.", + Code = "Unknown", + Source = "Unknown" + }; + } - return result; + throw new NetsApiException(statusCode, netsServerErrorResponse); + } + + if (statusCode == HttpStatusCode.BadRequest) + { + NetsErrorResponse netsErrorResponse = null; + try + { + netsErrorResponse = await ex.GetResponseJsonAsync().ConfigureAwait(false); + } + catch + { + netsErrorResponse = new NetsErrorResponse + { + Errors = new Dictionary> { { "UnknownError", new List { "A validation error occurred, but details could not be retrieved." } } } + }; + } + + throw new NetsApiException(statusCode, netsErrorResponse); + } + + string rawResponse = await ex.GetResponseStringAsync().ConfigureAwait(false); + throw new NetsApiException($"Unexpected error from Nets API (Status {statusCode}): {rawResponse}"); + } } } }