Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
377 changes: 377 additions & 0 deletions .gitattributes

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,16 @@ jobs:

- name: Test
run: dotnet test FacturapiTest/FacturapiTest.csproj --framework ${{ matrix.test_framework }} --configuration Release --no-build

codegen:
name: Verify generated SDK
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
- uses: pnpm/action-setup@v4
- run: pnpm install --frozen-lockfile
- run: pnpm test:codegen
- run: pnpm generate:sdk:check
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,6 @@ MigrationBackup/
.ionide/

# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
# SDK generation tooling
node_modules/
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ 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).

## [8.0.0] - 2026-11-13

### Breaking

- Align response models with the API contract, including nullable dates and optional fields, decimal numeric values, typed nested objects, and corrected property names. See the README migration guide.
- Retention methods return retention models; ZIP requests expose typed models; organization key and series operations return the documented lists. Receipt invoicing and email methods expose their API results.
- Webhook signature verification returns `ApiEvent` and runs locally. Explicit remote verification preserves string or object payload responses.

### Added

- Typed inputs, filters and XML documentation across all public API operations, including invoice and retention downloads, signed URLs, receipt assignment and summaries, FIEL uploads, product categories, geographic and fiscal catalogs, and foreign-trade catalogs.
- Invoice creation variants for Ingreso, Egreso, Pago, Nómina and Traslado, partial draft inputs, and cancellation inputs that require a substitution document when applicable.
- Typed complements for payments, payroll, Carta Porte, foreign trade and fiscal legends, preserving custom XML and open JSON values.
- Typed API error details through `FacturapiException.ErrorDetails`, retaining the original `Errors` JSON.
- `OneOf` alternatives with implicit conversions and response inspection, and input dates accepting ISO strings, `DateTime` or `DateTimeOffset`.

### Fixed

- Preserve omitted fields versus explicit nulls and serialize boolean, numeric and date query values using their wire formats.
- Decode webhook event objects according to their declared resource type without making an HTTP request for local signature verification.

## [7.1.0] - 2026-10-07

### Added
Expand Down
4 changes: 3 additions & 1 deletion FacturAPIException.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;

Expand All @@ -11,6 +11,8 @@ public class FacturapiException : Exception
public string Path { get; private set; }
public string Location { get; private set; }
public JArray Errors { get; private set; }
/// <summary>Detalles de error tipados. Errors conserva el JSON original para accesos dinámicos.</summary>
public IReadOnlyList<ErrorDetail> ErrorDetails => Errors?.ToObject<ErrorDetail[]>() ?? Array.Empty<ErrorDetail>();
public string LogId { get; private set; }
public IReadOnlyDictionary<string, string> Headers { get; private set; }

Expand Down
3 changes: 3 additions & 0 deletions FacturapiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public sealed class FacturapiClient : IFacturapiClient
public ICatalogWrapper Catalog { get; private set; }
public ICartaporteCatalogWrapper CartaporteCatalog { get; private set; }
public INominaCatalogWrapper NominaCatalog { get; private set; }
/// <summary>Busca claves y descripciones de fracciones arancelarias de comercio exterior.</summary>
public IComercioExteriorCatalogWrapper ComercioExteriorCatalog { get; private set; }
public IToolWrapper Tool { get; private set; }
public IWebhookWrapper Webhook { get; private set; }
private readonly HttpClient httpClient;
Expand All @@ -43,6 +45,7 @@ private FacturapiClient(string apiKey, string apiVersion, HttpClient 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.ComercioExteriorCatalog = new ComercioExteriorCatalogWrapper(apiKey, apiVersion, this.httpClient);
this.Tool = new ToolWrapper(apiKey, apiVersion, this.httpClient);
this.Webhook = new WebhookWrapper(apiKey, apiVersion, this.httpClient);
}
Expand Down
11 changes: 7 additions & 4 deletions FacturapiTest/ClientCompatibilityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void Router_ListCustomers_SkipsNullQueryValues()
[""] = "ignored"
};

var url = Router.ListCustomers(query);
var url = Router.UriWithQuery("customers", query);

Assert.Equal("customers", url);
}
Expand All @@ -32,12 +32,14 @@ public void Router_ListCustomers_SendsExplicitEmptyQueryValues()
{
var query = new Dictionary<string, object>
{
["foo"] = ""
["foo"] = "",
["enabled"] = true,
["disabled"] = false
};

var url = Router.ListCustomers(query);
var url = Router.UriWithQuery("customers", query);

Assert.Equal("customers?foo=", url);
Assert.Equal("customers?foo=&enabled=true&disabled=false", url);
}

[Fact]
Expand All @@ -63,6 +65,7 @@ public async Task RetentionCancelAsync_ThrowsFacturapiExceptionWithStatus()

Assert.Equal(400, exception.Status);
Assert.Equal("bad retention", exception.Message);
Assert.Empty(exception.ErrorDetails);
}

[Fact]
Expand Down
Loading
Loading