Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, object> data)` to call `POST /receipts/to-invoice`.
- Added `facturapi.Receipt.PreviewToInvoicePdfAsync(Dictionary<string, object> 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.
Expand Down
51 changes: 51 additions & 0 deletions FacturapiTest/WrapperBehaviorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, object>
{
["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<string, object>
{
["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()
{
Expand Down
10 changes: 10 additions & 0 deletions Router/ReceiptRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
2 changes: 2 additions & 0 deletions Wrappers/IReceiptWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public interface IReceiptWrapper
Task<Receipt> CancelAsync(string id, CancellationToken cancellationToken = default);
Task InvoiceAsync(string id, Dictionary<string, object> data, CancellationToken cancellationToken = default);
Task CreateGlobalInvoiceAsync(Dictionary<string, object> data, CancellationToken cancellationToken = default);
Task ToInvoiceAsync(Dictionary<string, object> data, CancellationToken cancellationToken = default);
Task<Stream> PreviewToInvoicePdfAsync(Dictionary<string, object> data, CancellationToken cancellationToken = default);
Task SendByEmailAsync(string id, Dictionary<string, object> data = null, CancellationToken cancellationToken = default);
Task<Stream> DownloadPdfAsync(string id, CancellationToken cancellationToken = default);
}
Expand Down
23 changes: 23 additions & 0 deletions Wrappers/ReceiptWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,29 @@ public async Task CreateGlobalInvoiceAsync(Dictionary<string, object> data, Canc
}
}

public async Task ToInvoiceAsync(Dictionary<string, object> 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<Stream> PreviewToInvoicePdfAsync(Dictionary<string, object> 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<string, object> data = null, CancellationToken cancellationToken = default)
{
using (var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json"))
Expand Down
2 changes: 1 addition & 1 deletion facturapi-net.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<Summary>SDK oficial de Facturapi para .NET para facturación electrónica en México (CFDI), envío de documentos, búsqueda y trazabilidad.</Summary>
<PackageTags>factura factura-electronica facturacion cfdi cfdi40 sat invoice invoicing facturapi mexico</PackageTags>
<Title>Facturapi</Title>
<Version>6.3.0</Version>
<Version>6.4.0</Version>
<PackageVersion>$(Version)</PackageVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
Expand Down
Loading