From cd7506f6394097be1c98c23411cd03aa439de3d7 Mon Sep 17 00:00:00 2001 From: "raul@facturapi.io" Date: Wed, 12 Aug 2026 09:36:25 -0600 Subject: [PATCH 1/2] chore(invoices): add ZIP request methods --- facturapi-net.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/facturapi-net.csproj b/facturapi-net.csproj index f95d0a8..0ea2c8c 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 - 7.0.0 + 7.1.0 $(Version) MIT false From facd28d90755c9944bfb16751a14699aab53d23e Mon Sep 17 00:00:00 2001 From: "raul@facturapi.io" Date: Thu, 17 Sep 2026 15:38:56 -0600 Subject: [PATCH 2/2] chore(nominaCatalogs): add new methods for nomina catalogs --- CHANGELOG.md | 6 ++++++ FacturapiClient.cs | 2 ++ IFacturapiClient.cs | 1 + README.md | 7 +++++++ Router/NominaCatalogRouter.cs | 17 +++++++++++++++ Wrappers/INominaCatalogWrapper.cs | 12 +++++++++++ Wrappers/NominaCatalogWrapper.cs | 35 +++++++++++++++++++++++++++++++ 7 files changed, 80 insertions(+) create mode 100644 Router/NominaCatalogRouter.cs create mode 100644 Wrappers/INominaCatalogWrapper.cs create mode 100644 Wrappers/NominaCatalogWrapper.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index d9b8dac..5d49595 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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). +## [7.1.0] - 2026-10-07 + +### Added + +- Add `NominaCatalog.SearchDeductions` and `NominaCatalog.SearchPerceptions` to search the SAT payroll deduction and perception catalogs. + ## [7.0.0] - 2026-09-09 ### Breaking - `SearchResult.Page`, `TotalPages`, and `TotalResults` are now nullable (`int?`): cursor responses after the first page omit them and absence must not deserialize as `0`. diff --git a/FacturapiClient.cs b/FacturapiClient.cs index a97eaeb..485b398 100644 --- a/FacturapiClient.cs +++ b/FacturapiClient.cs @@ -16,6 +16,7 @@ public sealed class FacturapiClient : IFacturapiClient public IRetentionWrapper Retention { get; private set; } public ICatalogWrapper Catalog { get; private set; } public ICartaporteCatalogWrapper CartaporteCatalog { get; private set; } + public INominaCatalogWrapper NominaCatalog { get; private set; } public IToolWrapper Tool { get; private set; } public IWebhookWrapper Webhook { get; private set; } private readonly HttpClient httpClient; @@ -41,6 +42,7 @@ private FacturapiClient(string apiKey, string apiVersion, HttpClient httpClient, this.Retention = new RetentionWrapper(apiKey, apiVersion, this.httpClient); this.Catalog = new CatalogWrapper(apiKey, apiVersion, this.httpClient); this.CartaporteCatalog = new CartaporteCatalogWrapper(apiKey, apiVersion, this.httpClient); + this.NominaCatalog = new NominaCatalogWrapper(apiKey, apiVersion, this.httpClient); this.Tool = new ToolWrapper(apiKey, apiVersion, this.httpClient); this.Webhook = new WebhookWrapper(apiKey, apiVersion, this.httpClient); } diff --git a/IFacturapiClient.cs b/IFacturapiClient.cs index 0217b28..dad3419 100644 --- a/IFacturapiClient.cs +++ b/IFacturapiClient.cs @@ -13,6 +13,7 @@ public interface IFacturapiClient : IDisposable IRetentionWrapper Retention { get; } ICatalogWrapper Catalog { get; } ICartaporteCatalogWrapper CartaporteCatalog { get; } + INominaCatalogWrapper NominaCatalog { get; } IToolWrapper Tool { get; } IWebhookWrapper Webhook { get; } } diff --git a/README.md b/README.md index e8303db..4e8ebc7 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,13 @@ var facturapi = new FacturapiClient("TU_API_KEY"); var invoice = await facturapi.Invoice.CreateAsync(...); ``` +### Catálogos de nómina + +```csharp +var deductions = await facturapi.NominaCatalog.SearchDeductions(new Dictionary { ["q"] = "001" }); +var perceptions = await facturapi.NominaCatalog.SearchPerceptions(new Dictionary { ["q"] = "001" }); +``` + ### Escenario avanzado: usar tu propio `HttpClient` Para la mayoría de usuarios, usa el constructor normal de `FacturapiClient`. diff --git a/Router/NominaCatalogRouter.cs b/Router/NominaCatalogRouter.cs new file mode 100644 index 0000000..a2f6774 --- /dev/null +++ b/Router/NominaCatalogRouter.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Facturapi +{ + internal static partial class Router + { + public static string SearchNominaDeductions(Dictionary query = null) + { + return UriWithQuery("catalogs/deductions", query); + } + + public static string SearchNominaPerceptions(Dictionary query = null) + { + return UriWithQuery("catalogs/perceptions", query); + } + } +} diff --git a/Wrappers/INominaCatalogWrapper.cs b/Wrappers/INominaCatalogWrapper.cs new file mode 100644 index 0000000..24971c2 --- /dev/null +++ b/Wrappers/INominaCatalogWrapper.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Facturapi.Wrappers +{ + public interface INominaCatalogWrapper + { + Task> SearchDeductions(Dictionary query = null, CancellationToken cancellationToken = default); + Task> SearchPerceptions(Dictionary query = null, CancellationToken cancellationToken = default); + } +} diff --git a/Wrappers/NominaCatalogWrapper.cs b/Wrappers/NominaCatalogWrapper.cs new file mode 100644 index 0000000..6fdc470 --- /dev/null +++ b/Wrappers/NominaCatalogWrapper.cs @@ -0,0 +1,35 @@ +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Facturapi.Wrappers +{ + public class NominaCatalogWrapper : BaseWrapper, INominaCatalogWrapper + { + internal NominaCatalogWrapper(string apiKey, string apiVersion, HttpClient httpClient) : base(apiKey, apiVersion, httpClient) + { + } + + public Task> SearchDeductions(Dictionary query = null, CancellationToken cancellationToken = default) + { + return SearchCatalogAsync(Router.SearchNominaDeductions(query), cancellationToken); + } + + public Task> SearchPerceptions(Dictionary query = null, CancellationToken cancellationToken = default) + { + return SearchCatalogAsync(Router.SearchNominaPerceptions(query), cancellationToken); + } + + private async Task> SearchCatalogAsync(string url, CancellationToken cancellationToken) + { + using (var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false)) + { + await ThrowIfErrorAsync(response, cancellationToken).ConfigureAwait(false); + var resultString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject>(resultString, jsonSettings); + } + } + } +}