diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 614229d..2d63f8a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,6 +17,10 @@ jobs: SUPER_BUSINESS_GUID: ${{ secrets.SUPER_BUSINESS_GUID }} SUPER_USERNAME: ${{ secrets.SUPER_USERNAME }} SUPER_PASSWORD: ${{ secrets.SUPER_PASSWORD }} + EPOSLOVANJE_USERNAME: ${{ secrets.EPOSLOVANJE_USERNAME }} + EPOSLOVANJE_PASSWORD: ${{ secrets.EPOSLOVANJE_PASSWORD }} + EPOSLOVANJE_API_KEY: ${{ secrets.EPOSLOVANJE_API_KEY }} + DOKU_API_KEY: ${{ secrets.DOKU_API_KEY }} steps: - name: Checkout repository @@ -31,7 +35,7 @@ jobs: run: dotnet restore MAES.Fiskal2.Tests/MAES.Fiskal2.Tests.csproj - name: Run tests - run: dotnet test MAES.Fiskal2.Tests/MAES.Fiskal2.Tests.csproj --configuration Release --no-restore + run: dotnet test MAES.Fiskal2.Tests/MAES.Fiskal2.Tests.csproj --configuration Release --no-restore -v normal --logger "console;verbosity=detailed" pack-and-publish: name: Pack and publish package diff --git a/MAES.Fiskal2.Tests/Fiskal2Tests.cs b/MAES.Fiskal2.Tests/Fiskal2Tests.cs deleted file mode 100644 index bd568cb..0000000 --- a/MAES.Fiskal2.Tests/Fiskal2Tests.cs +++ /dev/null @@ -1,73 +0,0 @@ -using MAES.Fiskal2.Posrednici; - -namespace MAES.Fiskal2.Tests; - -public class SuperTests -{ - readonly List posrednici = - [ - new Super - { - BusinessGuid = Environment.GetEnvironmentVariable("SUPER_BUSINESS_GUID") ?? throw new InvalidOperationException("SUPER_BUSINESS_GUID environment variable is not set."), - Username = Environment.GetEnvironmentVariable("SUPER_USERNAME") ?? throw new InvalidOperationException("SUPER_USERNAME environment variable is not set."), - Password = Environment.GetEnvironmentVariable("SUPER_PASSWORD") ?? throw new InvalidOperationException("SUPER_PASSWORD environment variable is not set."), - IsDev = true - }, - // new EPoslovanje - // { - // OIB = Environment.GetEnvironmentVariable("EPOSLOVANJE_OIB") ?? throw new InvalidOperationException("EPOSLOVANJE_OIB environment variable is not set."), - // Username = Environment.GetEnvironmentVariable("EPOSLOVANJE_USERNAME") ?? throw new InvalidOperationException("EPOSLOVANJE_USERNAME environment variable is not set."), - // Password = Environment.GetEnvironmentVariable("EPOSLOVANJE_PASSWORD") ?? throw new InvalidOperationException("EPOSLOVANJE_PASSWORD environment variable is not set."), - // IsDev = true - // }, - // new Fina - // { - // OIB = Environment.GetEnvironmentVariable("FINA_OIB") ?? throw new InvalidOperationException("FINA_OIB environment variable is not set."), - // Certificate = ..., - // IsDev = true - // }, - // new MER - // { - // OIB = Environment.GetEnvironmentVariable("MER_OIB") ?? throw new InvalidOperationException("MER_OIB environment variable is not set."), - // Username = Environment.GetEnvironmentVariable("MER_USERNAME") ?? throw new InvalidOperationException("MER_USERNAME environment variable is not set."), - // Password = Environment.GetEnvironmentVariable("MER_PASSWORD") ?? throw new InvalidOperationException("MER_PASSWORD environment variable is not set."), - // IsDev = true - // } - ]; - - [Fact] - public async Task SendInvoiceUBL() - { - foreach (var posrednik in posrednici.Where(p => p is not Fina)) - { - // evidencija računa - await posrednik.EvidentirajUBLAsync(File.ReadAllText("ubl.xml")); - - // izlazni računi - // var izlazni = await posrednik.IzlazniListAsync(DateTime.UtcNow.AddDays(-30), DateTime.UtcNow); - // Assert.NotNull(izlazni); - - // var first = izlazni.FirstOrDefault(); - // if(first != null) - // { - // Assert.NotNull(await posrednik.IzlazniPdfAsync(first.Id)); - // Assert.NotNull(await posrednik.IzlazniUBLAsync(first.Id)); - - // await posrednik.EvidentirajUplatuAsync(first.Id, DateTime.UtcNow, 100, NacinPlacanja.TransakcijskiRaCun); - // } - - // ulazni računi - var ulazni = await posrednik.UlazniListAsync(DateTime.UtcNow.AddDays(-30), DateTime.UtcNow); - Assert.NotNull(ulazni); - - var firstUlazni = ulazni.FirstOrDefault(); - if(firstUlazni != null) - { - Assert.NotNull(await posrednik.UlazniPdfAsync(firstUlazni.Id)); - Assert.NotNull(await posrednik.UlazniUBLAsync(firstUlazni.Id)); - - await posrednik.OdbijRacunAsync(firstUlazni.Id, RazlogOdbijanja.NeusklađenostKojaNeUtjeceNaObracunPoreza, "Nedostaje OIB"); - } - } - } -} \ No newline at end of file diff --git a/MAES.Fiskal2.Tests/PosredniciTests.cs b/MAES.Fiskal2.Tests/PosredniciTests.cs new file mode 100644 index 0000000..5dc0271 --- /dev/null +++ b/MAES.Fiskal2.Tests/PosredniciTests.cs @@ -0,0 +1,197 @@ + +using MAES.Fiskal2.Posrednici; +using Xunit.Abstractions; + +namespace MAES.Fiskal2.Tests; + +public class PosredniciTests +{ + readonly List posrednici = + [ + new Super + { + BusinessGuid = Environment.GetEnvironmentVariable("SUPER_BUSINESS_GUID") ?? throw new InvalidOperationException("SUPER_BUSINESS_GUID environment variable is not set."), + Username = Environment.GetEnvironmentVariable("SUPER_USERNAME") ?? throw new InvalidOperationException("SUPER_USERNAME environment variable is not set."), + Password = Environment.GetEnvironmentVariable("SUPER_PASSWORD") ?? throw new InvalidOperationException("SUPER_PASSWORD environment variable is not set."), + IsDev = true + }, + new EPoslovanje + { + ApiKey = Environment.GetEnvironmentVariable("EPOSLOVANJE_API_KEY") ?? throw new InvalidOperationException("EPOSLOVANJE_API_KEY environment variable is not set."), + IsDev = true + }, + new Doku + { + ApiKey = Environment.GetEnvironmentVariable("DOKU_API_KEY") ?? throw new InvalidOperationException("DOKU_API_KEY environment variable is not set."), + IsDev = true + } + ]; + + [Fact] + public async Task EvidentirajUBL() + { + var ubl = File.ReadAllText("ubl.xml"); + + foreach (var posrednik in posrednici.Where(p => p is not Fina)) + { + try + { + await posrednik.EvidentirajUBLAsync(ubl); + Console.WriteLine($"{posrednik.GetType().Name}: EvidentirajUBL OK"); + } + catch (Exception ex) + { + Console.WriteLine($"{posrednik.GetType().Name}: EvidentirajUBL FAIL {ex.Message}"); + } + } + } + + [Fact] + public async Task DohvatiIzlazneRacune() + { + foreach (var posrednik in posrednici.Where(p => p is not Fina)) + { + try + { + var izlazni = await posrednik.IzlazniListAsync( + DateTime.UtcNow.AddDays(-30), + DateTime.UtcNow); + + Console.WriteLine($"{posrednik.GetType().Name}: IzlazniList OK ({izlazni.Count()})"); + } + catch (Exception ex) + { + Console.WriteLine($"{posrednik.GetType().Name}: IzlazniList FAIL {ex.Message}"); + } + } + } + + [Fact] + public async Task DohvatiPrviIzlazniPdfIUBL() + { + foreach (var posrednik in posrednici.Where(p => p is not Fina)) + { + try + { + var izlazni = await posrednik.IzlazniListAsync( + DateTime.UtcNow.AddDays(-30), + DateTime.UtcNow); + + var first = izlazni.FirstOrDefault(); + + if (first == null) + { + Console.WriteLine($"{posrednik.GetType().Name}: nema izlaznih računa"); + continue; + } + + await posrednik.IzlazniPdfAsync(first.Id); + await posrednik.IzlazniUBLAsync(first.Id); + + Console.WriteLine($"{posrednik.GetType().Name}: Izlazni PDF + UBL OK"); + } + catch (Exception ex) + { + Console.WriteLine($"{posrednik.GetType().Name}: Izlazni PDF + UBL FAIL {ex.Message}"); + } + } + } + + [Fact] + public async Task EvidentirajUplatu() + { + foreach (var posrednik in posrednici.Where(p => p is not Fina)) + { + try + { + var izlazni = await posrednik.IzlazniListAsync( + DateTime.UtcNow.AddDays(-30), + DateTime.UtcNow); + + var first = izlazni.FirstOrDefault(); + + if (first == null) + { + Console.WriteLine($"{posrednik.GetType().Name}: nema izlaznih računa za uplatu"); + continue; + } + + await posrednik.EvidentirajUplatuAsync( + first.Id, + DateTime.UtcNow, + 100, + NacinPlacanja.TransakcijskiRaCun); + + Console.WriteLine($"{posrednik.GetType().Name}: EvidentirajUplatu OK"); + } + catch (Exception ex) + { + Console.WriteLine($"{posrednik.GetType().Name}: EvidentirajUplatu FAIL {ex.Message}"); + } + } + } + + [Fact] + public async Task UlazniRacuni() + { + foreach (var posrednik in posrednici.Where(p => p is not Fina)) + { + try + { + var ulazni = await posrednik.UlazniListAsync( + DateTime.UtcNow.AddDays(-30), + DateTime.UtcNow); + + var first = ulazni.FirstOrDefault(); + + if (first == null) + { + Console.WriteLine($"{posrednik.GetType().Name}: nema ulaznih računa"); + continue; + } + + await posrednik.UlazniPdfAsync(first.Id); + await posrednik.UlazniUBLAsync(first.Id); + + Console.WriteLine($"{posrednik.GetType().Name}: Ulazni PDF + UBL OK"); + } + catch (Exception ex) + { + Console.WriteLine($"{posrednik.GetType().Name}: Ulazni PDF + UBL FAIL {ex.Message}"); + } + } + } + + [Fact] + public async Task OdbijPrviUlazniRacun() + { + foreach (var posrednik in posrednici.Where(p => p is not Fina)) + { + try + { + var ulazni = await posrednik.UlazniListAsync( + DateTime.UtcNow.AddDays(-30), + DateTime.UtcNow); + + var first = ulazni.FirstOrDefault(); + + if (first == null) + { + Console.WriteLine($"{posrednik.GetType().Name}: nema ulaznih za odbijanje"); + continue; + } + + await posrednik.OdbijRacunAsync( + first.Id, + RazlogOdbijanja.NeusklađenostKojaNeUtjeceNaObracunPoreza, + "Nedostaje OIB"); + + Console.WriteLine($"{posrednik.GetType().Name}: OdbijRacun OK"); + } + catch (Exception ex) + { + Console.WriteLine($"{posrednik.GetType().Name}: OdbijRacun FAIL {ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/MAES.Fiskal2/MAES.Fiskal2.csproj b/MAES.Fiskal2/MAES.Fiskal2.csproj index ff3278d..cc1996a 100644 --- a/MAES.Fiskal2/MAES.Fiskal2.csproj +++ b/MAES.Fiskal2/MAES.Fiskal2.csproj @@ -10,7 +10,7 @@ MAES.Fiskal2 - 0.3.0 + 0.4.0 Roko Tomović MAES C# biblioteka za rad s Hrvatskim fiskalnim posrednicima diff --git a/MAES.Fiskal2/Posrednici/Doku.cs b/MAES.Fiskal2/Posrednici/Doku.cs new file mode 100644 index 0000000..73bd6b7 --- /dev/null +++ b/MAES.Fiskal2/Posrednici/Doku.cs @@ -0,0 +1,192 @@ +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; + +namespace MAES.Fiskal2.Posrednici; + +/// +/// Implementacija posrednika za Doku. https://api.doku.hr/docs/ +/// +public class Doku : Posrednik +{ + /// + /// API ključ za autentifikaciju. + /// + public string ApiKey { get; set; } = ""; + + /// + /// Konstruktor za inicijalizaciju Doku posrednika. + /// + public Doku() : base("https://api.doku.hr", "https://api-test.doku.hr") { } + + /// + /// Evidentira UBL dokument u Doku sustavu. + /// + /// UBL dokument. + /// Token za otkazivanje operacije. + /// + /// + public override async Task EvidentirajUBLAsync(string ubl, CancellationToken token = default) + { + await sendRequest(HttpMethod.Post, "/documents/invoices/outgoing/upload", new + { + xml = Convert.ToBase64String(Encoding.UTF8.GetBytes(ubl)) + }, token); + } + + /// + /// Dohvaća listu izlaznih e-računa za zadano razdoblje. + /// + /// Datum početka razdoblja. + /// Datum kraja razdoblja. + /// Token za otkazivanje operacije. + /// Lista izlaznih e-računa. + public override async Task> IzlazniListAsync(DateTime from, DateTime to, CancellationToken token = default) + { + var res = await sendRequest(HttpMethod.Get, $"/documents/invoices/outgoing?IssueDateFrom={from:O}&IssueDateTo={to:O}", null, token); + var doc = JsonDocument.Parse(res); + + var list = new List(); + foreach (var item in doc.RootElement.GetProperty("records").EnumerateArray()) + { + list.Add(new IzlazniERacun + { + Id = item.GetProperty("id").GetInt64().ToString(), + Broj = item.GetProperty("name").GetString()!, + Datum = item.GetProperty("issueDate").GetDateTime(), + PartnerNaziv = item.GetProperty("receiver").GetProperty("name").GetString()!, + PartnerOIB = item.GetProperty("receiver").GetProperty("oib").GetString()!, + Status = item.GetProperty("status").GetString() switch // <-- ovo treba popravit + { + "DRAFT" => IzlazniERacunStatus.Nacrt, + "SENT" => IzlazniERacunStatus.Poslano, + "REJECTED" => IzlazniERacunStatus.Odbijeno, + _ => IzlazniERacunStatus.Dostavljeno + } + }); + } + return list; + } + + /// + /// Dohvaća UBL/XML sadržaj izlaznog računa. + /// + /// ID izlaznog računa. + /// Token za otkazivanje operacije. + /// Base64 enkodirani XML sadržaj izlaznog računa. + public override async Task IzlazniUBLAsync(string id, CancellationToken token = default) + { + var content = await sendRequest(HttpMethod.Get, $"/documents/invoices/outgoing/{id}/download?format=ubl", null, token); + var doc = JsonDocument.Parse(content); + return doc.RootElement.GetProperty("data").GetProperty("xml").GetString()!; + } + + /// + /// Evidentira uplatu za izlazni račun. + /// + /// ID izlaznog računa. + /// Datum uplate. + /// Iznos uplate. + /// Način plaćanja. + /// Token za otkazivanje operacije. + /// + public override async Task EvidentirajUplatuAsync(string id, DateTime date, double amount, NacinPlacanja paymentMethod, CancellationToken token = default) + { + await sendRequest(HttpMethod.Post, $"/documents/invoices/outgoing/{id}/payments", new + { + datumNaplate = date, + naplaceniIznos = amount, + nacinPlacanja = paymentMethod switch + { + NacinPlacanja.ObračunskoPlaćanje => "Z", + NacinPlacanja.TransakcijskiRaCun => "T", + _ => "O" + } + }, token); + } + + /// + /// Dohvaća ulazne e-račune za zadano razdoblje. + /// + /// Početni datum. + /// Završni datum. + /// Token za otkazivanje operacije. + /// Lista ulaznih e-računa. + public override async Task> UlazniListAsync(DateTime from, DateTime to, CancellationToken token = default) + { + var content = await sendRequest(HttpMethod.Get, $"/documents/invoices/incoming?IssueDateFrom={from:O}&IssueDateTo={to:O}", null, token); + var doc = JsonDocument.Parse(content); + var list = new List(); + foreach (var item in doc.RootElement.GetProperty("records").EnumerateArray()) + { + list.Add(new UlazniERacun + { + Id = item.GetProperty("id").GetInt64().ToString(), + Broj = item.GetProperty("name").GetString()!, + Datum = item.GetProperty("issueDate").GetDateTime(), + Partner = item.GetProperty("sender").GetProperty("name").GetString()!, + PartnerOIB = item.GetProperty("sender").GetProperty("oib").GetString()!, + Status = item.GetProperty("status").GetString() switch + { + "RECEIVED" => UlazniERacunStatus.Zaprimljeno, // <-- ovo treba popravit mozda treba pogledat dokumentaciju + "APPROVED" => UlazniERacunStatus.Odobreno, + "REJECTED" => UlazniERacunStatus.Odbijeno, + _ => UlazniERacunStatus.Likvidirano + } + }); + } + return list; + } + + /// + /// Dohvaća UBL/XML sadržaj ulaznog računa. + /// + /// ID ulaznog računa. + /// Token za otkazivanje operacije. + /// Base64 enkodirani XML sadržaj ulaznog računa. + public override async Task UlazniUBLAsync(string id, CancellationToken token = default) + { + var content = await sendRequest(HttpMethod.Get, $"/documents/invoices/incoming/{id}/export", null, token); + var doc = JsonDocument.Parse(content); + return doc.RootElement.GetProperty("data").GetProperty("xml").GetString()!; + } + + /// + /// Odbija ulazni račun uz navedeni razlog i opis. + /// + /// ID ulaznog računa. + /// Razlog odbijanja. + /// Opis odbijanja. + /// Token za otkazivanje operacije. + /// + public override async Task OdbijRacunAsync(string id, RazlogOdbijanja razlog, string opis, CancellationToken token = default) + { + await sendRequest(HttpMethod.Post, $"/documents/invoices/incoming/{id}/reject", new + { + datumOdbijanja = DateTime.Now.ToString("yyyy-MM-dd"), + vrstaRazlogaOdbijanja = razlog switch + { + RazlogOdbijanja.NeusklađenostKojaNeUtjeceNaObracunPoreza => "N", + RazlogOdbijanja.NeusklađenostKojaUtjeceNaObracunPoreza => "U", + _ => "O" + }, + razlogOdbijanja = opis + }, token); + } + + async Task sendRequest(HttpMethod method, string uri, object? body = null, CancellationToken token = default) + { + var request = new HttpRequestMessage(method, uri) + { + Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json") + }; + + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + request.Headers.TryAddWithoutValidation("SOFTWARE-API-TOKEN", ApiKey); + + request.Headers.TryAddWithoutValidation("Authorization", ApiKey); + + return await SendRequest(request, token); + } +} \ No newline at end of file diff --git a/MAES.Fiskal2/Posrednici/EPoslovanje.cs b/MAES.Fiskal2/Posrednici/EPoslovanje.cs index 0340c6f..045b3f2 100644 --- a/MAES.Fiskal2/Posrednici/EPoslovanje.cs +++ b/MAES.Fiskal2/Posrednici/EPoslovanje.cs @@ -10,81 +10,14 @@ namespace MAES.Fiskal2.Posrednici; public class EPoslovanje : Posrednik { /// - /// OIB poslovnog subjekta. + /// API ključ za autentifikaciju. /// - public string OIB { get; set; } = ""; - - /// - /// Korisničko ime za autentifikaciju. - /// - public string Username { get; set; } = ""; - - /// - /// Lozinka za autentifikaciju. - /// - public string Password { get; set; } = ""; + public string ApiKey { get; set; } = ""; /// /// Inicijalizira novog ePoslovanje posrednika s definiranim URI postavkama za produkcijsko i razvojno okruženje. /// - public EPoslovanje() - { - UriProd = "https://eracun.eposlovanje.hr"; - UriDev = "https://test.eposlovanje.hr"; - } - - HttpClient createClient() - { - var client = new HttpClient - { - BaseAddress = new Uri(Uri) - }; - - client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - - return client; - } - - async Task apiKey() - { - using var client = createClient(); - - var request = new HttpRequestMessage(HttpMethod.Post, "/api/v2/account/apikey") - { - Content = new StringContent(JsonSerializer.Serialize(new - { - username = Username, - password = Password, - vatId = OIB, - softwareId = "MAES.Fiskal2" - }), Encoding.UTF8, "application/json") - }; - - var response = await client.SendAsync(request); - var json = await response.Content.ReadAsStringAsync(); - - if (!response.IsSuccessStatusCode) - throw new HttpRequestException(json); - - using var doc = JsonDocument.Parse(json); - return doc.RootElement.GetProperty("apiKey").GetString()!; - } - - async Task sendRequest(HttpMethod method, string url, object? body, CancellationToken token) - { - using var client = createClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await apiKey()); - - var req = new HttpRequestMessage(method, url); - if (body != null) req.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"); - - var res = await client.SendAsync(req, token); - var json = await res.Content.ReadAsStringAsync(); - - if (!res.IsSuccessStatusCode) throw new HttpRequestException(json); - - return JsonDocument.Parse(json); - } + public EPoslovanje() : base("https://eracun.eposlovanje.hr", "https://test.eposlovanje.hr") { } async Task changeStatusAsync(string id, int status, string? note = null, double? partialPaymentAmount = null, CancellationToken token = default) { @@ -104,21 +37,22 @@ async Task changeStatusAsync(string id, int status, string? note = null, double? /// Dohvaća XML/UBL sadržaj ulaznog računa. /// public override async Task UlazniUBLAsync(string id, CancellationToken token = default) => - (await sendRequest(HttpMethod.Get, $"/api/v2/document/get/{id}", null, token)).RootElement.GetProperty("document").GetString()!; + JsonDocument.Parse(await sendRequest(HttpMethod.Get, $"/api/v2/document/get/{id}", null, token)).RootElement.GetProperty("document").GetString()!; /// /// Dohvaća PDF sadržaj ulaznog računa. /// public override async Task UlazniPdfAsync(string id, CancellationToken token = default) => - Convert.FromBase64String((await sendRequest(HttpMethod.Get, $"/api/v2/document/visualization/{id}", null, token)).RootElement.GetProperty("pdf").GetString()!); + Convert.FromBase64String(JsonDocument.Parse(await sendRequest(HttpMethod.Get, $"/api/v2/document/visualization/{id}", null, token)).RootElement.GetProperty("pdf").GetString()!); /// /// Dohvaća popis ulaznih e-računa. /// public override async Task> UlazniListAsync(DateTime from, DateTime to, CancellationToken token = default) { - var doc = await sendRequest(HttpMethod.Get, $"/api/v2/document/incoming?insertedFrom={from:O}&insertedTo={to:O}&limit=1000&offset=0", null, token); - + var content = await sendRequest(HttpMethod.Get, $"/api/v2/document/incoming?insertedFrom={from:O}&insertedTo={to:O}&limit=1000&offset=0", null, token); + var doc = JsonDocument.Parse(content); + var list = new List(); foreach (var item in doc.RootElement.EnumerateArray()) @@ -157,7 +91,8 @@ public override async Task> IzlazniListAsync( DateTime to, CancellationToken token = default) { - var doc = await sendRequest(HttpMethod.Get, $"/api/v2/document/outgoing?insertedFrom={from:O}&insertedTo={to:O}&limit=1000&offset=0", null, token); + var content = await sendRequest(HttpMethod.Get, $"/api/v2/document/outgoing?insertedFrom={from:O}&insertedTo={to:O}&limit=1000&offset=0", null, token); + var doc = JsonDocument.Parse(content); var list = new List(); @@ -183,7 +118,7 @@ public override async Task> IzlazniListAsync( public override async Task EvidentirajUBLAsync(string ubl, CancellationToken token = default) => await sendRequest(HttpMethod.Post, "/api/v2/document/send", new { document = ubl, - softwareId = "MAES.Blagajna" + softwareId = "MAES.Fiskal2" }, token); /// @@ -200,4 +135,18 @@ public override Task EvidentirajUplatuAsync(string id, DateTime date, double amo /// public override Task OdbijRacunAsync(string id, RazlogOdbijanja razlog, string opis, CancellationToken token = default) => changeStatusAsync(id, status: 6, $"{razlog}: {opis}", token: token); + + async Task sendRequest(HttpMethod method, string uri, object? body = null, CancellationToken token = default) + { + var request = new HttpRequestMessage(method, uri) + { + Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json") + }; + + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + request.Headers.Authorization = new AuthenticationHeaderValue(ApiKey); + + return await SendRequest(request, token); + } } \ No newline at end of file diff --git a/MAES.Fiskal2/Posrednici/Fina.cs b/MAES.Fiskal2/Posrednici/Fina.cs index 7f789fa..2f989ff 100644 --- a/MAES.Fiskal2/Posrednici/Fina.cs +++ b/MAES.Fiskal2/Posrednici/Fina.cs @@ -23,11 +23,7 @@ public class Fina : Posrednik /// /// Inicijalizira novog FINA posrednika s definiranim URI postavkama za produkcijsko i razvojno okruženje. /// - public Fina() - { - UriProd = "https://eracun.fina.hr/eracun-b2b/services/eRacunB2BPortType"; - UriDev = "https://eracun-test.fina.hr/eracun-b2b/services/eRacunB2BPortType"; - } + public Fina() : base("https://eracun.fina.hr/eracun-b2b/services/eRacunB2BPortType", "https://eracun-test.fina.hr/eracun-b2b/services/eRacunB2BPortType") { } /// /// Evidentira i šalje UBL/XML dokument prema FINA e-Račun sustavu. @@ -66,7 +62,8 @@ public override async Task EvidentirajUBLAsync(string ubl, CancellationToken tok } }; - using var client = new eRacunB2BPortTypeClient(eRacunB2BPortTypeClient.EndpointConfiguration.eRacunB2BPortType, Uri); + var baseAddress = IsDev ? "https://eracun-test.fina.hr/eracun-b2b/services/eRacunB2BPortType" : "https://eracun.fina.hr/eracun-b2b/services/eRacunB2BPortType"; + using var client = new eRacunB2BPortTypeClient(eRacunB2BPortTypeClient.EndpointConfiguration.eRacunB2BPortType, baseAddress); var res = await client.sendB2BOutgoingInvoiceAsync(msg); @@ -181,13 +178,4 @@ static string GetBuyerId(XDocument xml) .Value ?? throw new InvalidOperationException("UBL nema BuyerID."); } - - eRacunB2BPortTypeClient CreateClient() => new (eRacunB2BPortTypeClient.EndpointConfiguration.eRacunB2BPortType, Uri); - - HeaderSupplierType Header() => new() - { - MessageID = Guid.NewGuid().ToString(), - ERPID = "MAES.Fiskal2", - SupplierID = OIB - }; } \ No newline at end of file diff --git a/MAES.Fiskal2/Posrednici/MER.cs b/MAES.Fiskal2/Posrednici/MER.cs index 7d53a9e..95a2dea 100644 --- a/MAES.Fiskal2/Posrednici/MER.cs +++ b/MAES.Fiskal2/Posrednici/MER.cs @@ -29,31 +29,7 @@ public class MER : Posrednik /// Inicijalizira novog Moj-eRačun posrednika s definiranim URI adresama /// za produkcijsko i razvojno okruženje. /// - public MER() - { - UriProd = "https://www.moj-eracun.hr"; - UriDev = "https://demo.moj-eracun.hr"; - } - - async Task sendRequest(HttpMethod method, string url, object? body, CancellationToken token) - { - var client = new HttpClient - { - BaseAddress = new Uri(Uri) - }; - - client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - - var req = new HttpRequestMessage(method, url); - if (body != null) req.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"); - - var res = await client.SendAsync(req, token); - var json = await res.Content.ReadAsStringAsync(); - - if (!res.IsSuccessStatusCode) throw new HttpRequestException(json); - - return json; - } + public MER() : base("https://www.moj-eracun.hr", "https://demo.moj-eracun.hr") { } /// /// Šalje UBL/XML dokument izlaznog e-računa na Moj-eRačun servis. @@ -62,7 +38,7 @@ async Task sendRequest(HttpMethod method, string url, object? body, Canc /// /// UBL XML sadržaj e-računa. /// Token za otkazivanje operacije. - public override async Task EvidentirajUBLAsync(string ubl, CancellationToken cancellationToken = default) =>await sendRequest(HttpMethod.Post, "/apis/v2/send", new + public override async Task EvidentirajUBLAsync(string ubl, CancellationToken cancellationToken = default) => await sendRequest(HttpMethod.Post, "/apis/v2/send", new { Username, Password, @@ -185,4 +161,13 @@ public override async Task OdbijRacunAsync(string id, RazlogOdbijanja razlog, st } }, cancellationToken); } + + async Task sendRequest(HttpMethod method, string uri, object? body = null, CancellationToken token = default) + { + using var request = new HttpRequestMessage(method, uri); + + if(body != null) request.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"); + + return await SendRequest(request, token); + } } \ No newline at end of file diff --git a/MAES.Fiskal2/Posrednici/Parra.cs b/MAES.Fiskal2/Posrednici/Parra.cs deleted file mode 100644 index 28413f9..0000000 --- a/MAES.Fiskal2/Posrednici/Parra.cs +++ /dev/null @@ -1,203 +0,0 @@ -using System.Net.Http.Headers; -using System.Text; -using System.Text.Json; - -namespace MAES.Fiskal2.Posrednici; - -/// -/// Implementacija posrednika Parra v1.1. https://api.parra.hr/docs/#api-v11 -/// -public class Parra : Posrednik -{ - /// - /// OIB poslovnog subjekta. - /// - public string OIB { get; set; } = ""; - - /// - /// Korisničko ime za autentifikaciju. - /// - public string Username { get; set; } = ""; - - /// - /// Lozinka za autentifikaciju. - /// - public string Password { get; set; } = ""; - - /// - /// Inicijalizira novog Parra posrednika s definiranim URI postavkama za produkcijsko i razvojno okruženje. - /// - public Parra() - { - UriProd = "https://api.parra.hr"; - UriDev = ""; - } - - HttpClient createClient() - { - var client = new HttpClient - { - BaseAddress = new Uri(Uri) - }; - - client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - - return client; - } - - async Task apiKey() - { - using var client = createClient(); - - var request = new HttpRequestMessage(HttpMethod.Post, "/api/v2/account/apikey") - { - Content = new StringContent(JsonSerializer.Serialize(new - { - username = Username, - password = Password, - vatId = OIB, - softwareId = "MAES.Fiskal2" - }), Encoding.UTF8, "application/json") - }; - - var response = await client.SendAsync(request); - var json = await response.Content.ReadAsStringAsync(); - - if (!response.IsSuccessStatusCode) - throw new HttpRequestException(json); - - using var doc = JsonDocument.Parse(json); - return doc.RootElement.GetProperty("apiKey").GetString()!; - } - - async Task sendRequest(HttpMethod method, string url, object? body, CancellationToken token) - { - using var client = createClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await apiKey()); - - var req = new HttpRequestMessage(method, url); - if (body != null) req.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"); - - var res = await client.SendAsync(req, token); - var json = await res.Content.ReadAsStringAsync(); - - if (!res.IsSuccessStatusCode) throw new HttpRequestException(json); - - return JsonDocument.Parse(json); - } - - async Task changeStatusAsync(string id, int status, string? note = null, double? partialPaymentAmount = null, CancellationToken token = default) - { - var body = new Dictionary - { - ["status"] = status, - ["changedOn"] = DateTimeOffset.Now.ToString("O") - }; - - if (!string.IsNullOrWhiteSpace(note)) body["note"] = note; - if (partialPaymentAmount.HasValue) body["partialPaymentAmount"] = partialPaymentAmount.Value; - - await sendRequest(HttpMethod.Post, $"/api/v2/document/changestatus/{id}", body, token); - } - - /// - /// Dohvaća XML/UBL sadržaj ulaznog računa. - /// - public override async Task UlazniUBLAsync(string id, CancellationToken token = default) => - (await sendRequest(HttpMethod.Get, $"/api/v2/document/get/{id}", null, token)).RootElement.GetProperty("document").GetString()!; - - /// - /// Dohvaća PDF sadržaj ulaznog računa. - /// - public override async Task UlazniPdfAsync(string id, CancellationToken token = default) => - Convert.FromBase64String((await sendRequest(HttpMethod.Get, $"/api/v2/document/visualization/{id}", null, token)).RootElement.GetProperty("pdf").GetString()!); - - /// - /// Dohvaća popis ulaznih e-računa. - /// - public override async Task> UlazniListAsync(DateTime from, DateTime to, CancellationToken token = default) - { - var doc = await sendRequest(HttpMethod.Get, $"/api/v2/document/incoming?insertedFrom={from:O}&insertedTo={to:O}&limit=1000&offset=0", null, token); - - var list = new List(); - - foreach (var item in doc.RootElement.EnumerateArray()) - { - list.Add(new UlazniERacun - { - Id = item.GetProperty("id").GetInt64().ToString(), - Datum = item.GetProperty("issuedOn").GetDateTime(), - Broj = item.GetProperty("documentId").GetString()!, - Status = UlazniERacunStatus.Zaprimljeno, // TODO: ovo treba popravit - Partner = item.GetProperty("customerPartyName").GetString()!, - PartnerOIB = item.GetProperty("customerPartyVATId").GetString()! - }); - } - - return list; - } - - /// - /// Dohvaća XML/UBL sadržaj izlaznog računa. - /// - public override async Task IzlazniUBLAsync(string id, CancellationToken token = default) - => await UlazniUBLAsync(id, token); - - /// - /// Dohvaća PDF sadržaj izlaznog računa. - /// - public override async Task IzlazniPdfAsync(string id, CancellationToken token = default) - => await UlazniPdfAsync(id, token); - - /// - /// Dohvaća popis izlaznih e-računa. - /// - public override async Task> IzlazniListAsync( - DateTime from, - DateTime to, - CancellationToken token = default) - { - var doc = await sendRequest(HttpMethod.Get, $"/api/v2/document/outgoing?insertedFrom={from:O}&insertedTo={to:O}&limit=1000&offset=0", null, token); - - var list = new List(); - - foreach (var item in doc.RootElement.EnumerateArray()) - { - list.Add(new IzlazniERacun - { - Id = item.GetProperty("id").GetInt64().ToString(), - Broj = item.GetProperty("documentId").GetString()!, - Datum = item.GetProperty("issuedOn").GetDateTime(), - PartnerNaziv = item.GetProperty("customerPartyName").GetString()!, - PartnerOIB = item.GetProperty("customerPartyVATId").GetString()!, - Status = IzlazniERacunStatus.Poslano // TODO: ovo treba popravit - }); - } - - return list; - } - - /// - /// Evidentira UBL dokument u ePoslovanje sustav. - /// - public override async Task EvidentirajUBLAsync(string ubl, CancellationToken token = default) => await sendRequest(HttpMethod.Post, "/api/v2/document/send", new - { - document = ubl, - softwareId = "MAES.Blagajna" - }, token); - - /// - /// Evidentira uplatu za račun. - /// - public override Task EvidentirajUplatuAsync(string id, DateTime date, double amount, NacinPlacanja paymentMethod, CancellationToken token = default) - { - var status = amount > 0 ? 8 : 7; // 8: partialno, 7: potpuno - return changeStatusAsync(id, status, partialPaymentAmount: status == 8 ? amount : null, token: token); - } - - /// - /// Odbija račun. - /// - public override Task OdbijRacunAsync(string id, RazlogOdbijanja razlog, string opis, CancellationToken token = default) => - changeStatusAsync(id, status: 6, $"{razlog}: {opis}", token: token); -} \ No newline at end of file diff --git a/MAES.Fiskal2/Posrednici/Super.cs b/MAES.Fiskal2/Posrednici/Super.cs index 273b7e1..fc157bf 100644 --- a/MAES.Fiskal2/Posrednici/Super.cs +++ b/MAES.Fiskal2/Posrednici/Super.cs @@ -29,53 +29,7 @@ public class Super : Posrednik /// /// Inicijalizira novog Super posrednika s definiranim URI postavkama za produkcijsko i razvojno okruženje. /// - public Super() - { - UriProd = "https://api.super.hr/"; - UriDev = "https://apitest.super.hr/"; - } - - async Task postRequest(string uri, Dictionary body, CancellationToken cancellationToken) - { - using var client = new HttpClient(); - client.BaseAddress = new Uri(Uri); - client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - - if (token == null || DateTime.UtcNow >= token.Value.Value) - { - using var tokenRequest = new HttpRequestMessage(HttpMethod.Post, "Token"); - tokenRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - tokenRequest.Content = new FormUrlEncodedContent(new Dictionary { ["grant_type"] = "password", ["username"] = Username, ["password"] = Password }); - - var tokenResponse = await client.SendAsync(tokenRequest, cancellationToken); - - if (!tokenResponse.IsSuccessStatusCode) throw new HttpRequestException($"Greška prilikom dohvaćanja tokena"); - - using var doc = JsonDocument.Parse(await tokenResponse.Content.ReadAsStringAsync()); - - token = new KeyValuePair( - doc.RootElement.GetProperty("access_token").GetString()!, - DateTime.Now.AddSeconds(doc.RootElement.GetProperty("expires_in").GetInt32() - 60) - ); - } - - body.Add("MessageId", Guid.NewGuid().ToString()); - body.Add("CompanyGuid", BusinessGuid); - - using var request = new HttpRequestMessage(HttpMethod.Post, uri); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token!.Value.Key); - request.Content = new FormUrlEncodedContent(body); - request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); - - var response = await client.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) throw new HttpRequestException($"Greška prilikom slanja zahtjeva: {uri}"); - - var jsonDocument = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); - if(jsonDocument.RootElement.TryGetProperty("errorMessage", out var errorMessage) && errorMessage.GetString() is string error && !string.IsNullOrWhiteSpace(error)) throw new Exception(error); - return jsonDocument; - } + public Super() : base("https://api.super.hr/", "https://apitest.super.hr/") { } /// /// Dohvaća XML/UBL sadržaj ulaznog računa po njegovom identifikatoru. @@ -85,12 +39,12 @@ async Task postRequest(string uri, Dictionary body /// XML/UBL sadržaj računa kao tekst. public override async Task UlazniUBLAsync(string id, CancellationToken cancellationToken = default) { - var jDoc = await postRequest("api/Invoice/GetInvoice", new Dictionary + var doc = await postRequestAsync("api/Invoice/GetInvoice", new Dictionary { ["Guid"] = id.ToString() }, cancellationToken); - if (!jDoc.RootElement.TryGetProperty("invoiceUbl", out var el)) throw new Exception("UBL not found in response"); + if (!doc.RootElement.TryGetProperty("invoiceUbl", out var el)) throw new Exception("UBL not found in response"); return Encoding.UTF8.GetString(Convert.FromBase64String(el.GetString())); } @@ -100,7 +54,7 @@ public override async Task UlazniUBLAsync(string id, CancellationToken c /// Identifikator ulaznog računa. /// Token za otkazivanje operacije. /// PDF sadržaj računa kao bajtni niz. - public override async Task UlazniPdfAsync(string id, CancellationToken cancellationToken = default) => Convert.FromBase64String((await postRequest("api/Invoice/GetInvoiceDetailVisualization", new Dictionary + public override async Task UlazniPdfAsync(string id, CancellationToken cancellationToken = default) => Convert.FromBase64String((await postRequestAsync("api/Invoice/GetInvoiceDetailVisualization", new Dictionary { ["Guid"] = id.ToString() }, cancellationToken)).RootElement.GetProperty("invoiceDetailVisualization").GetString()!); @@ -114,13 +68,13 @@ public override async Task UlazniUBLAsync(string id, CancellationToken c /// Popis ulaznih e-računa. public override async Task> UlazniListAsync(DateTime from, DateTime to, CancellationToken cancellationToken = default) { - var jsonDocument = await postRequest("api/Invoice/GetInvoiceList", new Dictionary + var doc = await postRequestAsync("api/Invoice/GetInvoiceList", new Dictionary { ["DateFrom"] = from.ToString("yyyy-MM-dd"), ["DateTo"] = to.ToString("yyyy-MM-dd") }, cancellationToken); - return jsonDocument.RootElement.GetProperty("invoices").EnumerateArray().Select(x => new UlazniERacun + return doc.RootElement.GetProperty("invoices").EnumerateArray().Select(x => new UlazniERacun { Broj = x.GetProperty("UniqueId").GetString() ?? "", Datum = x.GetProperty("IssueDate").GetDateTime(), @@ -143,12 +97,12 @@ public override async Task> UlazniListAsync(DateTime f /// XML/UBL sadržaj računa kao tekst. public override async Task IzlazniUBLAsync(string id, CancellationToken cancellationToken = default) { - var jDoc = await postRequest("api/SendingInvoice/GetSendingInvoice", new Dictionary + var doc = await postRequestAsync("api/SendingInvoice/GetSendingInvoice", new Dictionary { ["Guid"] = id.ToString() }, cancellationToken); - if (!jDoc.RootElement.TryGetProperty("sendingInvoiceUbl", out var el)) throw new Exception("UBL not found in response"); + if (!doc.RootElement.TryGetProperty("sendingInvoiceUbl", out var el)) throw new Exception("UBL not found in response"); return Encoding.UTF8.GetString(Convert.FromBase64String(el.GetString())); } @@ -161,12 +115,12 @@ public override async Task IzlazniUBLAsync(string id, CancellationToken /// PDF sadržaj računa kao bajtni niz. public override async Task IzlazniPdfAsync(string id, CancellationToken cancellationToken = default) { - var jDoc = await postRequest("api/SendingInvoice/GetSendingInvoiceDetailVisualization", new Dictionary + var doc = await postRequestAsync("api/SendingInvoice/GetSendingInvoiceDetailVisualization", new Dictionary { ["Guid"] = id.ToString() }, cancellationToken); - var root = jDoc.RootElement; + var root = doc.RootElement; if (!root.TryGetProperty("sendingInvoiceDetailVisualization", out var el)) throw new Exception("PDF not found in response"); return Convert.FromBase64String(el.GetString()); @@ -182,13 +136,13 @@ public override async Task IzlazniPdfAsync(string id, CancellationToken /// Popis izlaznih e-računa. public override async Task> IzlazniListAsync(DateTime from, DateTime to, CancellationToken cancellationToken) { - var jsonDocument = await postRequest("api/SendingInvoice/GetSendingInvoiceList", new Dictionary + var doc = await postRequestAsync("api/SendingInvoice/GetSendingInvoiceList", new Dictionary { ["DateFrom"] = from.ToString("yyyy-MM-dd"), ["DateTo"] = to.ToString("yyyy-MM-dd") }, cancellationToken); - return jsonDocument.RootElement.GetProperty("invoices").EnumerateArray().Select(x => new IzlazniERacun + return doc.RootElement.GetProperty("sendingInvoices").EnumerateArray().Select(x => new IzlazniERacun { Broj = x.GetProperty("UniqueId").GetString() ?? "", Datum = x.GetProperty("IssueDate").GetDateTime(), @@ -208,7 +162,7 @@ public override async Task> IzlazniListAsync(DateTime /// /// UBL XML dokument ulaznog računa. /// Token za otkazivanje operacije. - public override async Task EvidentirajUBLAsync(string ubl, CancellationToken cancellationToken = default) => await postRequest("api/SendingInvoice/GetSendingInvoiceList", new Dictionary + public override async Task EvidentirajUBLAsync(string ubl, CancellationToken cancellationToken = default) => await postRequestAsync("api/SendingInvoice/GetSendingInvoiceList", new Dictionary { ["Base64EncodedUbl"] = Convert.ToBase64String(Encoding.UTF8.GetBytes(ubl)), ["UblDocumentType"] = "1", // 1 = račun, 2 = odobrenje @@ -224,7 +178,7 @@ public override async Task> IzlazniListAsync(DateTime /// Token za otkazivanje operacije. public override async Task EvidentirajUplatuAsync(string id, DateTime date, double amount, NacinPlacanja paymentMethod, CancellationToken cancellationToken = default) { - await postRequest("api/Invoice/SetInvoicePayment", new Dictionary + await postRequestAsync("api/Invoice/SetInvoicePayment", new Dictionary { ["Guid"] = id.ToString() }, cancellationToken); @@ -239,11 +193,46 @@ public override async Task EvidentirajUplatuAsync(string id, DateTime date, doub /// Token za otkazivanje operacije. public override async Task OdbijRacunAsync(string id, RazlogOdbijanja razlog, string opis, CancellationToken cancellationToken = default) { - await postRequest("api/SendingInvoice/RejectSendingInvoice", new Dictionary + await postRequestAsync("api/SendingInvoice/RejectSendingInvoice", new Dictionary { ["Guid"] = id.ToString(), ["RejectReasonType"] = razlog.ToString(), ["RejectionReasonDescription"] = opis }, cancellationToken); } + + async Task postRequestAsync(string uri, Dictionary body, CancellationToken cancellationToken = default) + { + // dohvati token ako ga nema ili je istekao + if (token == null || DateTime.UtcNow >= token.Value.Value) + { + using var tokenRequest = new HttpRequestMessage(HttpMethod.Post, "Token"); + tokenRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + tokenRequest.Content = new FormUrlEncodedContent(new Dictionary { ["grant_type"] = "password", ["username"] = Username, ["password"] = Password }); + + var tokenContent = await SendRequest(tokenRequest, cancellationToken); + + using var doc = JsonDocument.Parse(tokenContent); + + token = new KeyValuePair( + doc.RootElement.GetProperty("access_token").GetString()!, + DateTime.Now.AddSeconds(doc.RootElement.GetProperty("expires_in").GetInt32() - 60) + ); + } + + body.Add("MessageId", Guid.NewGuid().ToString()); + body.Add("CompanyGuid", BusinessGuid); + + using var request = new HttpRequestMessage(HttpMethod.Post, uri); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Value.Key); + request.Content = new FormUrlEncodedContent(body); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); + + var content = await SendRequest(request, cancellationToken); + + var jsonDocument = JsonDocument.Parse(content); + if(jsonDocument.RootElement.TryGetProperty("errorMessage", out var errorMessage) && errorMessage.GetString() is string error && !string.IsNullOrWhiteSpace(error)) throw new Exception(error); + return jsonDocument; + } } \ No newline at end of file diff --git a/MAES.Fiskal2/Posrednici/TER.cs b/MAES.Fiskal2/Posrednici/TER.cs new file mode 100644 index 0000000..dd7cf92 --- /dev/null +++ b/MAES.Fiskal2/Posrednici/TER.cs @@ -0,0 +1,20 @@ +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; + +namespace MAES.Fiskal2.Posrednici; + +/// +/// Implementacija posrednika za Tvoj eRačun. https://ter.hr/api-za-fiskalizaciju/ +/// +public class TER : Posrednik +{ + /// + /// Konstruktor za inicijalizaciju TER posrednika. + /// + public TER() : base("", "") + { + //UriProd = "https://api.ter.hr"; + //UriDev = "https://api-test.ter.hr"; + } +} \ No newline at end of file diff --git a/MAES.Fiskal2/Posrednik.cs b/MAES.Fiskal2/Posrednik.cs index 730d3b7..4c579b8 100644 --- a/MAES.Fiskal2/Posrednik.cs +++ b/MAES.Fiskal2/Posrednik.cs @@ -1,3 +1,5 @@ +using System.Text; +using System.Text.Json; using System.Text.Json.Serialization; using MAES.Fiskal2.Posrednici; @@ -11,29 +13,15 @@ namespace MAES.Fiskal2; [JsonDerivedType(typeof(Super), "Super")] [JsonDerivedType(typeof(EPoslovanje), "EPoslovanje")] [JsonDerivedType(typeof(Fina), "Fina")] -public abstract class Posrednik +[JsonDerivedType(typeof(Doku), "Doku")] +[JsonDerivedType(typeof(MER), "MER")] +public abstract class Posrednik(string urlProd, string urlDev) { /// /// Označava koristi li se razvojno okruženje. - /// Ako je true, koristi se ; inače . /// public bool IsDev { get; set; } - /// - /// Produkcijski URI servisa. - /// - protected string UriProd { private get; set; } = ""; - - /// - /// URI razvojnog (testnog) okruženja servisa. - /// - protected string UriDev { private get; set; } = ""; - - /// - /// Aktivni URI servisa ovisno o odabranom okruženju. - /// - protected string Uri => IsDev ? UriDev : UriProd; - /// /// Evidentira izlazni eRačun na temelju UBL sadržaja. /// @@ -67,25 +55,13 @@ public virtual Task EvidentirajUplatuAsync( /// Kolekcija izlaznih eRačuna. public virtual Task> IzlazniListAsync(DateTime from, DateTime to, CancellationToken token = default) => throw new NotImplementedException(); - /// - /// Dohvaća popis izlaznih eRačuna unutar zadanog razdoblja. - /// - /// Stranica rezultata. - /// Broj rezultata po stranici. - /// Token za otkazivanje operacije. - /// Kolekcija izlaznih eRačuna. - public virtual Task> IzlazniListAsync(int page, int pageSize, CancellationToken token = default) => throw new NotImplementedException(); - /// /// Dohvaća PDF prikaz izlaznog računa. /// /// Identifikator računa. /// Token za otkazivanje operacije. /// Sadržaj PDF dokumenta kao niz bajtova. - public virtual Task IzlazniPdfAsync( - string id, - CancellationToken token = default) => - throw new NotImplementedException(); + public virtual Task IzlazniPdfAsync(string id, CancellationToken token = default) => throw new NotImplementedException(); /// /// Dohvaća UBL XML izlaznog računa. @@ -93,24 +69,16 @@ public virtual Task IzlazniPdfAsync( /// Identifikator računa. /// Token za otkazivanje operacije. /// UBL XML sadržaj računa. - public virtual Task IzlazniUBLAsync( - string id, - CancellationToken token = default) => - throw new NotImplementedException(); + public virtual Task IzlazniUBLAsync(string id, CancellationToken token = default) => throw new NotImplementedException(); /// - /// Odbija račun uz navedeni razlog i opis. + /// Odbija ulazni eRačun uz navedeni razlog i opis. /// /// Identifikator računa. /// Razlog odbijanja. /// Dodatni opis odbijanja. /// Token za otkazivanje operacije. - public virtual Task OdbijRacunAsync( - string id, - RazlogOdbijanja razlog, - string opis, - CancellationToken token = default) => - throw new NotImplementedException(); + public virtual Task OdbijRacunAsync(string id, RazlogOdbijanja razlog, string opis, CancellationToken token = default) => throw new NotImplementedException(); /// /// Dohvaća popis ulaznih eRačuna unutar zadanog razdoblja. @@ -121,25 +89,13 @@ public virtual Task OdbijRacunAsync( /// Kolekcija ulaznih eRačuna. public virtual Task> UlazniListAsync(DateTime from, DateTime to, CancellationToken token = default) => throw new NotImplementedException(); - /// - /// Dohvaća popis ulaznih eRačuna unutar zadanog razdoblja. - /// - /// Stranica rezultata. - /// Broj rezultata po stranici. - /// Token za otkazivanje operacije. - /// Kolekcija ulaznih eRačuna. - public virtual Task> UlazniListAsync(int page, int pageSize, CancellationToken token = default) => throw new NotImplementedException(); - /// /// Dohvaća PDF prikaz ulaznog računa. /// /// Identifikator računa. /// Token za otkazivanje operacije. /// Sadržaj PDF dokumenta kao niz bajtova. - public virtual Task UlazniPdfAsync( - string id, - CancellationToken token = default) => - throw new NotImplementedException(); + public virtual Task UlazniPdfAsync(string id, CancellationToken token = default) => throw new NotImplementedException(); /// /// Dohvaća UBL XML ulaznog računa. @@ -147,8 +103,34 @@ public virtual Task UlazniPdfAsync( /// Identifikator računa. /// Token za otkazivanje operacije. /// UBL XML sadržaj računa. - public virtual Task UlazniUBLAsync( - string id, - CancellationToken token = default) => - throw new NotImplementedException(); + public virtual Task UlazniUBLAsync(string id, CancellationToken token = default) => throw new NotImplementedException(); + + /// + /// Prihvaća ulazni eRačun, označavajući ga kao zaprimljen ili odobren (ovisno o implementaciji posrednika). + /// + /// Identifikator računa. + /// Token za otkazivanje operacije. + /// + /// + public virtual Task PrihvatiRacunAsync(string id, CancellationToken token = default) => throw new NotImplementedException(); + + /// + /// Generička metoda za slanje HTTP zahtjeva prema posredniku. Koristi se unutar specifičnih implementacija posrednika za komunikaciju s njihovim API-jem. + /// + /// Pripremljeni HTTP zahtjev. + /// Token za otkazivanje operacije. + /// Sadržaj odgovora kao niz znakova. + /// + protected async Task SendRequest(HttpRequestMessage request, CancellationToken token = default) + { + using var client = new HttpClient { BaseAddress = new Uri(IsDev ? urlDev : urlProd) }; + + using var response = await client.SendAsync(request, token); + + var content = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) throw new HttpRequestException(content); + + return content; + } } \ No newline at end of file diff --git a/README.md b/README.md index f82bee2..637ad1c 100644 --- a/README.md +++ b/README.md @@ -24,19 +24,21 @@ Modeli `UlazniERacun` i `IzlazniERacun` predstavljaju minimalne informacije o ra U `Posrednici/` direktoriju nalaze se konkretne implementacije -| Značajka / posrednik | `Super` | `EPoslovanje` | `Fina` | `Moj eRačun` | `Redok` | `Parra` | -|---|:---:|:---:|:---:|:---:|:---:|:---:| -| Dohvat ulaznih e-računa | ✅ | ✅ | ❌* | ❌ | ❌ | ❌ | -| Dohvat izlaznih e-računa | ✅ | ✅ | ❌* | ❌ | ❌ | ❌ | -| Dohvat UBL sadržaja | ✅ | ✅ | ❌* | ✅ | ❌ | ❌ | -| Dohvat PDF sadržaja | ✅ | ✅ | ❌* | ❌** | ❌ | ❌ | -| Evidentiranje UBL dokumenta | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | -| Evidentiranje uplate | ✅ | ✅ | ❌* | ❌ | ❌ | ❌ | -| Odbijanje računa | ✅ | ✅ | ❌* | ❌ | ❌ | ❌ | - -\* Fina nema pola ovih api callova ili su na nekom drugom endpointu treba vidit - -\*\* MER ne podržava dohvat PDF byte[] +| Značajka / posrednik | `Super` | `EPoslovanje` | `Fina` | `MER` | `Redok` | `Tvoj eRačun` | `Doku` | +|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| Dohvat ulaznih računa | ✅ | ✅ | ❌ | 🚧 | 🚧 | 🚧 | ⚠️ | +| Dohvat izlaznih računa | ✅ | ✅ | ❌ | 🚧 | 🚧 | 🚧 | ⚠️ | +| Dohvat UBL sadržaja | ✅ | ✅ | ❌ | ⚠️ | 🚧 | 🚧 | ⚠️ | +| Dohvat PDF sadržaja | ✅ | ✅ | ❌ | ❌ | 🚧 | 🚧 | ❌ | +| Evidentiranje UBL dokumenta | ✅ | ✅ | ⚠️ | ⚠️ | 🚧 | 🚧 | ⚠️ | +| Evidentiranje uplate | ✅ | ✅ | ❌ | 🚧 | 🚧 | 🚧 | ⚠️ | +| Odbijanje računa | ✅ | ✅ | ❌ | 🚧 | 🚧 | 🚧 | ⚠️ | +| Prihvaćanje računa | ❌ | 🚧 | ❌ | 🚧 | 🚧 | 🚧 | 🚧 | + +* ✅ — Implementirano +* ⚠️ — Nije testirano/Ne prolazi testove +* 🚧 — Nije još implementirano +* ❌ — Posrednik ne podržava ## Instalacija @@ -93,14 +95,20 @@ var posrednik = new MER Password = "...", OIB = "..." }; + +// Moj eRačun +var posrednik = new MER +{ + ApiKey = "...", + OIB = "..." +}; ``` ### Primjer korištenja posrednika ```csharp -// dohvat računa u razdoblju zadnjih mj. dana ili dohvati paginacijom zavisi sto posrednik podržava +// dohvat računa u razdoblju zadnjih mj. dana var racuni = posrednik.UlazniListAsync(DateTime.Now.AddMonths(-1), DateTime.Now); -var racuni = posrednik.UlazniListAsync(1, 20); var racun = racuni.FirstOrDefault(); if(racun != null) @@ -168,7 +176,11 @@ Abstraktna klasa `Posrednik` nudi sljedeće metode: - `Task OdbijRacunAsync(string id, RazlogOdbijana razlog, string opis)` - Odbija račun + Odbija ulazni eRačun + +- `Task PrihvatiRacunAsync(string id)` + + Prihvaća ulazni eRačun ## Izgradnja i pakiranje