diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..65fbf91 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build facturapi-net.sln --configuration Release --no-restore + + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - test_framework: net6.0 + runtime_version: 6.0.x + - test_framework: net8.0 + runtime_version: 8.0.x + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 10.0.x + ${{ matrix.runtime_version }} + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build facturapi-net.sln --configuration Release --no-restore + + - name: Test + run: dotnet test FacturapiTest/FacturapiTest.csproj --framework ${{ matrix.test_framework }} --configuration Release --no-build diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3305708..7c52a04 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,10 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: "10.0.x" # Adjust the .NET version as needed + dotnet-version: | + 6.0.x + 8.0.x + 10.0.x - name: Restore dependencies run: dotnet restore @@ -27,6 +30,11 @@ jobs: - name: Build run: dotnet build --configuration Release --no-restore + - name: Test + run: | + dotnet test FacturapiTest/FacturapiTest.csproj --framework net6.0 --configuration Release --no-build + dotnet test FacturapiTest/FacturapiTest.csproj --framework net8.0 --configuration Release --no-build + - name: Pack run: dotnet pack --configuration Release --no-build --output ./nupkg diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d8a2a1..c243436 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,52 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [6.0.0] - 2026-03-30 + +### Migration Guide + +No changes are required if your code: +- Instantiates `FacturapiClient` and calls wrapper methods directly (for example `await client.Invoice.CreateAsync(...)`). +- Uses `var` when storing wrapper properties (for example `var invoices = client.Invoice;`). +- Does not depend on concrete wrapper types in method signatures, fields, or tests. + +You need to update your code if you: +- Type wrapper properties as concrete classes (`CustomerWrapper`, `InvoiceWrapper`, etc.). +- Mock or inject concrete wrapper classes. +- Expose concrete wrappers in your own interfaces or public APIs. + +Before (v5): +```csharp +CustomerWrapper customers = client.Customer; +``` + +After (v6): +```csharp +ICustomerWrapper customers = client.Customer; +``` + +### Breaking + +- `IFacturapiClient` now exposes wrapper interfaces instead of concrete wrapper classes: + - `ICustomerWrapper`, `IProductWrapper`, `IInvoiceWrapper`, `IOrganizationWrapper`, `IReceiptWrapper`, `IRetentionWrapper`, `ICatalogWrapper`, `ICartaporteCatalogWrapper`, `IToolWrapper`, `IWebhookWrapper`. +- `FacturapiClient` properties now return those interface types as part of the public contract. +- Dropped `net452` target framework support. The package now targets `netstandard2.0`, `net6.0`, and `net8.0`. + +### Added + +- New public interfaces for all wrapper surfaces to improve dependency injection and mocking in tests. +- `Invoice.StampDraftAsync` and legacy `StampDraft` now co-exist; `StampDraft` is marked as obsolete. +- Added initial `FacturapiTest` test project with regression coverage for query building and wrapper behavior. +- Added `FacturapiClient.CreateWithCustomHttpClient(...)` for advanced scenarios where consumers need to provide their own `HttpClient` without changing the default constructor. +- Added organization team-management endpoints to `Organization` / `IOrganizationWrapper`: access listing and retrieval, invite send/cancel/respond flows, role listing/templates/operations, role CRUD, and role reassignment for team members. + +### Fixed + +- `Retention.CancelAsync` is now consistent with the rest of the SDK: supports `CancellationToken`, disposes HTTP responses, and uses shared error handling (`ThrowIfErrorAsync`). +- Query string generation now handles null values and empty query dictionaries safely. +- README examples were corrected to align with the current async API surface and valid C# snippets. +- Internal async calls in wrapper implementations now consistently use `ConfigureAwait(false)`. + ## [5.1.0] - 2026-03-12 ### Fix diff --git a/FacturapiClient.cs b/FacturapiClient.cs index cfe0b04..a97eaeb 100644 --- a/FacturapiClient.cs +++ b/FacturapiClient.cs @@ -8,27 +8,30 @@ namespace Facturapi { public sealed class FacturapiClient : IFacturapiClient { - public CustomerWrapper Customer { get; private set; } - public ProductWrapper Product { get; private set; } - public InvoiceWrapper Invoice { get; private set; } - public OrganizationWrapper Organization { get; private set; } - public ReceiptWrapper Receipt { get; private set; } - public RetentionWrapper Retention { get; private set; } - public CatalogWrapper Catalog { get; private set; } - public CartaporteCatalogWrapper CartaporteCatalog { get; private set; } - public ToolWrapper Tool { get; private set; } - public WebhookWrapper Webhook { get; private set; } + public ICustomerWrapper Customer { get; private set; } + public IProductWrapper Product { get; private set; } + public IInvoiceWrapper Invoice { get; private set; } + public IOrganizationWrapper Organization { get; private set; } + public IReceiptWrapper Receipt { get; private set; } + public IRetentionWrapper Retention { get; private set; } + public ICatalogWrapper Catalog { get; private set; } + public ICartaporteCatalogWrapper CartaporteCatalog { get; private set; } + public IToolWrapper Tool { get; private set; } + public IWebhookWrapper Webhook { get; private set; } private readonly HttpClient httpClient; + private readonly bool ownsHttpClient; private bool disposed; public FacturapiClient(string apiKey, string apiVersion = "v2") + : this(apiKey, apiVersion, CreateDefaultHttpClient(apiKey, apiVersion), ownsHttpClient: true) { - var apiKeyBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(apiKey + ":")); - this.httpClient = new HttpClient - { - BaseAddress = new Uri($"https://www.facturapi.io/{apiVersion}/") - }; - this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", apiKeyBase64); + } + + private FacturapiClient(string apiKey, string apiVersion, HttpClient httpClient, bool ownsHttpClient) + { + this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + this.ownsHttpClient = ownsHttpClient; + ConfigureHttpClient(this.httpClient, apiKey, apiVersion); this.Customer = new CustomerWrapper(apiKey, apiVersion, this.httpClient); this.Product = new ProductWrapper(apiKey, apiVersion, this.httpClient); @@ -42,6 +45,16 @@ public FacturapiClient(string apiKey, string apiVersion = "v2") this.Webhook = new WebhookWrapper(apiKey, apiVersion, this.httpClient); } + public static FacturapiClient CreateWithCustomHttpClient(string apiKey, HttpClient httpClient, string apiVersion = "v2") + { + if (httpClient == null) + { + throw new ArgumentNullException(nameof(httpClient)); + } + + return new FacturapiClient(apiKey, apiVersion, httpClient, ownsHttpClient: false); + } + public void Dispose() { if (this.disposed) @@ -49,9 +62,24 @@ public void Dispose() return; } - this.httpClient?.Dispose(); + if (this.ownsHttpClient) + { + this.httpClient.Dispose(); + } this.disposed = true; GC.SuppressFinalize(this); } + + private static HttpClient CreateDefaultHttpClient(string apiKey, string apiVersion) + { + return new HttpClient(); + } + + private static void ConfigureHttpClient(HttpClient client, string apiKey, string apiVersion) + { + var apiKeyBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(apiKey + ":")); + client.BaseAddress = new Uri($"https://www.facturapi.io/{apiVersion}/"); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", apiKeyBase64); + } } } diff --git a/FacturapiTest/ClientCompatibilityTests.cs b/FacturapiTest/ClientCompatibilityTests.cs new file mode 100644 index 0000000..c3a167a --- /dev/null +++ b/FacturapiTest/ClientCompatibilityTests.cs @@ -0,0 +1,125 @@ +using Facturapi; +using Facturapi.Wrappers; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace FacturapiTest +{ + public class ClientCompatibilityTests + { + [Fact] + public void Router_ListCustomers_AllowsNullQueryValues() + { + var query = new Dictionary + { + ["foo"] = null!, + [""] = "ignored" + }; + + var url = Router.ListCustomers(query); + + Assert.Equal("customers?foo=", url); + } + + [Fact] + public async Task RetentionCancelAsync_ThrowsFacturapiExceptionWithStatus() + { + var handler = new StubHttpMessageHandler((request, cancellationToken) => + { + var response = new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent("{\"message\":\"bad retention\",\"status\":400}", Encoding.UTF8, "application/json") + }; + return Task.FromResult(response); + }); + + var httpClient = new HttpClient(handler) + { + BaseAddress = new Uri("https://www.facturapi.io/v2/") + }; + var wrapper = new RetentionWrapper("test_key", "v2", httpClient); + + var exception = await Assert.ThrowsAsync(() => + wrapper.CancelAsync("ret_123", cancellationToken: CancellationToken.None)); + + Assert.Equal(400, exception.Status); + Assert.Equal("bad retention", exception.Message); + } + + [Fact] + public async Task InvoiceStampDraftAsync_AndLegacyMethod_BothWork() + { + var handler = new StubHttpMessageHandler((request, cancellationToken) => + { + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{\"id\":\"inv_123\"}", Encoding.UTF8, "application/json") + }; + return Task.FromResult(response); + }); + + var httpClient = new HttpClient(handler) + { + BaseAddress = new Uri("https://www.facturapi.io/v2/") + }; + var wrapper = new InvoiceWrapper("test_key", "v2", httpClient); + + var fromAsync = await wrapper.StampDraftAsync("inv_123"); +#pragma warning disable CS0618 + var fromLegacy = await wrapper.StampDraft("inv_123"); +#pragma warning restore CS0618 + + Assert.Equal("inv_123", fromAsync.Id); + Assert.Equal("inv_123", fromLegacy.Id); + } + + [Fact] + public async Task CreateWithCustomHttpClient_DoesNotDisposeInjectedClient() + { + var handler = new StubHttpMessageHandler((request, cancellationToken) => + { + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{\"ok\":true}", Encoding.UTF8, "application/json") + }; + return Task.FromResult(response); + }); + + var injectedHttpClient = new HttpClient(handler) + { + BaseAddress = new Uri("https://www.facturapi.io/v2/") + }; + + var client = FacturapiClient.CreateWithCustomHttpClient("test_key", injectedHttpClient, "v2"); + + var healthy = await client.Tool.HealthCheckAsync(); + Assert.True(healthy); + + client.Dispose(); + + var response = await injectedHttpClient.GetAsync("check"); + Assert.True(response.IsSuccessStatusCode); + } + + private sealed class StubHttpMessageHandler : HttpMessageHandler + { + private readonly Func> handler; + + public StubHttpMessageHandler(Func> handler) + { + this.handler = handler; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return this.handler(request, cancellationToken); + } + } + } +} diff --git a/FacturapiTest/FacturapiTest.csproj b/FacturapiTest/FacturapiTest.csproj new file mode 100644 index 0000000..4f4094e --- /dev/null +++ b/FacturapiTest/FacturapiTest.csproj @@ -0,0 +1,23 @@ + + + + net6.0;net8.0 + false + enable + enable + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/FacturapiTest/WrapperBehaviorTests.cs b/FacturapiTest/WrapperBehaviorTests.cs new file mode 100644 index 0000000..29021dc --- /dev/null +++ b/FacturapiTest/WrapperBehaviorTests.cs @@ -0,0 +1,363 @@ +using Facturapi; +using Facturapi.Wrappers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace FacturapiTest +{ + public class WrapperBehaviorTests + { + [Fact] + public async Task InvoiceCreateAsync_UsesPostAndQueryString() + { + var handler = new RecordingHandler(async (request, cancellationToken) => + { + Assert.Equal(HttpMethod.Post, request.Method); + Assert.NotNull(request.RequestUri); + Assert.Equal("/v2/invoices?foo=bar", request.RequestUri.PathAndQuery); + Assert.NotNull(request.Content); + var body = await request.Content.ReadAsStringAsync().ConfigureAwait(false); + Assert.Contains("\"field\":\"value\"", body); + + return JsonResponse("{\"id\":\"inv_001\"}"); + }); + + var wrapper = new InvoiceWrapper("test_key", "v2", CreateHttpClient(handler)); + var result = await wrapper.CreateAsync( + new Dictionary { ["field"] = "value" }, + new Dictionary { ["foo"] = "bar" }); + + Assert.Equal("inv_001", result.Id); + } + + [Fact] + public async Task ReceiptCancelAsync_UsesReceiptDeleteRoute() + { + var handler = new RecordingHandler((request, cancellationToken) => + { + Assert.Equal(HttpMethod.Delete, request.Method); + Assert.NotNull(request.RequestUri); + Assert.Equal("/v2/receipts/rcp_123", request.RequestUri.PathAndQuery); + return Task.FromResult(JsonResponse("{\"id\":\"rcp_123\"}")); + }); + + var wrapper = new ReceiptWrapper("test_key", "v2", CreateHttpClient(handler)); + var result = await wrapper.CancelAsync("rcp_123"); + + Assert.Equal("rcp_123", result.Id); + } + + [Fact] + public async Task OrganizationDeleteSeriesAsync_UsesDeleteSeriesRoute() + { + var handler = new RecordingHandler((request, cancellationToken) => + { + Assert.Equal(HttpMethod.Delete, request.Method); + Assert.NotNull(request.RequestUri); + Assert.Equal("/v2/organizations/org_1/series-group/A", request.RequestUri.PathAndQuery); + return Task.FromResult(JsonResponse("{\"name\":\"A\"}")); + }); + + var wrapper = new OrganizationWrapper("test_key", "v2", CreateHttpClient(handler)); + var result = await wrapper.DeleteSeriesAsync("org_1", "A"); + + Assert.Equal("A", result.Name); + } + + [Fact] + public async Task OrganizationListTeamAccessAsync_UsesTeamRoute() + { + var handler = new RecordingHandler((request, cancellationToken) => + { + Assert.Equal(HttpMethod.Get, request.Method); + Assert.NotNull(request.RequestUri); + Assert.Equal("/v2/organizations/org_1/team", request.RequestUri.PathAndQuery); + return Task.FromResult(JsonResponse("[]")); + }); + + var wrapper = new OrganizationWrapper("test_key", "v2", CreateHttpClient(handler)); + var result = await wrapper.ListTeamAccessAsync("org_1"); + + Assert.NotNull(result); + } + + [Fact] + public async Task OrganizationInviteUserToTeamAsync_UsesInvitesRoute() + { + var handler = new RecordingHandler(async (request, cancellationToken) => + { + Assert.Equal(HttpMethod.Post, request.Method); + Assert.NotNull(request.RequestUri); + Assert.Equal("/v2/organizations/org_1/team/invites", request.RequestUri.PathAndQuery); + Assert.NotNull(request.Content); + var body = await request.Content.ReadAsStringAsync().ConfigureAwait(false); + Assert.Contains("\"email\":\"dev@example.com\"", body); + + return JsonResponse("{\"id\":\"inv_001\",\"email\":\"dev@example.com\"}"); + }); + + var wrapper = new OrganizationWrapper("test_key", "v2", CreateHttpClient(handler)); + var result = await wrapper.InviteUserToTeamAsync("org_1", new Dictionary + { + ["email"] = "dev@example.com" + }); + + Assert.Equal("inv_001", result.Id); + } + + [Fact] + public async Task OrganizationListTeamRoleOperationsAsync_UsesOperationsRoute() + { + var handler = new RecordingHandler((request, cancellationToken) => + { + Assert.Equal(HttpMethod.Get, request.Method); + Assert.NotNull(request.RequestUri); + Assert.Equal("/v2/organizations/org_1/team/roles/operations", request.RequestUri.PathAndQuery); + return Task.FromResult(JsonResponse("[\"invoice:list\"]")); + }); + + var wrapper = new OrganizationWrapper("test_key", "v2", CreateHttpClient(handler)); + var result = await wrapper.ListTeamRoleOperationsAsync("org_1"); + + Assert.Single(result); + Assert.Equal("invoice:list", result[0]); + } + + [Fact] + public async Task OrganizationRemoveTeamAccessAsync_ParsesOkResponse() + { + var handler = new RecordingHandler((request, cancellationToken) => + { + Assert.Equal(HttpMethod.Delete, request.Method); + Assert.NotNull(request.RequestUri); + Assert.Equal("/v2/organizations/org_1/team/acc_1", request.RequestUri.PathAndQuery); + return Task.FromResult(JsonResponse("{\"ok\":true}")); + }); + + var wrapper = new OrganizationWrapper("test_key", "v2", CreateHttpClient(handler)); + var result = await wrapper.RemoveTeamAccessAsync("org_1", "acc_1"); + + Assert.True(result); + } + + [Fact] + public async Task OrganizationRespondTeamInviteAsync_UsesInviteResponseRoute() + { + var handler = new RecordingHandler(async (request, cancellationToken) => + { + Assert.Equal(HttpMethod.Post, request.Method); + Assert.NotNull(request.RequestUri); + Assert.Equal("/v2/organizations/invites/inv_1/response", request.RequestUri.PathAndQuery); + Assert.NotNull(request.Content); + var body = await request.Content.ReadAsStringAsync().ConfigureAwait(false); + Assert.Contains("\"accept\":true", body); + return JsonResponse("{\"ok\":true}"); + }); + + var wrapper = new OrganizationWrapper("test_key", "v2", CreateHttpClient(handler)); + var result = await wrapper.RespondTeamInviteAsync("inv_1", new Dictionary + { + ["accept"] = true + }); + + Assert.True(result); + } + + [Fact] + public async Task OrganizationUpdateTeamRoleAsync_UsesRoleRoute() + { + var handler = new RecordingHandler(async (request, cancellationToken) => + { + Assert.Equal(HttpMethod.Put, request.Method); + Assert.NotNull(request.RequestUri); + Assert.Equal("/v2/organizations/org_1/team/roles/role_1", request.RequestUri.PathAndQuery); + Assert.NotNull(request.Content); + var body = await request.Content.ReadAsStringAsync().ConfigureAwait(false); + Assert.Contains("\"name\":\"Senior billing analyst\"", body); + return JsonResponse("{\"id\":\"role_1\",\"name\":\"Senior billing analyst\"}"); + }); + + var wrapper = new OrganizationWrapper("test_key", "v2", CreateHttpClient(handler)); + var result = await wrapper.UpdateTeamRoleAsync("org_1", "role_1", new Dictionary + { + ["name"] = "Senior billing analyst" + }); + + Assert.Equal("role_1", result.Id); + Assert.Equal("Senior billing analyst", result.Name); + } + + [Fact] + public async Task RetentionListAsync_UsesRetentionsRoute() + { + var handler = new RecordingHandler((request, cancellationToken) => + { + Assert.Equal(HttpMethod.Get, request.Method); + Assert.NotNull(request.RequestUri); + Assert.Equal("/v2/retentions?page=2", request.RequestUri.PathAndQuery); + return Task.FromResult(JsonResponse("{\"data\":[]}")); + }); + + var wrapper = new RetentionWrapper("test_key", "v2", CreateHttpClient(handler)); + var result = await wrapper.ListAsync(new Dictionary { ["page"] = 2 }); + + Assert.NotNull(result); + Assert.NotNull(result.Data); + } + + [Fact] + public async Task ErrorMapping_UsesStatusFromString() + { + var handler = new RecordingHandler((request, cancellationToken) => + { + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent("{\"message\":\"bad request\",\"status\":\"400\"}", Encoding.UTF8, "application/json") + }); + }); + + var wrapper = new CustomerWrapper("test_key", "v2", CreateHttpClient(handler)); + var exception = await Assert.ThrowsAsync(() => wrapper.ListAsync()); + + Assert.Equal(400, exception.Status); + Assert.Equal("bad request", exception.Message); + } + + [Fact] + public async Task ErrorMapping_UsesStatusFromFloat() + { + var handler = new RecordingHandler((request, cancellationToken) => + { + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent("{\"message\":\"unauthorized\",\"status\":401.0}", Encoding.UTF8, "application/json") + }); + }); + + var wrapper = new CustomerWrapper("test_key", "v2", CreateHttpClient(handler)); + var exception = await Assert.ThrowsAsync(() => wrapper.ListAsync()); + + Assert.Equal(401, exception.Status); + Assert.Equal("unauthorized", exception.Message); + } + + [Fact] + public async Task ErrorMapping_NonJsonFallbacksToHttpStatus() + { + var handler = new RecordingHandler((request, cancellationToken) => + { + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError) + { + Content = new StringContent("server exploded", Encoding.UTF8, "text/plain") + }); + }); + + var wrapper = new CustomerWrapper("test_key", "v2", CreateHttpClient(handler)); + var exception = await Assert.ThrowsAsync(() => wrapper.ListAsync()); + + Assert.Equal(500, exception.Status); + Assert.Equal("An error occurred", exception.Message); + } + + [Fact] + public async Task CustomerListAsync_RespectsCancellationToken() + { + var handler = new RecordingHandler((request, cancellationToken) => + { + return Task.FromResult(JsonResponse("{\"data\":[]}")); + }); + + var wrapper = new CustomerWrapper("test_key", "v2", CreateHttpClient(handler)); + using var cts = new CancellationTokenSource(); + cts.Cancel(); + + await Assert.ThrowsAnyAsync(() => wrapper.ListAsync(cancellationToken: cts.Token)); + } + + [Fact] + public async Task InvoiceDownloadPdfAsync_ReturnsSeekableStreamAtPositionZero() + { + var payload = Encoding.UTF8.GetBytes("pdf-bytes"); + var handler = new RecordingHandler((request, cancellationToken) => + { + Assert.Equal(HttpMethod.Get, request.Method); + Assert.NotNull(request.RequestUri); + Assert.Equal("/v2/invoices/inv_1/pdf", request.RequestUri.PathAndQuery); + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new ByteArrayContent(payload) + }); + }); + + var wrapper = new InvoiceWrapper("test_key", "v2", CreateHttpClient(handler)); + using var stream = await wrapper.DownloadPdfAsync("inv_1"); + + Assert.Equal(0, stream.Position); + using var reader = new StreamReader(stream, Encoding.UTF8, false, 1024, leaveOpen: true); + var text = await reader.ReadToEndAsync(); + Assert.Equal("pdf-bytes", text); + } + + [Fact] + public async Task RetentionDownloadZipAsync_ReturnsSeekableStreamAtPositionZero() + { + var payload = Encoding.UTF8.GetBytes("zip-content"); + var handler = new RecordingHandler((request, cancellationToken) => + { + Assert.Equal(HttpMethod.Get, request.Method); + Assert.NotNull(request.RequestUri); + Assert.Equal("/v2/retentions/ret_1/zip", request.RequestUri.PathAndQuery); + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new ByteArrayContent(payload) + }); + }); + + var wrapper = new RetentionWrapper("test_key", "v2", CreateHttpClient(handler)); + using var stream = await wrapper.DownloadZipAsync("ret_1"); + + Assert.Equal(0, stream.Position); + using var reader = new StreamReader(stream, Encoding.UTF8, false, 1024, leaveOpen: true); + var text = await reader.ReadToEndAsync(); + Assert.Equal("zip-content", text); + } + + private static HttpClient CreateHttpClient(HttpMessageHandler handler) + { + return new HttpClient(handler) + { + BaseAddress = new Uri("https://www.facturapi.io/v2/") + }; + } + + private static HttpResponseMessage JsonResponse(string json) + { + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(json, Encoding.UTF8, "application/json") + }; + } + + private sealed class RecordingHandler : HttpMessageHandler + { + private readonly Func> responder; + + public RecordingHandler(Func> responder) + { + this.responder = responder; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return this.responder(request, cancellationToken); + } + } + } +} diff --git a/IFacturapiClient.cs b/IFacturapiClient.cs index 1e0b7d9..0217b28 100644 --- a/IFacturapiClient.cs +++ b/IFacturapiClient.cs @@ -5,15 +5,15 @@ namespace Facturapi { public interface IFacturapiClient : IDisposable { - CustomerWrapper Customer { get; } - ProductWrapper Product { get; } - InvoiceWrapper Invoice { get; } - OrganizationWrapper Organization { get; } - ReceiptWrapper Receipt { get; } - RetentionWrapper Retention { get; } - CatalogWrapper Catalog { get; } - CartaporteCatalogWrapper CartaporteCatalog { get; } - ToolWrapper Tool { get; } - WebhookWrapper Webhook { get; } + ICustomerWrapper Customer { get; } + IProductWrapper Product { get; } + IInvoiceWrapper Invoice { get; } + IOrganizationWrapper Organization { get; } + IReceiptWrapper Receipt { get; } + IRetentionWrapper Retention { get; } + ICatalogWrapper Catalog { get; } + ICartaporteCatalogWrapper CartaporteCatalog { get; } + IToolWrapper Tool { get; } + IWebhookWrapper Webhook { get; } } } diff --git a/Models/OrganizationInvite.cs b/Models/OrganizationInvite.cs new file mode 100644 index 0000000..aebd23d --- /dev/null +++ b/Models/OrganizationInvite.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace Facturapi +{ + public class OrganizationInvite + { + public string Id { get; set; } + public DateTime? CreatedAt { get; set; } + public string Email { get; set; } + public string OrganizationName { get; set; } + public string Role { get; set; } + public string RoleName { get; set; } + public List Roles { get; set; } + public DateTime? ExpiresAt { get; set; } + } +} diff --git a/Models/OrganizationTeamRole.cs b/Models/OrganizationTeamRole.cs new file mode 100644 index 0000000..1f82574 --- /dev/null +++ b/Models/OrganizationTeamRole.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace Facturapi +{ + public class OrganizationTeamRole + { + public string Id { get; set; } + public string Name { get; set; } + public string TemplateCode { get; set; } + public string Scope { get; set; } + public string Organization { get; set; } + public List Operations { get; set; } + public int UsedBy { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public Dictionary CreatedBy { get; set; } + public Dictionary UpdatedBy { get; set; } + } +} diff --git a/Models/OrganizationTeamRoleTemplate.cs b/Models/OrganizationTeamRoleTemplate.cs new file mode 100644 index 0000000..3b5a662 --- /dev/null +++ b/Models/OrganizationTeamRoleTemplate.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Facturapi +{ + public class OrganizationTeamRoleTemplate + { + public string Code { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public List Operations { get; set; } + } +} diff --git a/Models/OrganizationUserAccess.cs b/Models/OrganizationUserAccess.cs new file mode 100644 index 0000000..539de74 --- /dev/null +++ b/Models/OrganizationUserAccess.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace Facturapi +{ + public class OrganizationUserAccess + { + public string Id { get; set; } + public string FullName { get; set; } + public string Email { get; set; } + public string Role { get; set; } + public string RoleName { get; set; } + public string Organization { get; set; } + public List Operations { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c66dc03 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("FacturapiTest")] diff --git a/README.md b/README.md index b958367..e8303db 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Facturapi .NET Library [![NuGet version](https://badge.fury.io/nu/Facturapi.svg)](https://www.nuget.org/packages/Facturapi/) [![NuGet downloads](https://img.shields.io/nuget/dt/Facturapi.svg)](https://www.nuget.org/packages/Facturapi/) +[![CI](https://github.com/FacturAPI/facturapi-net/actions/workflows/ci.yml/badge.svg)](https://github.com/FacturAPI/facturapi-net/actions/workflows/ci.yml) Librería oficial para .NET de https://www.facturapi.io @@ -10,6 +11,34 @@ Facturapi ayuda a generar facturas electrónicas válidas en México (CFDI) de l Si alguna vez has usado [Stripe](https://stripe.com) o [Conekta](https://conekta.io), verás que Facturapi es igual de sencillo de entender e integrar a tu aplicación. +## Migración a v6 + +### ¿Cuándo NO necesitas cambiar nada? + +No necesitas actualizar tu código si: +- Creas `FacturapiClient` y llamas métodos directamente (por ejemplo `await client.Invoice.CreateAsync(...)`). +- Usas `var` al guardar wrappers (por ejemplo `var invoices = client.Invoice;`). +- No dependes de tipos concretos de wrappers en firmas, propiedades o pruebas. + +### ¿Cuándo SÍ necesitas actualizar? + +Debes ajustar tu código si: +- Declaras wrappers como clases concretas (`CustomerWrapper`, `InvoiceWrapper`, etc.). +- Mockeas wrappers concretos en pruebas. +- Expones wrappers concretos en tus propias interfaces o APIs públicas. + +Antes (v5): + +```csharp +CustomerWrapper customers = client.Customer; +``` + +Después (v6): + +```csharp +ICustomerWrapper customers = client.Customer; +``` + ## Instalación Puedes instalar Facturapi en tu proyecto usando [Nuget](https://www.nuget.org/) @@ -30,9 +59,22 @@ Empieza por crear una instancia del Wrapper de Facturapi usando tu llave secreta using Facturapi; // Esto asegura que puedas usar diferentes ApiKeys en diferentes instancias de Wrapper -var facturapi = new FacturapiClient('TU_API_KEY'); +var facturapi = new FacturapiClient("TU_API_KEY"); // Después, procede a llamar a los métodos como muestra la documentación. -var invoice = await facturapi.Invoice.Create(...); +var invoice = await facturapi.Invoice.CreateAsync(...); +``` + +### Escenario avanzado: usar tu propio `HttpClient` + +Para la mayoría de usuarios, usa el constructor normal de `FacturapiClient`. +Si necesitas un `HttpClient` custom (por ejemplo para un `HttpMessageHandler` propio), usa el factory avanzado: + +```csharp +using System.Net.Http; +using Facturapi; + +var customHttpClient = new HttpClient(); +var facturapi = FacturapiClient.CreateWithCustomHttpClient("TU_API_KEY", customHttpClient); ``` ### Métodos asíncronos (async, await) @@ -41,10 +83,10 @@ Esta librería utiliza métodos asíncronos. Si tu aplicación no tiene código ```csharp // Asíncrono -var customers = await facturapi.Customer.List(); +var customers = await facturapi.Customer.ListAsync(); // Síncrono -var customers = facturapi.Customer.List().GetAwaiter().GetResult(); +var customers = facturapi.Customer.ListAsync().GetAwaiter().GetResult(); ``` ## Uso de la librería @@ -104,7 +146,7 @@ var product = await facturapi.Product.CreateAsync(new Dictionary ### Crear una factura ```csharp -var invoice = await facturapi.Product.CreateAsync(new Dictionary +var invoice = await facturapi.Invoice.CreateAsync(new Dictionary { ["customer"] = "ID_DEL_CLIENTE", // Para clientes no registrados, puedes asignar // un Dictionary con los datos del cliente. @@ -116,7 +158,7 @@ var invoice = await facturapi.Product.CreateAsync(new Dictionary ["product"] = "ID_DEL_PRODUCTO" // Para productos no registrados, puedes asignar // un Dictionary con los datos del producto. } - } + }, ["payment_form"] = Facturapi.PaymentForm.DINERO_ELECTRONICO }); ``` @@ -131,9 +173,8 @@ var zipStream = await facturapi.Invoice.DownloadZipAsync(invoice.Id); var xmlStream = await facturapi.Invoice.DownloadXmlAsync(invoice.Id); var pdfStream = await facturapi.Invoice.DownloadPdfAsync(invoice.Id); // Y luego guardarlo en un archivo del disco duro -var file = new System.IO.FileStrem("C:\\route\\to\\save\\invoice.zip", FileMode.Create); -zipStream.CopyTo(file); -file.Close(); +using var file = new System.IO.FileStream("C:\\route\\to\\save\\invoice.zip", System.IO.FileMode.Create); +await zipStream.CopyToAsync(file); ``` #### Envía la factura por correo electrónico diff --git a/Router/OrganizationRouter.cs b/Router/OrganizationRouter.cs index af45881..2f7b533 100644 --- a/Router/OrganizationRouter.cs +++ b/Router/OrganizationRouter.cs @@ -120,5 +120,60 @@ public static string UpdateDomain(string organizationId) { return $"{RetrieveOrganization(organizationId)}/domain"; } + + public static string ListTeamAccess(string organizationId) + { + return $"{RetrieveOrganization(organizationId)}/team"; + } + + public static string RetrieveTeamAccess(string organizationId, string accessId) + { + return $"{ListTeamAccess(organizationId)}/{accessId}"; + } + + public static string UpdateTeamAccessRole(string organizationId, string accessId) + { + return $"{RetrieveTeamAccess(organizationId, accessId)}/role"; + } + + public static string ListSentTeamInvites(string organizationId) + { + return $"{ListTeamAccess(organizationId)}/invites"; + } + + public static string CancelTeamInvite(string organizationId, string inviteKey) + { + return $"{ListSentTeamInvites(organizationId)}/{inviteKey}"; + } + + public static string ListReceivedTeamInvites() + { + return "organizations/invites/pending"; + } + + public static string RespondTeamInvite(string inviteKey) + { + return $"organizations/invites/{inviteKey}/response"; + } + + public static string ListTeamRoles(string organizationId) + { + return $"{ListTeamAccess(organizationId)}/roles"; + } + + public static string ListTeamRoleTemplates(string organizationId) + { + return $"{ListTeamRoles(organizationId)}/templates"; + } + + public static string ListTeamRoleOperations(string organizationId) + { + return $"{ListTeamRoles(organizationId)}/operations"; + } + + public static string RetrieveTeamRole(string organizationId, string roleId) + { + return $"{ListTeamRoles(organizationId)}/{roleId}"; + } } } diff --git a/Router/RetentionRouter.cs b/Router/RetentionRouter.cs index 1df011f..4f655ee 100644 --- a/Router/RetentionRouter.cs +++ b/Router/RetentionRouter.cs @@ -8,7 +8,7 @@ namespace Facturapi { internal static partial class Router { - public static string ListRetentionss(Dictionary query = null) + public static string ListRetentions(Dictionary query = null) { return UriWithQuery("retentions", query); } diff --git a/Router/Router.cs b/Router/Router.cs index 1f3a674..06228eb 100644 --- a/Router/Router.cs +++ b/Router/Router.cs @@ -8,20 +8,30 @@ internal static partial class Router { private static string UriWithQuery(string path, Dictionary query = null) { - var url = path; - if (query != null) + if (query == null || query.Count == 0) { - return $"{path}?{DictionaryToQueryString(query)}"; + return path; } - else + + var queryString = DictionaryToQueryString(query); + if (String.IsNullOrEmpty(queryString)) { return path; } + + return $"{path}?{queryString}"; } private static string DictionaryToQueryString(Dictionary dict) { - return String.Join("&", dict.Select(x => String.Format("{0}={1}", Uri.EscapeDataString(x.Key), Uri.EscapeDataString(x.Value.ToString())))); + return String.Join( + "&", + dict + .Where(x => !String.IsNullOrEmpty(x.Key)) + .Select(x => String.Format( + "{0}={1}", + Uri.EscapeDataString(x.Key), + Uri.EscapeDataString(x.Value?.ToString() ?? String.Empty)))); } } } diff --git a/Wrappers/BaseWrapper.cs b/Wrappers/BaseWrapper.cs index 9f0a28e..88992d1 100644 --- a/Wrappers/BaseWrapper.cs +++ b/Wrappers/BaseWrapper.cs @@ -79,7 +79,7 @@ protected async Task ThrowIfErrorAsync(HttpResponseMessage response, Cancellatio return; } - var resultString = await response.Content.ReadAsStringAsync(); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); throw CreateException(resultString, response); } } diff --git a/Wrappers/CartaporteCatalogWrapper.cs b/Wrappers/CartaporteCatalogWrapper.cs index a519d7d..f354a4c 100644 --- a/Wrappers/CartaporteCatalogWrapper.cs +++ b/Wrappers/CartaporteCatalogWrapper.cs @@ -6,7 +6,7 @@ namespace Facturapi.Wrappers { - public class CartaporteCatalogWrapper : BaseWrapper + public class CartaporteCatalogWrapper : BaseWrapper, ICartaporteCatalogWrapper { internal CartaporteCatalogWrapper(string apiKey, string apiVersion, HttpClient httpClient) : base(apiKey, apiVersion, httpClient) { @@ -14,60 +14,60 @@ internal CartaporteCatalogWrapper(string apiKey, string apiVersion, HttpClient h public async Task> SearchAirTransportCodes(Dictionary query = null, CancellationToken cancellationToken = default) { - return await this.SearchCatalogAsync(Router.SearchCartaporteAirTransportCodes(query), cancellationToken); + return await this.SearchCatalogAsync(Router.SearchCartaporteAirTransportCodes(query), cancellationToken).ConfigureAwait(false); } public async Task> SearchTransportConfigs(Dictionary query = null, CancellationToken cancellationToken = default) { - return await this.SearchCatalogAsync(Router.SearchCartaporteTransportConfigs(query), cancellationToken); + return await this.SearchCatalogAsync(Router.SearchCartaporteTransportConfigs(query), cancellationToken).ConfigureAwait(false); } public async Task> SearchRightsOfPassage(Dictionary query = null, CancellationToken cancellationToken = default) { - return await this.SearchCatalogAsync(Router.SearchCartaporteRightsOfPassage(query), cancellationToken); + return await this.SearchCatalogAsync(Router.SearchCartaporteRightsOfPassage(query), cancellationToken).ConfigureAwait(false); } public async Task> SearchCustomsDocuments(Dictionary query = null, CancellationToken cancellationToken = default) { - return await this.SearchCatalogAsync(Router.SearchCartaporteCustomsDocuments(query), cancellationToken); + return await this.SearchCatalogAsync(Router.SearchCartaporteCustomsDocuments(query), cancellationToken).ConfigureAwait(false); } public async Task> SearchPackagingTypes(Dictionary query = null, CancellationToken cancellationToken = default) { - return await this.SearchCatalogAsync(Router.SearchCartaportePackagingTypes(query), cancellationToken); + return await this.SearchCatalogAsync(Router.SearchCartaportePackagingTypes(query), cancellationToken).ConfigureAwait(false); } public async Task> SearchTrailerTypes(Dictionary query = null, CancellationToken cancellationToken = default) { - return await this.SearchCatalogAsync(Router.SearchCartaporteTrailerTypes(query), cancellationToken); + return await this.SearchCatalogAsync(Router.SearchCartaporteTrailerTypes(query), cancellationToken).ConfigureAwait(false); } public async Task> SearchHazardousMaterials(Dictionary query = null, CancellationToken cancellationToken = default) { - return await this.SearchCatalogAsync(Router.SearchCartaporteHazardousMaterials(query), cancellationToken); + return await this.SearchCatalogAsync(Router.SearchCartaporteHazardousMaterials(query), cancellationToken).ConfigureAwait(false); } public async Task> SearchNavalAuthorizations(Dictionary query = null, CancellationToken cancellationToken = default) { - return await this.SearchCatalogAsync(Router.SearchCartaporteNavalAuthorizations(query), cancellationToken); + return await this.SearchCatalogAsync(Router.SearchCartaporteNavalAuthorizations(query), cancellationToken).ConfigureAwait(false); } public async Task> SearchPortStations(Dictionary query = null, CancellationToken cancellationToken = default) { - return await this.SearchCatalogAsync(Router.SearchCartaportePortStations(query), cancellationToken); + return await this.SearchCatalogAsync(Router.SearchCartaportePortStations(query), cancellationToken).ConfigureAwait(false); } public async Task> SearchMarineContainers(Dictionary query = null, CancellationToken cancellationToken = default) { - return await this.SearchCatalogAsync(Router.SearchCartaporteMarineContainers(query), cancellationToken); + return await this.SearchCatalogAsync(Router.SearchCartaporteMarineContainers(query), cancellationToken).ConfigureAwait(false); } private async Task> SearchCatalogAsync(string url, CancellationToken cancellationToken) { - using (var response = await client.GetAsync(url, cancellationToken)) + using (var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var searchResult = JsonConvert.DeserializeObject>(resultString, this.jsonSettings); return searchResult; } diff --git a/Wrappers/CatalogWrapper.cs b/Wrappers/CatalogWrapper.cs index 1c13407..ed21955 100644 --- a/Wrappers/CatalogWrapper.cs +++ b/Wrappers/CatalogWrapper.cs @@ -6,7 +6,7 @@ namespace Facturapi.Wrappers { - public class CatalogWrapper : BaseWrapper + public class CatalogWrapper : BaseWrapper, ICatalogWrapper { internal CatalogWrapper(string apiKey, string apiVersion, HttpClient httpClient) : base(apiKey, apiVersion, httpClient) { @@ -14,20 +14,20 @@ internal CatalogWrapper(string apiKey, string apiVersion, HttpClient httpClient) public async Task> SearchProducts(Dictionary query = null, CancellationToken cancellationToken = default) { - return await this.SearchCatalogAsync(Router.SearchProductKeys(query), cancellationToken); + return await this.SearchCatalogAsync(Router.SearchProductKeys(query), cancellationToken).ConfigureAwait(false); } public async Task> SearchUnits(Dictionary query = null, CancellationToken cancellationToken = default) { - return await this.SearchCatalogAsync(Router.SearchUnitKeys(query), cancellationToken); + return await this.SearchCatalogAsync(Router.SearchUnitKeys(query), cancellationToken).ConfigureAwait(false); } private async Task> SearchCatalogAsync(string url, CancellationToken cancellationToken) { - using (var response = await client.GetAsync(url, cancellationToken)) + using (var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var searchResult = JsonConvert.DeserializeObject>(resultString, this.jsonSettings); return searchResult; } diff --git a/Wrappers/CustomerWrapper.cs b/Wrappers/CustomerWrapper.cs index 60f2292..ab7e64a 100644 --- a/Wrappers/CustomerWrapper.cs +++ b/Wrappers/CustomerWrapper.cs @@ -7,7 +7,7 @@ namespace Facturapi.Wrappers { - public class CustomerWrapper : BaseWrapper + public class CustomerWrapper : BaseWrapper, ICustomerWrapper { internal CustomerWrapper(string apiKey, string apiVersion, HttpClient httpClient) : base(apiKey, apiVersion, httpClient) { @@ -15,10 +15,10 @@ internal CustomerWrapper(string apiKey, string apiVersion, HttpClient httpClient public async Task> ListAsync(Dictionary query = null, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.ListCustomers(query), cancellationToken)) + using (var response = await client.GetAsync(Router.ListCustomers(query), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var searchResult = JsonConvert.DeserializeObject>(resultString, this.jsonSettings); return searchResult; @@ -28,10 +28,10 @@ public async Task> ListAsync(Dictionary q public async Task CreateAsync(Dictionary data, Dictionary queryParams = null, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.CreateCustomer(queryParams), content, cancellationToken)) + using (var response = await client.PostAsync(Router.CreateCustomer(queryParams), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var customer = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return customer; } @@ -39,10 +39,10 @@ public async Task CreateAsync(Dictionary data, Diction public async Task RetrieveAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.RetrieveCustomer(id), cancellationToken)) + using (var response = await client.GetAsync(Router.RetrieveCustomer(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var customer = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return customer; } @@ -50,10 +50,10 @@ public async Task RetrieveAsync(string id, CancellationToken cancellat public async Task DeleteAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.DeleteAsync(Router.DeleteCustomer(id), cancellationToken)) + using (var response = await client.DeleteAsync(Router.DeleteCustomer(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var customer = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return customer; } @@ -62,10 +62,10 @@ public async Task DeleteAsync(string id, CancellationToken cancellatio public async Task UpdateAsync(string id, Dictionary data, Dictionary queryParams = null, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PutAsync(Router.UpdateCustomer(id, queryParams), content, cancellationToken)) + using (var response = await client.PutAsync(Router.UpdateCustomer(id, queryParams), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var customer = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return customer; } @@ -73,10 +73,10 @@ public async Task UpdateAsync(string id, Dictionary da public async Task ValidateTaxInfoAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.ValidateCustomerTaxInfo(id), cancellationToken)) + using (var response = await client.GetAsync(Router.ValidateCustomerTaxInfo(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var validation = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return validation; } @@ -85,9 +85,9 @@ public async Task ValidateTaxInfoAsync(string id, Cancellatio public async Task SendEditLinkByEmailAsync(string id, Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.SendEditLinkByEmail(id), content, cancellationToken)) + using (var response = await client.PostAsync(Router.SendEditLinkByEmail(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); } } } diff --git a/Wrappers/ICartaporteCatalogWrapper.cs b/Wrappers/ICartaporteCatalogWrapper.cs new file mode 100644 index 0000000..3beb615 --- /dev/null +++ b/Wrappers/ICartaporteCatalogWrapper.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Facturapi.Wrappers +{ + public interface ICartaporteCatalogWrapper + { + Task> SearchAirTransportCodes(Dictionary query = null, CancellationToken cancellationToken = default); + Task> SearchTransportConfigs(Dictionary query = null, CancellationToken cancellationToken = default); + Task> SearchRightsOfPassage(Dictionary query = null, CancellationToken cancellationToken = default); + Task> SearchCustomsDocuments(Dictionary query = null, CancellationToken cancellationToken = default); + Task> SearchPackagingTypes(Dictionary query = null, CancellationToken cancellationToken = default); + Task> SearchTrailerTypes(Dictionary query = null, CancellationToken cancellationToken = default); + Task> SearchHazardousMaterials(Dictionary query = null, CancellationToken cancellationToken = default); + Task> SearchNavalAuthorizations(Dictionary query = null, CancellationToken cancellationToken = default); + Task> SearchPortStations(Dictionary query = null, CancellationToken cancellationToken = default); + Task> SearchMarineContainers(Dictionary query = null, CancellationToken cancellationToken = default); + } +} diff --git a/Wrappers/ICatalogWrapper.cs b/Wrappers/ICatalogWrapper.cs new file mode 100644 index 0000000..80a41b3 --- /dev/null +++ b/Wrappers/ICatalogWrapper.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Facturapi.Wrappers +{ + public interface ICatalogWrapper + { + Task> SearchProducts(Dictionary query = null, CancellationToken cancellationToken = default); + Task> SearchUnits(Dictionary query = null, CancellationToken cancellationToken = default); + } +} diff --git a/Wrappers/ICustomerWrapper.cs b/Wrappers/ICustomerWrapper.cs new file mode 100644 index 0000000..9006fd1 --- /dev/null +++ b/Wrappers/ICustomerWrapper.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Facturapi.Wrappers +{ + public interface ICustomerWrapper + { + Task> ListAsync(Dictionary query = null, CancellationToken cancellationToken = default); + Task CreateAsync(Dictionary data, Dictionary queryParams = null, CancellationToken cancellationToken = default); + Task RetrieveAsync(string id, CancellationToken cancellationToken = default); + Task DeleteAsync(string id, CancellationToken cancellationToken = default); + Task UpdateAsync(string id, Dictionary data, Dictionary queryParams = null, CancellationToken cancellationToken = default); + Task ValidateTaxInfoAsync(string id, CancellationToken cancellationToken = default); + Task SendEditLinkByEmailAsync(string id, Dictionary data, CancellationToken cancellationToken = default); + } +} diff --git a/Wrappers/IInvoiceWrapper.cs b/Wrappers/IInvoiceWrapper.cs new file mode 100644 index 0000000..c38b2aa --- /dev/null +++ b/Wrappers/IInvoiceWrapper.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Facturapi.Wrappers +{ + public interface IInvoiceWrapper + { + Task> ListAsync(Dictionary query = null, CancellationToken cancellationToken = default); + Task CreateAsync(Dictionary data, Dictionary options = null, CancellationToken cancellationToken = default); + Task RetrieveAsync(string id, CancellationToken cancellationToken = default); + Task CancelAsync(string id, Dictionary query = null, CancellationToken cancellationToken = default); + Task SendByEmailAsync(string id, Dictionary data = null, CancellationToken cancellationToken = default); + Task DownloadZipAsync(string id, CancellationToken cancellationToken = default); + Task DownloadPdfAsync(string id, CancellationToken cancellationToken = default); + Task DownloadXmlAsync(string id, CancellationToken cancellationToken = default); + Task DownloadCancellationReceiptXmlAsync(string id, CancellationToken cancellationToken = default); + Task DownloadCancellationReceiptPdfAsync(string id, CancellationToken cancellationToken = default); + Task UpdateStatusAsync(string id, CancellationToken cancellationToken = default); + Task UpdateDraftAsync(string id, Dictionary data, CancellationToken cancellationToken = default); + Task StampDraftAsync(string id, Dictionary options = null, CancellationToken cancellationToken = default); + [Obsolete("Use StampDraftAsync instead.")] + Task StampDraft(string id, Dictionary options = null, CancellationToken cancellationToken = default); + Task CopyToDraftAsync(string id, CancellationToken cancellationToken = default); + Task PreviewPdfAsync(Dictionary data, CancellationToken cancellationToken = default); + } +} diff --git a/Wrappers/IOrganizationWrapper.cs b/Wrappers/IOrganizationWrapper.cs new file mode 100644 index 0000000..b3a9705 --- /dev/null +++ b/Wrappers/IOrganizationWrapper.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Facturapi.Wrappers +{ + public interface IOrganizationWrapper + { + Task> ListAsync(Dictionary query = null, CancellationToken cancellationToken = default); + Task MeAsync(CancellationToken cancellationToken = default); + Task CheckDomainIsAvailableAsync(string domain, CancellationToken cancellationToken = default); + Task CreateAsync(Dictionary data, CancellationToken cancellationToken = default); + Task RetrieveAsync(string id, CancellationToken cancellationToken = default); + Task DeleteAsync(string id, CancellationToken cancellationToken = default); + Task UploadLogoAsync(string id, Stream file, CancellationToken cancellationToken = default); + Task UploadCertificateAsync(string id, Stream cerFile, Stream keyFile, string password, CancellationToken cancellationToken = default); + Task DeleteCertificateAsync(string id, CancellationToken cancellationToken = default); + Task UpdateLegalAsync(string id, Dictionary data, CancellationToken cancellationToken = default); + Task UpdateReceiptSettingsAsync(string id, Dictionary data, CancellationToken cancellationToken = default); + Task UpdateCustomizationAsync(string id, Dictionary data, CancellationToken cancellationToken = default); + Task UpdateDomainAsync(string id, Dictionary data, CancellationToken cancellationToken = default); + Task GetTestApiKeyAsync(string id, CancellationToken cancellationToken = default); + Task RenewTestApiKeyAsync(string id, CancellationToken cancellationToken = default); + Task ListLiveApiKeysAsync(string id, CancellationToken cancellationToken = default); + Task RenewLiveApiKeyAsync(string id, CancellationToken cancellationToken = default); + Task> ListSeriesAsync(string id, CancellationToken cancellationToken = default); + Task CreateSeriesAsync(string id, Dictionary data, CancellationToken cancellationToken = default); + Task UpdateSeriesAsync(string id, string seriesName, Dictionary data, CancellationToken cancellationToken = default); + Task DeleteSeriesAsync(string id, string seriesName, CancellationToken cancellationToken = default); + Task> DeleteLiveApiKeyAsync(string id, string apiKeyId, CancellationToken cancellationToken = default); + Task UpdateSelfInvoiceSettingsAsync(string organizationId, Dictionary data, CancellationToken cancellationToken = default); + Task> ListTeamAccessAsync(string organizationId, CancellationToken cancellationToken = default); + Task RetrieveTeamAccessAsync(string organizationId, string accessId, CancellationToken cancellationToken = default); + Task UpdateTeamAccessRoleAsync(string organizationId, string accessId, string role, CancellationToken cancellationToken = default); + Task RemoveTeamAccessAsync(string organizationId, string accessId, CancellationToken cancellationToken = default); + Task> ListSentTeamInvitesAsync(string organizationId, CancellationToken cancellationToken = default); + Task InviteUserToTeamAsync(string organizationId, Dictionary data, CancellationToken cancellationToken = default); + Task CancelTeamInviteAsync(string organizationId, string inviteKey, CancellationToken cancellationToken = default); + Task> ListReceivedTeamInvitesAsync(CancellationToken cancellationToken = default); + Task RespondTeamInviteAsync(string inviteKey, Dictionary data, CancellationToken cancellationToken = default); + Task> ListTeamRolesAsync(string organizationId, CancellationToken cancellationToken = default); + Task> ListTeamRoleTemplatesAsync(string organizationId, CancellationToken cancellationToken = default); + Task> ListTeamRoleOperationsAsync(string organizationId, CancellationToken cancellationToken = default); + Task RetrieveTeamRoleAsync(string organizationId, string roleId, CancellationToken cancellationToken = default); + Task CreateTeamRoleAsync(string organizationId, Dictionary data, CancellationToken cancellationToken = default); + Task UpdateTeamRoleAsync(string organizationId, string roleId, Dictionary data, CancellationToken cancellationToken = default); + Task DeleteTeamRoleAsync(string organizationId, string roleId, CancellationToken cancellationToken = default); + } +} diff --git a/Wrappers/IProductWrapper.cs b/Wrappers/IProductWrapper.cs new file mode 100644 index 0000000..006a19a --- /dev/null +++ b/Wrappers/IProductWrapper.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Facturapi.Wrappers +{ + public interface IProductWrapper + { + Task> ListAsync(Dictionary query = null, CancellationToken cancellationToken = default); + Task CreateAsync(Dictionary data, CancellationToken cancellationToken = default); + Task RetrieveAsync(string id, CancellationToken cancellationToken = default); + Task DeleteAsync(string id, CancellationToken cancellationToken = default); + Task UpdateAsync(string id, Dictionary data, CancellationToken cancellationToken = default); + } +} diff --git a/Wrappers/IReceiptWrapper.cs b/Wrappers/IReceiptWrapper.cs new file mode 100644 index 0000000..022c023 --- /dev/null +++ b/Wrappers/IReceiptWrapper.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Facturapi.Wrappers +{ + public interface IReceiptWrapper + { + Task> ListAsync(Dictionary query = null, CancellationToken cancellationToken = default); + Task CreateAsync(Dictionary data, CancellationToken cancellationToken = default); + Task RetrieveAsync(string id, CancellationToken cancellationToken = default); + Task CancelAsync(string id, CancellationToken cancellationToken = default); + Task InvoiceAsync(string id, Dictionary data, CancellationToken cancellationToken = default); + Task CreateGlobalInvoiceAsync(Dictionary data, CancellationToken cancellationToken = default); + Task SendByEmailAsync(string id, Dictionary data = null, CancellationToken cancellationToken = default); + Task DownloadPdfAsync(string id, CancellationToken cancellationToken = default); + } +} diff --git a/Wrappers/IRetentionWrapper.cs b/Wrappers/IRetentionWrapper.cs new file mode 100644 index 0000000..9359c4d --- /dev/null +++ b/Wrappers/IRetentionWrapper.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Facturapi.Wrappers +{ + public interface IRetentionWrapper + { + Task> ListAsync(Dictionary query = null, CancellationToken cancellationToken = default); + Task CreateAsync(Dictionary data, CancellationToken cancellationToken = default); + Task RetrieveAsync(string id, CancellationToken cancellationToken = default); + Task CancelAsync(string id, Dictionary query = null, CancellationToken cancellationToken = default); + Task SendByEmailAsync(string id, Dictionary data = null, CancellationToken cancellationToken = default); + Task DownloadZipAsync(string id, CancellationToken cancellationToken = default); + Task DownloadPdfAsync(string id, CancellationToken cancellationToken = default); + Task DownloadXmlAsync(string id, CancellationToken cancellationToken = default); + } +} diff --git a/Wrappers/IToolWrapper.cs b/Wrappers/IToolWrapper.cs new file mode 100644 index 0000000..5ada938 --- /dev/null +++ b/Wrappers/IToolWrapper.cs @@ -0,0 +1,11 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Facturapi.Wrappers +{ + public interface IToolWrapper + { + Task ValidateTaxIdAsync(string taxId, CancellationToken cancellationToken = default); + Task HealthCheckAsync(CancellationToken cancellationToken = default); + } +} diff --git a/Wrappers/IWebhookWrapper.cs b/Wrappers/IWebhookWrapper.cs new file mode 100644 index 0000000..83db0c0 --- /dev/null +++ b/Wrappers/IWebhookWrapper.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Facturapi.Wrappers +{ + public interface IWebhookWrapper + { + Task> ListAsync(Dictionary query = null, CancellationToken cancellationToken = default); + Task CreateAsync(Dictionary data, CancellationToken cancellationToken = default); + Task RetrieveAsync(string id, CancellationToken cancellationToken = default); + Task UpdateAsync(string id, Dictionary data, CancellationToken cancellationToken = default); + Task DeleteAsync(string id, CancellationToken cancellationToken = default); + Task ValidateSignatureAsync(Dictionary data, CancellationToken cancellationToken = default); + } +} diff --git a/Wrappers/InvoiceWrapper.cs b/Wrappers/InvoiceWrapper.cs index 87f1153..b9c891d 100644 --- a/Wrappers/InvoiceWrapper.cs +++ b/Wrappers/InvoiceWrapper.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System; +using Newtonsoft.Json; using System.Collections.Generic; using System.IO; using System.Net.Http; @@ -8,7 +9,7 @@ namespace Facturapi.Wrappers { - public class InvoiceWrapper : BaseWrapper + public class InvoiceWrapper : BaseWrapper, IInvoiceWrapper { internal InvoiceWrapper(string apiKey, string apiVersion, HttpClient httpClient) : base(apiKey, apiVersion, httpClient) { @@ -16,10 +17,10 @@ internal InvoiceWrapper(string apiKey, string apiVersion, HttpClient httpClient) public async Task> ListAsync(Dictionary query = null, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.ListInvoices(query), cancellationToken)) + using (var response = await client.GetAsync(Router.ListInvoices(query), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var searchResult = JsonConvert.DeserializeObject>(resultString, this.jsonSettings); return searchResult; @@ -29,10 +30,10 @@ public async Task> ListAsync(Dictionary qu public async Task CreateAsync(Dictionary data, Dictionary options = null, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.CreateInvoice(options), content, cancellationToken)) + using (var response = await client.PostAsync(Router.CreateInvoice(options), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var invoice = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return invoice; } @@ -40,10 +41,10 @@ public async Task CreateAsync(Dictionary data, Dictiona public async Task RetrieveAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.RetrieveInvoice(id), cancellationToken)) + using (var response = await client.GetAsync(Router.RetrieveInvoice(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var invoice = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return invoice; } @@ -51,10 +52,10 @@ public async Task RetrieveAsync(string id, CancellationToken cancellati public async Task CancelAsync(string id, Dictionary query = null, CancellationToken cancellationToken = default) { - using (var response = await client.DeleteAsync(Router.CancelInvoice(id, query), cancellationToken)) + using (var response = await client.DeleteAsync(Router.CancelInvoice(id, query), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var invoice = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return invoice; } @@ -63,20 +64,20 @@ public async Task CancelAsync(string id, Dictionary que public async Task SendByEmailAsync(string id, Dictionary data = null, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.SendByEmail(id), content, cancellationToken)) + using (var response = await client.PostAsync(Router.SendByEmail(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); } } private async Task DownloadAsync(string id, string format, CancellationToken cancellationToken) { - using (var response = await client.GetAsync(Router.DownloadInvoice(id, format), cancellationToken)) + using (var response = await client.GetAsync(Router.DownloadInvoice(id, format), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var responseStream = await response.Content.ReadAsStreamAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var memory = new MemoryStream(); - await responseStream.CopyToAsync(memory, 81920, cancellationToken); + await responseStream.CopyToAsync(memory, 81920, cancellationToken).ConfigureAwait(false); memory.Position = 0; return memory; } @@ -99,12 +100,12 @@ public Task DownloadXmlAsync(string id, CancellationToken cancellationTo private async Task DownloadCancellationReceiptAsync(string id, string format, CancellationToken cancellationToken) { - using (var response = await client.GetAsync(Router.DownloadCancellationReceipt(id, format), cancellationToken)) + using (var response = await client.GetAsync(Router.DownloadCancellationReceipt(id, format), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var responseStream = await response.Content.ReadAsStreamAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var memory = new MemoryStream(); - await responseStream.CopyToAsync(memory, 81920, cancellationToken); + await responseStream.CopyToAsync(memory, 81920, cancellationToken).ConfigureAwait(false); memory.Position = 0; return memory; } @@ -123,10 +124,10 @@ public Task DownloadCancellationReceiptPdfAsync(string id, CancellationT public async Task UpdateStatusAsync(string id, CancellationToken cancellationToken = default) { using (var content = new StringContent("", Encoding.UTF8, "application/json")) - using (var response = await client.PutAsync(Router.UpdateStatus(id), content, cancellationToken)) + using (var response = await client.PutAsync(Router.UpdateStatus(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var invoice = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return invoice; } @@ -135,34 +136,40 @@ public async Task UpdateStatusAsync(string id, CancellationToken cancel public async Task UpdateDraftAsync(string id, Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PutAsync(Router.UpdateDraftInvoice(id), content, cancellationToken)) + using (var response = await client.PutAsync(Router.UpdateDraftInvoice(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var invoice = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return invoice; } } - public async Task StampDraft(string id, Dictionary options = null, CancellationToken cancellationToken = default) + public async Task StampDraftAsync(string id, Dictionary options = null, CancellationToken cancellationToken = default) { using (var content = new StringContent("", Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.StampDraftInvoice(id, options), content, cancellationToken)) + using (var response = await client.PostAsync(Router.StampDraftInvoice(id, options), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var invoice = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return invoice; } } + [Obsolete("Use StampDraftAsync instead.")] + public Task StampDraft(string id, Dictionary options = null, CancellationToken cancellationToken = default) + { + return this.StampDraftAsync(id, options, cancellationToken); + } + public async Task CopyToDraftAsync(string id, CancellationToken cancellationToken = default) { using (var content = new StringContent("", Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.CopyInvoice(id), content, cancellationToken)) + using (var response = await client.PostAsync(Router.CopyInvoice(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var invoice = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return invoice; } @@ -171,12 +178,12 @@ public async Task CopyToDraftAsync(string id, CancellationToken cancell public async Task PreviewPdfAsync(Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.PreviewPdf(), content, cancellationToken)) + using (var response = await client.PostAsync(Router.PreviewPdf(), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var responseStream = await response.Content.ReadAsStreamAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var memory = new MemoryStream(); - await responseStream.CopyToAsync(memory, 81920, cancellationToken); + await responseStream.CopyToAsync(memory, 81920, cancellationToken).ConfigureAwait(false); memory.Position = 0; return memory; } diff --git a/Wrappers/OrganizationWrapper.cs b/Wrappers/OrganizationWrapper.cs index 33901a9..83ac9c8 100644 --- a/Wrappers/OrganizationWrapper.cs +++ b/Wrappers/OrganizationWrapper.cs @@ -8,7 +8,7 @@ namespace Facturapi.Wrappers { - public class OrganizationWrapper : BaseWrapper + public class OrganizationWrapper : BaseWrapper, IOrganizationWrapper { internal OrganizationWrapper(string apiKey, string apiVersion, HttpClient httpClient) : base(apiKey, apiVersion, httpClient) { @@ -16,10 +16,10 @@ internal OrganizationWrapper(string apiKey, string apiVersion, HttpClient httpCl public async Task> ListAsync(Dictionary query = null, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.ListOrganizations(query), cancellationToken)) + using (var response = await client.GetAsync(Router.ListOrganizations(query), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var searchResult = JsonConvert.DeserializeObject>(resultString, this.jsonSettings); return searchResult; @@ -28,10 +28,10 @@ public async Task> ListAsync(Dictionary MeAsync(CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.OrganizationMe(), cancellationToken)) + using (var response = await client.GetAsync(Router.OrganizationMe(), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var organization = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return organization; } @@ -39,10 +39,10 @@ public async Task MeAsync(CancellationToken cancellationToken = de public async Task CheckDomainIsAvailableAsync(string domain, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.CheckDomainAvailability(new Dictionary { ["domain"] = domain }), cancellationToken)) + using (var response = await client.GetAsync(Router.CheckDomainAvailability(new Dictionary { ["domain"] = domain }), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var availability = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return availability; } @@ -51,10 +51,10 @@ public async Task CheckDomainIsAvailableAsync(string domain, public async Task CreateAsync(Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.CreateOrganization(), content, cancellationToken)) + using (var response = await client.PostAsync(Router.CreateOrganization(), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var organization = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return organization; } @@ -62,10 +62,10 @@ public async Task CreateAsync(Dictionary data, Can public async Task RetrieveAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.RetrieveOrganization(id), cancellationToken)) + using (var response = await client.GetAsync(Router.RetrieveOrganization(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var organization = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return organization; } @@ -73,10 +73,10 @@ public async Task RetrieveAsync(string id, CancellationToken cance public async Task DeleteAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.DeleteAsync(Router.DeleteOrganization(id), cancellationToken)) + using (var response = await client.DeleteAsync(Router.DeleteOrganization(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var organization = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return organization; } @@ -87,10 +87,10 @@ public async Task UploadLogoAsync(string id, Stream file, Cancella using (var form = new MultipartFormDataContent()) { form.Add(new StreamContent(file), "file", "logo.jpg"); - using (var response = await client.PutAsync(Router.UploadLogo(id), form, cancellationToken)) + using (var response = await client.PutAsync(Router.UploadLogo(id), form, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var organization = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return organization; } @@ -104,10 +104,10 @@ public async Task UploadCertificateAsync(string id, Stream cerFile form.Add(new StreamContent(cerFile), "cer", "certificate.cer"); form.Add(new StreamContent(keyFile), "key", "key.key"); form.Add(new StringContent(password), "password"); - using (var response = await client.PutAsync(Router.UploadCertificate(id), form, cancellationToken)) + using (var response = await client.PutAsync(Router.UploadCertificate(id), form, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var organization = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return organization; } @@ -116,10 +116,10 @@ public async Task UploadCertificateAsync(string id, Stream cerFile public async Task DeleteCertificateAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.DeleteAsync(Router.DeleteCertificate(id), cancellationToken)) + using (var response = await client.DeleteAsync(Router.DeleteCertificate(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var certificateInfo = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return certificateInfo; } @@ -128,10 +128,10 @@ public async Task DeleteCertificateAsync(string id, CancellationTok public async Task UpdateLegalAsync(string id, Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PutAsync(Router.UpdateLegal(id), content, cancellationToken)) + using (var response = await client.PutAsync(Router.UpdateLegal(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var organization = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return organization; } @@ -140,10 +140,10 @@ public async Task UpdateLegalAsync(string id, Dictionary UpdateReceiptSettingsAsync(string id, Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PutAsync(Router.UpdateReceipts(id), content, cancellationToken)) + using (var response = await client.PutAsync(Router.UpdateReceipts(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var organization = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return organization; } @@ -152,10 +152,10 @@ public async Task UpdateReceiptSettingsAsync(string id, Dictionary public async Task UpdateCustomizationAsync(string id, Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PutAsync(Router.UpdateCustomization(id), content, cancellationToken)) + using (var response = await client.PutAsync(Router.UpdateCustomization(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var organization = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return organization; } @@ -164,10 +164,10 @@ public async Task UpdateCustomizationAsync(string id, Dictionary UpdateDomainAsync(string id, Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PutAsync(Router.UpdateDomain(id), content, cancellationToken)) + using (var response = await client.PutAsync(Router.UpdateDomain(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var organization = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return organization; } @@ -175,10 +175,10 @@ public async Task UpdateDomainAsync(string id, Dictionary GetTestApiKeyAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.GetTestApiKey(id), cancellationToken)) + using (var response = await client.GetAsync(Router.GetTestApiKey(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); return resultString; } } @@ -187,20 +187,20 @@ public async Task GetTestApiKeyAsync(string id, CancellationToken cancel public async Task RenewTestApiKeyAsync(string id, CancellationToken cancellationToken = default) { using (var content = new StringContent("")) - using (var response = await client.PutAsync(Router.RenewTestApiKey(id), content, cancellationToken)) + using (var response = await client.PutAsync(Router.RenewTestApiKey(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); return resultString; } } public async Task ListLiveApiKeysAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.ListAsyncLiveApiKeys(id), cancellationToken)) + using (var response = await client.GetAsync(Router.ListAsyncLiveApiKeys(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var listResult = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return listResult; @@ -210,10 +210,10 @@ public async Task ListLiveApiKeysAsync(string id, CancellationToken public async Task RenewLiveApiKeyAsync(string id, CancellationToken cancellationToken = default) { using (var content = new StringContent("")) - using (var response = await client.PutAsync(Router.RenewLiveApiKey(id), content, cancellationToken)) + using (var response = await client.PutAsync(Router.RenewLiveApiKey(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); return resultString; } } @@ -221,10 +221,10 @@ public async Task RenewLiveApiKeyAsync(string id, CancellationToken canc public async Task> ListSeriesAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.ListSeriesGroup(id), cancellationToken)) + using (var response = await client.GetAsync(Router.ListSeriesGroup(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var searchResult = JsonConvert.DeserializeObject>(resultString, this.jsonSettings); return searchResult; @@ -234,10 +234,10 @@ public async Task> ListSeriesAsync(string id, CancellationToke public async Task CreateSeriesAsync(string id, Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.CreateSeriesGroup(id), content, cancellationToken)) + using (var response = await client.PostAsync(Router.CreateSeriesGroup(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var series = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return series; } @@ -246,10 +246,10 @@ public async Task CreateSeriesAsync(string id, Dictionary UpdateSeriesAsync(string id, string seriesName, Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PutAsync(Router.UpdateSeriesGroup(id, seriesName), content, cancellationToken)) + using (var response = await client.PutAsync(Router.UpdateSeriesGroup(id, seriesName), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var series = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return series; } @@ -257,10 +257,10 @@ public async Task UpdateSeriesAsync(string id, string seriesName, D public async Task DeleteSeriesAsync(string id, string seriesName, CancellationToken cancellationToken = default) { - using (var response = await client.DeleteAsync(Router.UpdateSeriesGroup(id, seriesName), cancellationToken)) + using (var response = await client.DeleteAsync(Router.DeleteSeriesGroup(id, seriesName), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var series = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return series; } @@ -268,10 +268,10 @@ public async Task DeleteSeriesAsync(string id, string seriesName, C public async Task> DeleteLiveApiKeyAsync(string id, string apiKeyId, CancellationToken cancellationToken = default) { - using (var response = await client.DeleteAsync(Router.DeleteLiveApiKey(id, apiKeyId), cancellationToken)) + using (var response = await client.DeleteAsync(Router.DeleteLiveApiKey(id, apiKeyId), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var deserializeJson = JsonConvert.DeserializeObject>(resultString, this.jsonSettings); return deserializeJson; } @@ -280,13 +280,196 @@ public async Task> DeleteLiveApiKeyAsync(string id, string apiK public async Task UpdateSelfInvoiceSettingsAsync(string organizationId, Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PutAsync(Router.UpdateSelfInvoiceSettings(organizationId), content, cancellationToken)) + using (var response = await client.PutAsync(Router.UpdateSelfInvoiceSettings(organizationId), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var organization = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return organization; } } + + public async Task> ListTeamAccessAsync(string organizationId, CancellationToken cancellationToken = default) + { + using (var response = await client.GetAsync(Router.ListTeamAccess(organizationId), cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject>(resultString, this.jsonSettings); + } + } + + public async Task RetrieveTeamAccessAsync(string organizationId, string accessId, CancellationToken cancellationToken = default) + { + using (var response = await client.GetAsync(Router.RetrieveTeamAccess(organizationId, accessId), cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject(resultString, this.jsonSettings); + } + } + + public async Task UpdateTeamAccessRoleAsync(string organizationId, string accessId, string role, CancellationToken cancellationToken = default) + { + using (var content = new StringContent(JsonConvert.SerializeObject(new Dictionary { ["role"] = role }), Encoding.UTF8, "application/json")) + using (var response = await client.PutAsync(Router.UpdateTeamAccessRole(organizationId, accessId), content, cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject(resultString, this.jsonSettings); + } + } + + public async Task RemoveTeamAccessAsync(string organizationId, string accessId, CancellationToken cancellationToken = default) + { + using (var response = await client.DeleteAsync(Router.RetrieveTeamAccess(organizationId, accessId), cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return DeserializeOk(resultString); + } + } + + public async Task> ListSentTeamInvitesAsync(string organizationId, CancellationToken cancellationToken = default) + { + using (var response = await client.GetAsync(Router.ListSentTeamInvites(organizationId), cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject>(resultString, this.jsonSettings); + } + } + + public async Task InviteUserToTeamAsync(string organizationId, Dictionary data, CancellationToken cancellationToken = default) + { + using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) + using (var response = await client.PostAsync(Router.ListSentTeamInvites(organizationId), content, cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject(resultString, this.jsonSettings); + } + } + + public async Task CancelTeamInviteAsync(string organizationId, string inviteKey, CancellationToken cancellationToken = default) + { + using (var response = await client.DeleteAsync(Router.CancelTeamInvite(organizationId, inviteKey), cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return DeserializeOk(resultString); + } + } + + public async Task> ListReceivedTeamInvitesAsync(CancellationToken cancellationToken = default) + { + using (var response = await client.GetAsync(Router.ListReceivedTeamInvites(), cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject>(resultString, this.jsonSettings); + } + } + + public async Task RespondTeamInviteAsync(string inviteKey, Dictionary data, CancellationToken cancellationToken = default) + { + using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) + using (var response = await client.PostAsync(Router.RespondTeamInvite(inviteKey), content, cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return DeserializeOk(resultString); + } + } + + public async Task> ListTeamRolesAsync(string organizationId, CancellationToken cancellationToken = default) + { + using (var response = await client.GetAsync(Router.ListTeamRoles(organizationId), cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject>(resultString, this.jsonSettings); + } + } + + public async Task> ListTeamRoleTemplatesAsync(string organizationId, CancellationToken cancellationToken = default) + { + using (var response = await client.GetAsync(Router.ListTeamRoleTemplates(organizationId), cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject>(resultString, this.jsonSettings); + } + } + + public async Task> ListTeamRoleOperationsAsync(string organizationId, CancellationToken cancellationToken = default) + { + using (var response = await client.GetAsync(Router.ListTeamRoleOperations(organizationId), cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject>(resultString, this.jsonSettings); + } + } + + public async Task RetrieveTeamRoleAsync(string organizationId, string roleId, CancellationToken cancellationToken = default) + { + using (var response = await client.GetAsync(Router.RetrieveTeamRole(organizationId, roleId), cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject(resultString, this.jsonSettings); + } + } + + public async Task CreateTeamRoleAsync(string organizationId, Dictionary data, CancellationToken cancellationToken = default) + { + using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) + using (var response = await client.PostAsync(Router.ListTeamRoles(organizationId), content, cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject(resultString, this.jsonSettings); + } + } + + public async Task UpdateTeamRoleAsync(string organizationId, string roleId, Dictionary data, CancellationToken cancellationToken = default) + { + using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) + using (var response = await client.PutAsync(Router.RetrieveTeamRole(organizationId, roleId), content, cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject(resultString, this.jsonSettings); + } + } + + public async Task DeleteTeamRoleAsync(string organizationId, string roleId, CancellationToken cancellationToken = default) + { + using (var response = await client.DeleteAsync(Router.RetrieveTeamRole(organizationId, roleId), cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return DeserializeOk(resultString); + } + } + + private bool DeserializeOk(string resultString) + { + var result = JsonConvert.DeserializeObject>(resultString, this.jsonSettings); + if (result != null && result.TryGetValue("ok", out var okValue)) + { + if (okValue is bool okBool) + { + return okBool; + } + if (bool.TryParse(okValue.ToString(), out var parsed)) + { + return parsed; + } + } + + return false; + } } } diff --git a/Wrappers/ProductWrapper.cs b/Wrappers/ProductWrapper.cs index cddc8d0..2bb3f72 100644 --- a/Wrappers/ProductWrapper.cs +++ b/Wrappers/ProductWrapper.cs @@ -7,7 +7,7 @@ namespace Facturapi.Wrappers { - public class ProductWrapper : BaseWrapper + public class ProductWrapper : BaseWrapper, IProductWrapper { internal ProductWrapper(string apiKey, string apiVersion, HttpClient httpClient) : base(apiKey, apiVersion, httpClient) { @@ -15,10 +15,10 @@ internal ProductWrapper(string apiKey, string apiVersion, HttpClient httpClient) public async Task> ListAsync(Dictionary query = null, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.ListProducts(query), cancellationToken)) + using (var response = await client.GetAsync(Router.ListProducts(query), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var searchResult = JsonConvert.DeserializeObject>(resultString, this.jsonSettings); return searchResult; @@ -28,10 +28,10 @@ public async Task> ListAsync(Dictionary qu public async Task CreateAsync(Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.CreateProduct(), content, cancellationToken)) + using (var response = await client.PostAsync(Router.CreateProduct(), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var customer = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return customer; } @@ -39,10 +39,10 @@ public async Task CreateAsync(Dictionary data, Cancella public async Task RetrieveAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.RetrieveProduct(id), cancellationToken)) + using (var response = await client.GetAsync(Router.RetrieveProduct(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var product = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return product; } @@ -50,10 +50,10 @@ public async Task RetrieveAsync(string id, CancellationToken cancellati public async Task DeleteAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.DeleteAsync(Router.DeleteProduct(id), cancellationToken)) + using (var response = await client.DeleteAsync(Router.DeleteProduct(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var product = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return product; } @@ -62,10 +62,10 @@ public async Task DeleteAsync(string id, CancellationToken cancellation public async Task UpdateAsync(string id, Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PutAsync(Router.UpdateProduct(id), content, cancellationToken)) + using (var response = await client.PutAsync(Router.UpdateProduct(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var product = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return product; } diff --git a/Wrappers/ReceiptWrapper.cs b/Wrappers/ReceiptWrapper.cs index 76e7b5e..ee1c507 100644 --- a/Wrappers/ReceiptWrapper.cs +++ b/Wrappers/ReceiptWrapper.cs @@ -8,7 +8,7 @@ namespace Facturapi.Wrappers { - public class ReceiptWrapper : BaseWrapper + public class ReceiptWrapper : BaseWrapper, IReceiptWrapper { internal ReceiptWrapper(string apiKey, string apiVersion, HttpClient httpClient) : base(apiKey, apiVersion, httpClient) { @@ -16,10 +16,10 @@ internal ReceiptWrapper(string apiKey, string apiVersion, HttpClient httpClient) public async Task> ListAsync(Dictionary query = null, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.ListReceipts(query), cancellationToken)) + using (var response = await client.GetAsync(Router.ListReceipts(query), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var searchResult = JsonConvert.DeserializeObject>(resultString, this.jsonSettings); return searchResult; @@ -29,10 +29,10 @@ public async Task> ListAsync(Dictionary qu public async Task CreateAsync(Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.CreateReceipt(), content, cancellationToken)) + using (var response = await client.PostAsync(Router.CreateReceipt(), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var customer = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return customer; } @@ -40,10 +40,10 @@ public async Task CreateAsync(Dictionary data, Cancella public async Task RetrieveAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.RetrieveReceipt(id), cancellationToken)) + using (var response = await client.GetAsync(Router.RetrieveReceipt(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var customer = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return customer; } @@ -51,10 +51,10 @@ public async Task RetrieveAsync(string id, CancellationToken cancellati public async Task CancelAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.DeleteAsync(Router.CancelReceipt(id), cancellationToken)) + using (var response = await client.DeleteAsync(Router.CancelReceipt(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var customer = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return customer; } @@ -63,38 +63,38 @@ public async Task CancelAsync(string id, CancellationToken cancellation public async Task InvoiceAsync(string id, Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.InvoiceReceipt(id), content, cancellationToken)) + using (var response = await client.PostAsync(Router.InvoiceReceipt(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); } } public async Task CreateGlobalInvoiceAsync(Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.CreateGlobalInvoice(), content, cancellationToken)) + using (var response = await client.PostAsync(Router.CreateGlobalInvoice(), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); } } public async Task SendByEmailAsync(string id, Dictionary data = null, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.SendReceiptByEmail(id), content, cancellationToken)) + using (var response = await client.PostAsync(Router.SendReceiptByEmail(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); } } public async Task DownloadPdfAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.DownloadReceiptPdf(id), cancellationToken)) + using (var response = await client.GetAsync(Router.DownloadReceiptPdf(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var responseStream = await response.Content.ReadAsStreamAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var memory = new MemoryStream(); - await responseStream.CopyToAsync(memory, 81920, cancellationToken); + await responseStream.CopyToAsync(memory, 81920, cancellationToken).ConfigureAwait(false); memory.Position = 0; return memory; } diff --git a/Wrappers/RetentionWrapper.cs b/Wrappers/RetentionWrapper.cs index 7deb0bc..607d31e 100644 --- a/Wrappers/RetentionWrapper.cs +++ b/Wrappers/RetentionWrapper.cs @@ -5,11 +5,10 @@ using System.Text; using System.Threading.Tasks; using System.Threading; -using Newtonsoft.Json.Linq; namespace Facturapi.Wrappers { - public class RetentionWrapper : BaseWrapper + public class RetentionWrapper : BaseWrapper, IRetentionWrapper { internal RetentionWrapper(string apiKey, string apiVersion, HttpClient httpClient) : base(apiKey, apiVersion, httpClient) { @@ -17,10 +16,10 @@ internal RetentionWrapper(string apiKey, string apiVersion, HttpClient httpClien public async Task> ListAsync(Dictionary query = null, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.ListRetentionss(query), cancellationToken)) + using (var response = await client.GetAsync(Router.ListRetentions(query), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var searchResult = JsonConvert.DeserializeObject>(resultString, this.jsonSettings); return searchResult; @@ -30,10 +29,10 @@ public async Task> ListAsync(Dictionary qu public async Task CreateAsync(Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.CreateRetention(), content, cancellationToken)) + using (var response = await client.PostAsync(Router.CreateRetention(), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var customer = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return customer; } @@ -41,46 +40,43 @@ public async Task CreateAsync(Dictionary data, Cancella public async Task RetrieveAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.RetrieveRetention(id), cancellationToken)) + using (var response = await client.GetAsync(Router.RetrieveRetention(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var customer = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return customer; } } - public async Task CancelAsync(string id, Dictionary query = null) + public async Task CancelAsync(string id, Dictionary query = null, CancellationToken cancellationToken = default) { - var url = Router.CancelRetention(id, query); - var response = await client.DeleteAsync(url); - var responseString = await response.Content.ReadAsStringAsync(); - if (!response.IsSuccessStatusCode) + using (var response = await client.DeleteAsync(Router.CancelRetention(id, query), cancellationToken).ConfigureAwait(false)) { - var error = JsonConvert.DeserializeObject(responseString, this.jsonSettings); - throw new FacturapiException(error["message"].ToString()); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var retention = JsonConvert.DeserializeObject(responseString, this.jsonSettings); + return retention; } - var retention = JsonConvert.DeserializeObject(responseString, this.jsonSettings); - return retention; } public async Task SendByEmailAsync(string id, Dictionary data = null, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.SendRetentionByEmail(id), content, cancellationToken)) + using (var response = await client.PostAsync(Router.SendRetentionByEmail(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); } } private async Task DownloadAsync(string id, string format, CancellationToken cancellationToken) { - using (var response = await client.GetAsync(Router.DownloadRetention(id, format), cancellationToken)) + using (var response = await client.GetAsync(Router.DownloadRetention(id, format), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var responseStream = await response.Content.ReadAsStreamAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var memory = new MemoryStream(); - await responseStream.CopyToAsync(memory, 81920, cancellationToken); + await responseStream.CopyToAsync(memory, 81920, cancellationToken).ConfigureAwait(false); memory.Position = 0; return memory; } diff --git a/Wrappers/ToolWrapper.cs b/Wrappers/ToolWrapper.cs index 6d0e662..cf3eb2c 100644 --- a/Wrappers/ToolWrapper.cs +++ b/Wrappers/ToolWrapper.cs @@ -6,7 +6,7 @@ namespace Facturapi.Wrappers { - public class ToolWrapper : BaseWrapper + public class ToolWrapper : BaseWrapper, IToolWrapper { internal ToolWrapper(string apiKey, string apiVersion, HttpClient httpClient) : base(apiKey, apiVersion, httpClient) { @@ -19,10 +19,10 @@ public async Task ValidateTaxIdAsync(string taxId, Cancellation { ["tax_id"] = taxId } - ), cancellationToken)) + ), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var result = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return result; @@ -31,10 +31,10 @@ public async Task ValidateTaxIdAsync(string taxId, Cancellation public async Task HealthCheckAsync(CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.HealthCheck(), cancellationToken)) + using (var response = await client.GetAsync(Router.HealthCheck(), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var result = JsonConvert.DeserializeObject>(resultString, this.jsonSettings); if (result != null && result.TryGetValue("ok", out var okValue)) { diff --git a/Wrappers/WebhookWrapper.cs b/Wrappers/WebhookWrapper.cs index 325cf5f..94f5776 100644 --- a/Wrappers/WebhookWrapper.cs +++ b/Wrappers/WebhookWrapper.cs @@ -7,7 +7,7 @@ namespace Facturapi.Wrappers { - public class WebhookWrapper : BaseWrapper + public class WebhookWrapper : BaseWrapper, IWebhookWrapper { internal WebhookWrapper(string apiKey, string apiVersion, HttpClient httpClient) : base(apiKey, apiVersion, httpClient) { @@ -15,10 +15,10 @@ internal WebhookWrapper(string apiKey, string apiVersion, HttpClient httpClient) public async Task> ListAsync(Dictionary query = null, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.ListWebhooks(query), cancellationToken)) + using (var response = await client.GetAsync(Router.ListWebhooks(query), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var searchResult = JsonConvert.DeserializeObject>(resultString, this.jsonSettings); return searchResult; @@ -28,10 +28,10 @@ public async Task> ListAsync(Dictionary qu public async Task CreateAsync(Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.CreateWebhook(), content, cancellationToken)) + using (var response = await client.PostAsync(Router.CreateWebhook(), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var webhook = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return webhook; } @@ -39,10 +39,10 @@ public async Task CreateAsync(Dictionary data, Cancella public async Task RetrieveAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.GetAsync(Router.RetrieveWebhook(id), cancellationToken)) + using (var response = await client.GetAsync(Router.RetrieveWebhook(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var webhook = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return webhook; } @@ -51,10 +51,10 @@ public async Task RetrieveAsync(string id, CancellationToken cancellati public async Task UpdateAsync(string id, Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PutAsync(Router.UpdateWebhook(id), content, cancellationToken)) + using (var response = await client.PutAsync(Router.UpdateWebhook(id), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var webhook = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return webhook; } @@ -62,10 +62,10 @@ public async Task UpdateAsync(string id, Dictionary dat public async Task DeleteAsync(string id, CancellationToken cancellationToken = default) { - using (var response = await client.DeleteAsync(Router.DeleteWebhook(id), cancellationToken)) + using (var response = await client.DeleteAsync(Router.DeleteWebhook(id), cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var webhook = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return webhook; } @@ -74,10 +74,10 @@ public async Task DeleteAsync(string id, CancellationToken cancellation public async Task ValidateSignatureAsync(Dictionary data, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) - using (var response = await client.PostAsync(Router.ValidateSignature(), content, cancellationToken)) + using (var response = await client.PostAsync(Router.ValidateSignature(), content, cancellationToken).ConfigureAwait(false)) { - await this.ThrowIfErrorAsync(response, cancellationToken); - var resultString = await response.Content.ReadAsStringAsync(); + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var webhook = JsonConvert.DeserializeObject(resultString, this.jsonSettings); return webhook; } diff --git a/facturapi-net.csproj b/facturapi-net.csproj index 1f94b60..804faff 100644 --- a/facturapi-net.csproj +++ b/facturapi-net.csproj @@ -1,7 +1,7 @@ - netstandard2.0;net452;net6.0;net8.0 + netstandard2.0;net6.0;net8.0 Facturapi Facturapi Facturapi @@ -11,14 +11,19 @@ API Keys creando una cuenta gratuita en https://www.facturapi.io factura factura-electronica cfdi facturapi mexico conekta Facturapi - 5.1.1 + 6.0.0 $(Version) Facturapi facturapi.ico True + latest + disable + true + true + snupkg Facturación Espacial Facturación Espacial © 2025 - www.facturapi.io + https://www.facturapi.io README.md https://github.com/facturapi/facturapi-net git @@ -29,11 +34,14 @@ - - + + + + + diff --git a/facturapi-net.sln b/facturapi-net.sln index 300e153..dfd6ed4 100644 --- a/facturapi-net.sln +++ b/facturapi-net.sln @@ -4,16 +4,42 @@ VisualStudioVersion = 17.8.34322.80 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "facturapi-net", "facturapi-net.csproj", "{11E14BFA-6A9B-43D6-828F-B493E1617326}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FacturapiTest", "FacturapiTest\FacturapiTest.csproj", "{C424B990-E6E7-428C-9BA3-BD08BD5246A0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {11E14BFA-6A9B-43D6-828F-B493E1617326}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {11E14BFA-6A9B-43D6-828F-B493E1617326}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11E14BFA-6A9B-43D6-828F-B493E1617326}.Debug|x64.ActiveCfg = Debug|Any CPU + {11E14BFA-6A9B-43D6-828F-B493E1617326}.Debug|x64.Build.0 = Debug|Any CPU + {11E14BFA-6A9B-43D6-828F-B493E1617326}.Debug|x86.ActiveCfg = Debug|Any CPU + {11E14BFA-6A9B-43D6-828F-B493E1617326}.Debug|x86.Build.0 = Debug|Any CPU {11E14BFA-6A9B-43D6-828F-B493E1617326}.Release|Any CPU.ActiveCfg = Release|Any CPU {11E14BFA-6A9B-43D6-828F-B493E1617326}.Release|Any CPU.Build.0 = Release|Any CPU + {11E14BFA-6A9B-43D6-828F-B493E1617326}.Release|x64.ActiveCfg = Release|Any CPU + {11E14BFA-6A9B-43D6-828F-B493E1617326}.Release|x64.Build.0 = Release|Any CPU + {11E14BFA-6A9B-43D6-828F-B493E1617326}.Release|x86.ActiveCfg = Release|Any CPU + {11E14BFA-6A9B-43D6-828F-B493E1617326}.Release|x86.Build.0 = Release|Any CPU + {C424B990-E6E7-428C-9BA3-BD08BD5246A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C424B990-E6E7-428C-9BA3-BD08BD5246A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C424B990-E6E7-428C-9BA3-BD08BD5246A0}.Debug|x64.ActiveCfg = Debug|Any CPU + {C424B990-E6E7-428C-9BA3-BD08BD5246A0}.Debug|x64.Build.0 = Debug|Any CPU + {C424B990-E6E7-428C-9BA3-BD08BD5246A0}.Debug|x86.ActiveCfg = Debug|Any CPU + {C424B990-E6E7-428C-9BA3-BD08BD5246A0}.Debug|x86.Build.0 = Debug|Any CPU + {C424B990-E6E7-428C-9BA3-BD08BD5246A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C424B990-E6E7-428C-9BA3-BD08BD5246A0}.Release|Any CPU.Build.0 = Release|Any CPU + {C424B990-E6E7-428C-9BA3-BD08BD5246A0}.Release|x64.ActiveCfg = Release|Any CPU + {C424B990-E6E7-428C-9BA3-BD08BD5246A0}.Release|x64.Build.0 = Release|Any CPU + {C424B990-E6E7-428C-9BA3-BD08BD5246A0}.Release|x86.ActiveCfg = Release|Any CPU + {C424B990-E6E7-428C-9BA3-BD08BD5246A0}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE