ajustes sobre verifactu y el banco

This commit is contained in:
Алекс 2026-05-24 23:57:22 +02:00
parent 62bc070115
commit e0ab16f82d
9 changed files with 210 additions and 10 deletions

View File

@ -0,0 +1,90 @@
using System.Text.Json;
using DoliMiddlewareApi.Dtos.VeriFactu;
using DoliMiddlewareApi.Services.VeriFactu;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace DoliMiddlewareApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class VeriFactuController(VeriFactuApiClient verifactuApiClient) : ControllerBase
{
[HttpGet("health")]
[AllowAnonymous]
public async Task<IActionResult> Health()
{
try
{
var result = await verifactuApiClient.GetHealthAsync();
return Content(result.Content ?? "{\"status\":\"ok\"}", result.ContentType ?? "application/json");
}
catch (HttpRequestException)
{
return Ok(new
{
status = "down",
error = $"Connection refused ({verifactuApiClient.BaseUrl})",
upstream = verifactuApiClient.BaseUrl
});
}
}
[HttpGet("public-key")]
[AllowAnonymous]
public async Task<IActionResult> PublicKey()
{
return await Proxy(verifactuApiClient.GetPublicKeyAsync());
}
[HttpGet("formats")]
[AllowAnonymous]
public async Task<IActionResult> Formats()
{
return await Proxy(verifactuApiClient.GetFormatsAsync());
}
[HttpPost("certificates/register")]
public async Task<IActionResult> RegisterCertificate([FromBody] RegisterCertificateDto dto)
{
var logger = HttpContext.RequestServices.GetRequiredService<ILogger<VeriFactuController>>();
logger.LogInformation("[VeriFactuController] RegisterCertificate called. CertName={CertName}, CertFile length={CertFileLen}, Password length={PasswordLen}",
dto.CertName, dto.CertFile?.Length ?? 0, dto.Password?.Length ?? 0);
return await Proxy(verifactuApiClient.RegisterCertificateAsync(dto));
}
[HttpPost("facturas")]
public async Task<IActionResult> SendInvoice([FromBody] JsonElement payload)
{
return await Proxy(verifactuApiClient.SendInvoiceAsync(payload));
}
[HttpPost("facturas/anular")]
public async Task<IActionResult> CancelInvoice([FromBody] JsonElement payload)
{
return await Proxy(verifactuApiClient.CancelInvoiceAsync(payload));
}
private async Task<IActionResult> Proxy(Task<ProxyResponse> task)
{
try
{
var result = await task;
return new ContentResult
{
StatusCode = result.StatusCode,
Content = result.Content,
ContentType = result.ContentType
};
}
catch (HttpRequestException)
{
return StatusCode(StatusCodes.Status503ServiceUnavailable, new
{
title = "VeriFactu unavailable",
status = 503,
detail = $"No se puede conectar con VeriFactu en {verifactuApiClient.BaseUrl}"
});
}
}
}

View File

@ -0,0 +1,3 @@
namespace DoliMiddlewareApi.Dtos.VeriFactu;
public record ProxyResponse(int StatusCode, string? Content, string? ContentType);

View File

@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace DoliMiddlewareApi.Dtos.VeriFactu;
public class RegisterCertificateDto
{
[Required]
[StringLength(100)]
[JsonPropertyName("cert_name")]
public string CertName { get; set; } = string.Empty;
[Required]
[JsonPropertyName("cert_file")]
public string CertFile { get; set; } = string.Empty;
[Required]
[StringLength(1024)]
[JsonPropertyName("password")]
public string Password { get; set; } = string.Empty;
}

View File

@ -7,6 +7,7 @@ public class InvoiceDetailDto
public DateTime? Date { get; set; } public DateTime? Date { get; set; }
public DateTime? ExpireDate { get; set; } public DateTime? ExpireDate { get; set; }
public int ClientId { get; set; } public int ClientId { get; set; }
public string? ClientName { get; set; }
public decimal? TotalHt { get; set; } public decimal? TotalHt { get; set; }
public decimal? TotalTax { get; set; } public decimal? TotalTax { get; set; }
public decimal? Total { get; set; } public decimal? Total { get; set; }

View File

@ -5,6 +5,7 @@ using DoliMiddlewareApi.Services;
using DoliMiddlewareApi.Services.Auth; using DoliMiddlewareApi.Services.Auth;
using DoliMiddlewareApi.Services.Clients; using DoliMiddlewareApi.Services.Clients;
using DoliMiddlewareApi.Services.Notifications; using DoliMiddlewareApi.Services.Notifications;
using DoliMiddlewareApi.Services.VeriFactu;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Diagnostics;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
@ -136,12 +137,21 @@ builder.Services.AddHttpClient("Dolibarr", client =>
client.BaseAddress = new Uri(baseUrl); client.BaseAddress = new Uri(baseUrl);
}); });
builder.Services.AddHttpClient("VeriFactu", client =>
{
var baseUrl = builder.Configuration["VeriFactu:ApiUrl"] ?? "http://localhost:6789/";
if (!baseUrl.EndsWith('/')) baseUrl += '/';
client.BaseAddress = new Uri(baseUrl);
client.Timeout = TimeSpan.FromSeconds(30);
});
builder.Services.AddScoped<IDolibarrApiClient>(sp => builder.Services.AddScoped<IDolibarrApiClient>(sp =>
{ {
var factory = sp.GetRequiredService<IHttpClientFactory>(); var factory = sp.GetRequiredService<IHttpClientFactory>();
var client = factory.CreateClient("Dolibarr"); var client = factory.CreateClient("Dolibarr");
var tokenCacheService = sp.GetRequiredService<DolibarrTokenCacheService>(); var tokenCacheService = sp.GetRequiredService<DolibarrTokenCacheService>();
return new DolibarrApiClient(client, tokenCacheService); var config = sp.GetRequiredService<IConfiguration>();
return new DolibarrApiClient(client, tokenCacheService, config);
}); });
// ========================================= // =========================================
@ -163,6 +173,13 @@ builder.Services.AddScoped<DocumentService>();
// Servicio de negocio (setup/diccionarios) // Servicio de negocio (setup/diccionarios)
builder.Services.AddScoped<SetupService>(); builder.Services.AddScoped<SetupService>();
// Cliente de VeriFactu MidAPI
builder.Services.AddScoped<VeriFactuApiClient>(sp =>
{
var factory = sp.GetRequiredService<IHttpClientFactory>();
return new VeriFactuApiClient(factory.CreateClient("VeriFactu"));
});
// Servicio de negocio (banco) // Servicio de negocio (banco)
builder.Services.AddScoped<BankService>(); builder.Services.AddScoped<BankService>();

View File

@ -1,10 +1,11 @@
using System.Net; using System.Net;
using DoliMiddlewareApi.Exceptions; using DoliMiddlewareApi.Exceptions;
using DoliMiddlewareApi.Services.Auth; using DoliMiddlewareApi.Services.Auth;
using Microsoft.Extensions.Configuration;
namespace DoliMiddlewareApi.Services.Clients; namespace DoliMiddlewareApi.Services.Clients;
public class DolibarrApiClient(HttpClient httpClient, DolibarrTokenCacheService tokenCacheService) public class DolibarrApiClient(HttpClient httpClient, DolibarrTokenCacheService tokenCacheService, IConfiguration configuration)
: IDolibarrApiClient : IDolibarrApiClient
{ {
public async Task<T> GetResourceAsync<T>(string endpoint) where T : class public async Task<T> GetResourceAsync<T>(string endpoint) where T : class
@ -76,11 +77,10 @@ public class DolibarrApiClient(HttpClient httpClient, DolibarrTokenCacheService
private void AddDolibarrTokenHeader(HttpRequestMessage request) private void AddDolibarrTokenHeader(HttpRequestMessage request)
{ {
var dolibarrToken = tokenCacheService.GetDolibarrToken(); var sharedKey = configuration["Dolibarr:ApiKey"];
if (!string.IsNullOrEmpty(dolibarrToken)) var token = !string.IsNullOrEmpty(sharedKey) ? sharedKey : tokenCacheService.GetDolibarrToken();
{ if (!string.IsNullOrEmpty(token))
request.Headers.Add("DOLAPIKEY", dolibarrToken); request.Headers.Add("DOLAPIKEY", token);
}
} }

View File

@ -16,7 +16,19 @@ public class InvoiceService(IDolibarrApiClient apiClient, INotificationService n
public async Task<InvoiceDetailDto> GetInvoiceAsync(int id) public async Task<InvoiceDetailDto> GetInvoiceAsync(int id)
{ {
var data = await apiClient.GetResourceAsync<InvoiceDetailResponse>($"invoices/{id}"); var data = await apiClient.GetResourceAsync<InvoiceDetailResponse>($"invoices/{id}");
return InvoiceMapper.MapToInvoiceDetailDto(data); var dto = InvoiceMapper.MapToInvoiceDetailDto(data);
if (dto.ClientId > 0)
{
try
{
var client = await apiClient.GetResourceAsync<ClientResponse>($"thirdparties/{dto.ClientId}");
dto.ClientName = client?.name;
}
catch { /* best-effort */ }
}
return dto;
} }
public async Task<List<InvoiceDto>> GetInvoicesAsync( public async Task<List<InvoiceDto>> GetInvoicesAsync(

View File

@ -0,0 +1,52 @@
using System.Net.Http.Headers;
using System.Text.Json;
using DoliMiddlewareApi.Dtos.VeriFactu;
namespace DoliMiddlewareApi.Services.VeriFactu;
public class VeriFactuApiClient(HttpClient httpClient)
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
public string BaseUrl => httpClient.BaseAddress?.ToString() ?? "http://host.docker.internal:6789/";
public Task<ProxyResponse> GetHealthAsync() => SendAsync(HttpMethod.Get, "api/v1/health");
public Task<ProxyResponse> GetPublicKeyAsync() => SendAsync(HttpMethod.Get, "api/v1/auth/public-key");
public Task<ProxyResponse> GetFormatsAsync() => SendAsync(HttpMethod.Get, "api/v1/formats");
public async Task<ProxyResponse> RegisterCertificateAsync(RegisterCertificateDto dto)
{
var payload = new
{
cert_name = dto.CertName,
cert_file = dto.CertFile,
password_encrypted = dto.Password
};
return await SendAsync(HttpMethod.Post, "api/v1/auth/register", payload);
}
public Task<ProxyResponse> SendInvoiceAsync(JsonElement payload)
=> SendAsync(HttpMethod.Post, "api/v1/facturas", payload);
public Task<ProxyResponse> CancelInvoiceAsync(JsonElement payload)
=> SendAsync(HttpMethod.Post, "api/v1/facturas/anular", payload);
private async Task<ProxyResponse> SendAsync(HttpMethod method, string path, object? payload = null)
{
using var request = new HttpRequestMessage(method, path);
if (payload is not null)
{
request.Content = JsonContent.Create(payload, options: JsonOptions);
}
using var response = await httpClient.SendAsync(request);
var content = response.Content is null ? string.Empty : await response.Content.ReadAsStringAsync();
var contentType = response.Content?.Headers.ContentType?.ToString() ?? "application/json";
return new ProxyResponse((int)response.StatusCode, content, contentType);
}
}

View File

@ -3,13 +3,14 @@
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",
"Microsoft.AspNetCore": "Warning", "Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware": "None" "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware": "None",
"System.Net.Http.HttpClient.VeriFactu": "None"
} }
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"Dolibarr": { "Dolibarr": {
"ApiUrl": "http://localhost/api/index.php", "ApiUrl": "http://localhost/api/index.php",
"ApiKey": "" "ApiKey": "847ad838254caa863c2da3dc949a5028e2317be9"
}, },
"Jwt": { "Jwt": {
"Secret": "", "Secret": "",
@ -18,5 +19,8 @@
}, },
"Notifications": { "Notifications": {
"WebhookUrl": "" "WebhookUrl": ""
},
"VeriFactu": {
"ApiUrl": "http://localhost:6789/"
} }
} }