From 91ca9a039cae82913a1843f5b089ef49e04bd804 Mon Sep 17 00:00:00 2001 From: "raul@facturapi.io" Date: Mon, 27 Apr 2026 14:54:59 -0600 Subject: [PATCH] chore(multiInvoices): add new methods for multiinvoice receipts --- CHANGELOG.md | 5 +++ FacturapiTest/WrapperBehaviorTests.cs | 51 +++++++++++++++++++++++++++ Router/ReceiptRouter.cs | 10 ++++++ Wrappers/IReceiptWrapper.cs | 2 ++ Wrappers/ReceiptWrapper.cs | 23 ++++++++++++ facturapi-net.csproj | 2 +- 6 files changed, 92 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb1da6..88a703a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ 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.4.0] +### Added +- Added `facturapi.Receipt.ToInvoiceAsync(Dictionary data)` to call `POST /receipts/to-invoice`. +- Added `facturapi.Receipt.PreviewToInvoicePdfAsync(Dictionary data)` to call `POST /receipts/to-invoice/preview`. + ## [6.3.0] - 2026-04-23 ### Added - Added Carta Porte constants and description catalogs: customs regimes, transport keys, station types, SCT permits, COFEPRIS sectors, pharmaceutical forms, special transport conditions, material types, customs document types, transport unit/figure types, Istmo records, loading keys, maritime configuration, rail traffic, container types, maritime container types, rail car/service types, transfer motives, incoterms, and customs units. diff --git a/FacturapiTest/WrapperBehaviorTests.cs b/FacturapiTest/WrapperBehaviorTests.cs index 29021dc..9080889 100644 --- a/FacturapiTest/WrapperBehaviorTests.cs +++ b/FacturapiTest/WrapperBehaviorTests.cs @@ -54,6 +54,57 @@ public async Task ReceiptCancelAsync_UsesReceiptDeleteRoute() Assert.Equal("rcp_123", result.Id); } + [Fact] + public async Task ReceiptToInvoiceAsync_UsesToInvoiceRoute() + { + var handler = new RecordingHandler(async (request, cancellationToken) => + { + Assert.Equal(HttpMethod.Post, request.Method); + Assert.NotNull(request.RequestUri); + Assert.Equal("/v2/receipts/to-invoice", request.RequestUri.PathAndQuery); + Assert.NotNull(request.Content); + var body = await request.Content.ReadAsStringAsync().ConfigureAwait(false); + Assert.Contains("\"keys\":[\"rcp_1\",\"rcp_2\"]", body); + return JsonResponse("{\"id\":\"inv_123\"}"); + }); + + var wrapper = new ReceiptWrapper("test_key", "v2", CreateHttpClient(handler)); + await wrapper.ToInvoiceAsync(new Dictionary + { + ["keys"] = new[] { "rcp_1", "rcp_2" } + }); + } + + [Fact] + public async Task ReceiptPreviewToInvoicePdfAsync_UsesPreviewPdfRoute() + { + var payload = Encoding.UTF8.GetBytes("pdf-bytes"); + var handler = new RecordingHandler(async (request, cancellationToken) => + { + Assert.Equal(HttpMethod.Post, request.Method); + Assert.NotNull(request.RequestUri); + Assert.Equal("/v2/receipts/to-invoice/preview", request.RequestUri.PathAndQuery); + Assert.NotNull(request.Content); + var body = await request.Content.ReadAsStringAsync().ConfigureAwait(false); + Assert.Contains("\"keys\":[\"rcp_1\"]", body); + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new ByteArrayContent(payload) + }; + }); + + var wrapper = new ReceiptWrapper("test_key", "v2", CreateHttpClient(handler)); + using var stream = await wrapper.PreviewToInvoicePdfAsync(new Dictionary + { + ["keys"] = new[] { "rcp_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 OrganizationDeleteSeriesAsync_UsesDeleteSeriesRoute() { diff --git a/Router/ReceiptRouter.cs b/Router/ReceiptRouter.cs index 962d825..92c6e37 100644 --- a/Router/ReceiptRouter.cs +++ b/Router/ReceiptRouter.cs @@ -38,6 +38,16 @@ public static string CreateGlobalInvoice() return $"receipts/global-invoice"; } + public static string ToInvoice() + { + return "receipts/to-invoice"; + } + + public static string PreviewToInvoicePdf() + { + return "receipts/to-invoice/preview"; + } + public static string DownloadReceiptPdf(string id) { return $"receipts/{id}/pdf"; diff --git a/Wrappers/IReceiptWrapper.cs b/Wrappers/IReceiptWrapper.cs index 022c023..085b480 100644 --- a/Wrappers/IReceiptWrapper.cs +++ b/Wrappers/IReceiptWrapper.cs @@ -13,6 +13,8 @@ public interface IReceiptWrapper Task CancelAsync(string id, CancellationToken cancellationToken = default); Task InvoiceAsync(string id, Dictionary data, CancellationToken cancellationToken = default); Task CreateGlobalInvoiceAsync(Dictionary data, CancellationToken cancellationToken = default); + Task ToInvoiceAsync(Dictionary data, CancellationToken cancellationToken = default); + Task PreviewToInvoicePdfAsync(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/ReceiptWrapper.cs b/Wrappers/ReceiptWrapper.cs index ee1c507..eb93fc3 100644 --- a/Wrappers/ReceiptWrapper.cs +++ b/Wrappers/ReceiptWrapper.cs @@ -78,6 +78,29 @@ public async Task CreateGlobalInvoiceAsync(Dictionary data, Canc } } + public async Task ToInvoiceAsync(Dictionary data, CancellationToken cancellationToken = default) + { + using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) + using (var response = await client.PostAsync(Router.ToInvoice(), content, cancellationToken).ConfigureAwait(false)) + { + await this.ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + } + } + + public async Task PreviewToInvoicePdfAsync(Dictionary data, CancellationToken cancellationToken = default) + { + using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) + using (var response = await client.PostAsync(Router.PreviewToInvoicePdf(), content, cancellationToken).ConfigureAwait(false)) + { + 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).ConfigureAwait(false); + memory.Position = 0; + return memory; + } + } + public async Task SendByEmailAsync(string id, Dictionary data = null, CancellationToken cancellationToken = default) { using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")) diff --git a/facturapi-net.csproj b/facturapi-net.csproj index 32c8964..1ed4cdd 100644 --- a/facturapi-net.csproj +++ b/facturapi-net.csproj @@ -11,7 +11,7 @@ SDK oficial de Facturapi para .NET para facturación electrónica en México (CFDI), envío de documentos, búsqueda y trazabilidad. factura factura-electronica facturacion cfdi cfdi40 sat invoice invoicing facturapi mexico Facturapi - 6.3.0 + 6.4.0 $(Version) MIT false