Skip to content

Add error handling #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: v13/dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<string, List<string>> Errors { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -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; }
}
}
Original file line number Diff line number Diff line change
@@ -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})";
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -62,8 +64,6 @@ public async Task<NetsRefund> RefundPaymentAsync(string chargeId, object data, C

private async Task<TResult> RequestAsync<TResult>(string url, Func<IFlurlRequest, CancellationToken, Task<TResult>> func, CancellationToken cancellationToken = default)
{
var result = default(TResult);

try
{
var req = new FlurlRequest(_config.BaseUrl + url)
Expand All @@ -78,14 +78,61 @@ private async Task<TResult> RequestAsync<TResult>(string url, Func<IFlurlRequest
})
.WithHeader("Authorization", _config.Authorization);

result = await func.Invoke(req, cancellationToken).ConfigureAwait(false);
return await func.Invoke(req, cancellationToken).ConfigureAwait(false);
}
catch (FlurlHttpException ex)
{
throw;
}
HttpStatusCode statusCode = ex.StatusCode.HasValue ? (HttpStatusCode)ex.StatusCode.Value : HttpStatusCode.BadRequest;

if (statusCode == HttpStatusCode.Unauthorized)
{
throw new NetsApiException(HttpStatusCode.Unauthorized, new NetsErrorResponse
{
Errors = new Dictionary<string, List<string>> { { "Authorization", new List<string> { "Unauthorized request - Missing or invalid secret." } } }
});
}

if (statusCode == HttpStatusCode.InternalServerError)
{
NetsServerErrorResponse netsServerErrorResponse = null;
try
{
netsServerErrorResponse = await ex.GetResponseJsonAsync<NetsServerErrorResponse>().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<NetsErrorResponse>().ConfigureAwait(false);
}
catch
{
netsErrorResponse = new NetsErrorResponse
{
Errors = new Dictionary<string, List<string>> { { "UnknownError", new List<string> { "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}");
}
}
}
}