Skip to content

Commit

Permalink
Added handling of srv4pos error responses. (#261)
Browse files Browse the repository at this point in the history
* Added handling of srv4pos error responses.

* tests fixed.
  • Loading branch information
abdallahbeshi authored Jan 24, 2025
1 parent 989c89f commit cf81e24
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 20 deletions.
8 changes: 6 additions & 2 deletions src/Basque/Mews.Fiscalizations.Basque.Tests/TestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public TestFixture(Region region)
}

private Region Region { get; }

private TaxpayerIdentificationNumber LocalNif { get; }

internal TicketBaiClient Client => new TicketBaiClient(Certificate, Region, Environment.Test);
Expand All @@ -48,7 +48,11 @@ internal static void AssertResponse(Region region, SendInvoiceResponse response,
// Araba region validates that each invoice is chained, but that's something we can't do in tests, so we will be ignoring that error.
// Also the NIF must be registered in the Araba region.
var applicableValidationResults = region.Match(
Region.Araba, _ => validationResults.Where(r => !r.ErrorCode.Equals(ErrorCode.InvalidOrMissingInvoiceChain) && !r.ErrorCode.Equals(ErrorCode.IssuerNifMustBeRegisteredInArabaRegion)),
Region.Araba, _ => validationResults.Where(r =>
r.ErrorCode != ErrorCode.InvalidOrMissingInvoiceChain
&& r.ErrorCode != ErrorCode.IssuerNifMustBeRegisteredInArabaRegion
&& r.ErrorCode != ErrorCode.ArabaRegionTestCertificate
),
_ => validationResults
);
Assert.That(applicableValidationResults, Is.Empty);
Expand Down
3 changes: 3 additions & 0 deletions src/Basque/Mews.Fiscalizations.Basque/Model/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public enum ErrorCode
// The size of the message must not exceed the allowed size.
MessageLengthLimitExceeded = 017,

// Returned when using a test certificate in Araba region.
ArabaRegionTestCertificate = 998,

// Invalid ID field value. NIF-VAT must be correct for the indicated country. Brexit control.
InvalidCountryTaxIdentifier = 1104,

Expand Down
2 changes: 1 addition & 1 deletion src/Mews.Fiscalizations.All/Mews.Fiscalizations.All.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<RepositoryUrl>https://github.com/MewsSystems/fiscalizations</RepositoryUrl>
<Icon>https://raw.githubusercontent.com/msigut/eet/master/receipt.png</Icon>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageVersion>29.0.0</PackageVersion>
<PackageVersion>30.0.0</PackageVersion>
<LangVersion>12</LangVersion>
<ImplicitUsings>true</ImplicitUsings>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;

namespace Mews.Fiscalizations.Sweden.DTOs;

public sealed class Srv4posBasicErrorResponse
{
[JsonPropertyName("error")]
public string Error { get; set; }
}
37 changes: 37 additions & 0 deletions src/Sweden/Mews.Fiscalizations.Sweden/DTOs/Srv4posError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Text.Json.Serialization;

namespace Mews.Fiscalizations.Sweden.DTOs;

public sealed class Srv4posError
{
[JsonPropertyName("error")]
public string Error { get; set; }

[JsonPropertyName("details")]
public List<ErrorDetail> Details { get; set; }
}

public sealed class ErrorDetail
{
[JsonPropertyName("field")]
public string Field { get; set; }

[JsonPropertyName("message")]
public string Message { get; set; }

[JsonPropertyName("code")]
public string Code { get; set; }

[JsonPropertyName("params")]
public ErrorParams Params { get; set; }
}

public sealed class ErrorParams
{
[JsonPropertyName("actualValue")]
public string ActualValue { get; set; }

[JsonPropertyName("pattern")]
public string Pattern { get; set; }
}

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<RepositoryUrl>https://github.com/MewsSystems/fiscalizations</RepositoryUrl>
<Icon>https://raw.githubusercontent.com/msigut/eet/master/receipt.png</Icon>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageVersion>1.0.0</PackageVersion>
<PackageVersion>2.0.0</PackageVersion>
<LangVersion>12</LangVersion>
<ImplicitUsings>true</ImplicitUsings>
</PropertyGroup>
Expand Down
12 changes: 12 additions & 0 deletions src/Sweden/Mews.Fiscalizations.Sweden/Models/ErrorResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using FuncSharp;

namespace Mews.Fiscalizations.Sweden.Models;

public sealed class ErrorResponse(NonEmptyString error, Srv4posErrorType errorType, string message = null)
{
public NonEmptyString Error { get; } = error;

public Srv4posErrorType ErrorType { get; } = errorType;

public Option<NonEmptyString> Message { get; } = message.AsNonEmpty();
}
12 changes: 12 additions & 0 deletions src/Sweden/Mews.Fiscalizations.Sweden/Models/Srv4posErrorType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Mews.Fiscalizations.Sweden.Models;

public enum Srv4posErrorType
{
ServerSideError = 0,
InvalidRequest = 1,
InvalidCorporateId = 2,
InvalidCashRegisterName = 3,
InvalidFieldFormat = 4,
NonUniqueCashRegisterNameOrCorporateId = 5,
UnknownError = 6
}
58 changes: 42 additions & 16 deletions src/Sweden/Mews.Fiscalizations.Sweden/Srv4posClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using FuncSharp;
using Mews.Fiscalizations.Core.Model;
using Mews.Fiscalizations.Sweden.Models;
Expand All @@ -26,7 +27,7 @@ private static void SetAuthorizationHeader(string token)
/// <summary>
/// A request to create an activation that is immediately ready for use, you only need to deliver the API key to a new cash register.
/// </summary>
public async Task<Try<CreateActivationResponse, string>> CreateActivation(string username, string password, CreateActivationRequest request, CancellationToken cancellationToken = default)
public async Task<Try<CreateActivationResponse, ErrorResponse>> CreateActivation(string username, string password, CreateActivationRequest request, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
SetAuthorizationHeader($"{username}:{password}");
Expand All @@ -38,7 +39,7 @@ public async Task<Try<CreateActivationResponse, string>> CreateActivation(string
/// <summary>
/// Request for recording fiscal data to the control unit.
/// </summary>
public async Task<Try<SendDataResponse, string>> SendDataToControlUnitAsync(string apiKey, string corporateId, string cashRegisterName, SendDataRequest request, CancellationToken cancellationToken = default)
public async Task<Try<SendDataResponse, ErrorResponse>> SendDataToControlUnitAsync(string apiKey, string corporateId, string cashRegisterName, SendDataRequest request, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
SetAuthorizationHeader(apiKey);
Expand All @@ -54,7 +55,7 @@ public async Task<Try<SendDataResponse, string>> SendDataToControlUnitAsync(stri
/// <summary>
/// Checks if a cash register name is unique within the country and corporate.
/// </summary>
public async Task<Try<bool, string>> CheckCashRegisterUniquenessAsync(Country country, string corporateId, string cashRegisterName, CancellationToken cancellationToken = default)
public async Task<Try<bool, ErrorResponse>> CheckCashRegisterUniquenessAsync(Country country, string corporateId, string cashRegisterName, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();

Expand All @@ -68,20 +69,45 @@ public async Task<Try<bool, string>> CheckCashRegisterUniquenessAsync(Country co
return await HandleResponse<DTOs.CheckCashRegisterUniquenessResponse, bool>(response, r => r.Exists, cancellationToken);
}

// TODO: Not a great way to handle responses and errors, we will improve it later.
private static async Task<Try<TResult, string>> HandleResponse<TDto, TResult>(HttpResponseMessage response, Func<TDto, TResult> map, CancellationToken cancellationToken)
private static async Task<Try<TResult, ErrorResponse>> HandleResponse<TDto, TResult>(HttpResponseMessage response, Func<TDto, TResult> map, CancellationToken cancellationToken)
{
var result = await response.IsSuccessStatusCode.MatchAsync(
async t =>
if (response.IsSuccessStatusCode)
{
return Try.Success<TResult, ErrorResponse>(map(await response.Content.ReadFromJsonAsync<TDto>(cancellationToken)));
}

var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);

return (int)response.StatusCode switch
{
>= 500 => Try.Error<TResult, ErrorResponse>(new ErrorResponse(NonEmptyString.CreateUnsafe("Server side error, try again."), Srv4posErrorType.ServerSideError, responseContent)),
>= 400 => HandleErrorResponse<TResult>(responseContent),
_ => Try.Error<TResult, ErrorResponse>(new ErrorResponse(NonEmptyString.CreateUnsafe("Unknown error"), Srv4posErrorType.UnknownError, responseContent))
};
}

private static Try<TResult, ErrorResponse> HandleErrorResponse<TResult>(string responseContent)
{
var errorResponse = JsonSerializer.Deserialize<DTOs.Srv4posBasicErrorResponse>(responseContent);
if (errorResponse.Error == "NonUniqueJsonException")
{
return Try.Error<TResult, ErrorResponse>(new ErrorResponse(NonEmptyString.CreateUnsafe("Non-unique cash register name or corporate id."), Srv4posErrorType.NonUniqueCashRegisterNameOrCorporateId, responseContent));
}
if (errorResponse.Error == "ValueNotValidJsonException")
{
var srv4PosError = JsonSerializer.Deserialize<DTOs.Srv4posError>(responseContent);
if (srv4PosError.Details?.Count > 0)
{
var dataResponse = await Try.CatchAsync<TDto, Exception>(
async _ => await response.Content.ReadFromJsonAsync<TDto>(cancellationToken)
);
return dataResponse.MapError(e => e.Message);
},
async f => Try.Error<TDto, string>(await response.Content.ReadAsStringAsync(cancellationToken))
);

return result.Map(map);
var errorType = srv4PosError.Details[0].Field switch
{
"corporateId" => Srv4posErrorType.InvalidCorporateId,
"cashRegisterName" => Srv4posErrorType.InvalidCashRegisterName,
_ => Srv4posErrorType.InvalidFieldFormat
};
return Try.Error<TResult, ErrorResponse>(new ErrorResponse(NonEmptyString.CreateUnsafe("Invalid field format"), errorType, responseContent));
}
return Try.Error<TResult, ErrorResponse>(new ErrorResponse(NonEmptyString.CreateUnsafe("Invalid request."), Srv4posErrorType.InvalidRequest, responseContent));
}
return Try.Error<TResult, ErrorResponse>(new ErrorResponse(NonEmptyString.CreateUnsafe("Unknown error"), Srv4posErrorType.UnknownError, responseContent));
}
}

0 comments on commit cf81e24

Please sign in to comment.