Access.It.Pdf.Facturx
10.6.5
dotnet add package Access.It.Pdf.Facturx --version 10.6.5
NuGet\Install-Package Access.It.Pdf.Facturx -Version 10.6.5
<PackageReference Include="Access.It.Pdf.Facturx" Version="10.6.5" />
<PackageVersion Include="Access.It.Pdf.Facturx" Version="10.6.5" />
<PackageReference Include="Access.It.Pdf.Facturx" />
paket add Access.It.Pdf.Facturx --version 10.6.5
#r "nuget: Access.It.Pdf.Facturx, 10.6.5"
#:package Access.It.Pdf.Facturx@10.6.5
#addin nuget:?package=Access.It.Pdf.Facturx&version=10.6.5
#tool nuget:?package=Access.It.Pdf.Facturx&version=10.6.5
Access.It.Pdf.Facturx
Génération ET lecture de factures Factur-X (EN 16931) pour .NET 8 et .NET 10.
Fournit :
- API duale
byte[]+Streampour Fusion / Importer / PdfaPreCheck — RAM-efficient pour services web et gros PDFs - Modèles typés BT/BG EN 16931 (records C# immuables)
- Auto-détection du profil minimal (
FacturXProfileResolver.DetectMinimal) — évite d'envoyer EN16931 quand Basic ou Minimum suffit - Validateur modèle pré-XML (
IFacturXModelValidator) : champs requis + cohérence totaux (BR-CO-10/13/14/16) avant sérialisation — feedback immédiat - Helpers de calcul métier (
FacturXCalculator) :ComputeVatBreakdown+ComputeTotals— bâtit les ventilations et totaux à partir des lignes, garantit cohérence BR-CO-* - Générateur XML CII UN/CEFACT D16B (XSD-conforme, ordre des éléments strict)
- Validateur structurel des règles BR-* critiques (~20 sur 138)
- Validateur XSD officiel par profil (UN/CEFACT CII embarqués : Minimum / Basic / EN 16931 / Extended)
- Pre-check PDF/A-3 du PDF source (
IFacturXPdfaCompliancePreCheck) : détecte fonts non embedded, CIDToGIDMap manquant, transparence avant fusion - Fusion PDF/A-3 via PdfSharp 7 (OutputIntent sRGB, XMP Factur-X, embedded file, AF)
- Importer : extraction du XML CII embarqué d'un Factur-X existant (use case réception facture)
- DI extension :
services.AddAccessItFacturx()
Aucune dépendance commerciale (Aspose, iText AGPL) — PdfSharp MIT uniquement.
Installation
<PackageReference Include="Access.It.Pdf.Facturx" />
Puis dans Program.cs :
services.AddAccessItFacturx();
Usage
using Access.It.Facturx.Models;
using Access.It.Facturx.Models.Enums;
using Access.It.Facturx.Services.Abstract;
public class InvoiceFacturXBuilder(
IFacturXXmlGenerator generator,
IFacturXValidator validator,
IFacturXFusion fusion)
{
public async Task<byte[]> BuildAsync(byte[] sourcePdf, MyInvoice invoice, CancellationToken ct)
{
// 1. Construire le modèle Factur-X depuis vos entités métier
var model = new FacturXInvoiceModel
{
Number = invoice.Number,
IssueDate = invoice.Date,
TypeCode = DocumentTypeCode.CommercialInvoice,
BusinessProcess = BusinessProcessCode.ServicesInvoice,
Seller = new FacturXSellerModel { /* ... */ },
Buyer = new FacturXBuyerModel { /* ... */ },
Lines = invoice.Lines.Select(MapLine).ToList(),
VatBreakdown = ComputeVatBreakdown(invoice),
Totals = ComputeTotals(invoice)
};
// 2. Générer l'XML CII
var xml = generator.Generate(model, FacturXProfile.EN16931);
// 3. (Optionnel) Valider la structure XSD avant fusion
var report = await validator.ValidateAsync(xml, ct);
if (!report.IsValid)
throw new InvalidOperationException(
$"Factur-X invalid: {string.Join("; ", report.Errors.Select(e => e.Message))}");
// 4. Fusionner XML + PDF source → PDF/A-3 Factur-X
return await fusion.EmbedAsync(sourcePdf, xml, FacturXProfile.EN16931, ct);
}
}
Workflow RTF / Word / Excel → Factur-X
Cette lib ne gère que le PDF en entrée. La conversion d'un document source (RTF, DOCX,
XLSX, HTML, etc.) vers PDF est volontairement déléguée à d'autres libs dédiées du mono-repo
AIT, pour garder Access.It.Pdf.Facturx en pur MIT (sans dépendance commerciale Aspose).
Combine avec :
Access.It.Pdf.WordToPdf— Word / RTF → PDF (via Aspose.Words)Access.It.Pdf.ExcelToPdf— Excel → PDF (via Aspose.Cells)
Pattern recommandé pour un workflow RTF → Factur-X :
public class RtfToFacturX(
IWordToPdfConverter wordConverter, // Access.It.Pdf.WordToPdf
IFacturXXmlGenerator generator, // Access.It.Pdf.Facturx
IFacturXFusion fusion) // Access.It.Pdf.Facturx
{
public async Task<byte[]> ConvertAsync(byte[] rtfBytes, FacturXInvoiceModel invoice, CancellationToken ct)
{
var pdfBytes = await wordConverter.ConvertRtfToPdfAsync(rtfBytes, ct);
var xml = generator.Generate(invoice);
return await fusion.EmbedAsync(pdfBytes, xml, FacturXProfile.EN16931, ct);
}
}
Calcul + validation pré-XML (FacturXCalculator + IFacturXModelValidator)
Plutôt que de calculer la VAT breakdown et les totaux à la main côté caller (source de bugs d'arrondi BR-CO-10/13/14/16), on bâtit le modèle à partir des lignes seules :
using Access.It.Facturx.Services;
using Access.It.Facturx.Services.Abstract;
var lines = invoice.Lines.Select(MapLine).ToList();
var breakdown = FacturXCalculator.ComputeVatBreakdown(lines, documentAllowances: null);
var totals = FacturXCalculator.ComputeTotals(lines, breakdown);
var model = new FacturXInvoiceModel
{
/* en-tête + Seller + Buyer */
Lines = lines,
VatBreakdown = breakdown,
Totals = totals
};
// Pré-validation rapide (in-memory, pas d'XML) : champs requis + cohérence totaux
var preReport = modelValidator.Validate(model);
if (!preReport.IsValid)
throw new InvalidOperationException(string.Join("; ", preReport.Errors.Select(e => e.Message)));
var xml = generator.Generate(model);
Le contrat est : la sortie de FacturXCalculator satisfait toujours IFacturXModelValidator
(testé). Utiliser les deux ensemble élimine la classe de bugs « total différent de la somme
des lignes ».
Arrondi : 2 décimales, MidpointRounding.AwayFromZero (arrondi commercial français).
Auto-détection du profil (FacturXProfileResolver)
Plutôt que de hard-coder EN16931 partout, demander à la lib le profil le plus simple
compatible avec un modèle donné :
using Access.It.Facturx.Services;
var profile = FacturXProfileResolver.DetectMinimal(model);
var xml = generator.Generate(model, profile);
var pdf = await fusion.EmbedAsync(sourcePdf, xml, profile);
Heuristique :
- Aucune ligne →
Minimum - Lignes simples (TVA standard
Suniquement, pas d'allowance/charge/exemption, 1 seule TVA, pas de payment means, pas de période, pas de ShipTo, pas de précédente facture) →Basic - Toute feature avancée (autoliquidation, exemption, document allowance, multi-TVA, ShipTo,
payment means, période, credit note référençant une facture) →
EN16931
Extended n'est pas détecté automatiquement (aucun champ Extended-only n'est exposé par
le modèle actuel — le caller doit le sélectionner explicitement si besoin).
Adresse de routage française (FrCtcElectronicAddressBuilder)
Le PPF ne route pas sur le SIRET brut : la valeur du <URIID> (BT-34 Seller / BT-49 Buyer)
se compose à partir du SIREN, sous le schemeID 0225.
using Access.It.Facturx.Models.Enums;
using Access.It.Facturx.Services;
var buyer = new FacturXBuyerModel
{
// ...
ElectronicAddress = FrCtcElectronicAddressBuilder.TryBuild(
"33514718700116", FrCtcAddressingMode.SiretWithServiceCode, "COMPTA")
// → Value = "335147187_33514718700116_COMPTA", SchemeId = "0225"
};
FrCtcAddressingMode |
Valeur produite |
|---|---|
Siren |
SIREN |
Siret |
SIREN_SIRET |
SirenWithServiceCode |
SIREN_codeService |
SiretWithServiceCode |
SIREN_SIRET_codeService |
Le formatage de l'identifiant est ignoré (335 147 187 00116 est accepté). TryBuild
renvoie null si l'identifiant ne porte pas de SIREN exploitable — moins de 9 chiffres,
ou SIREN entièrement à zéro. Quand un code service est attendu mais absent, le mode est
dégradé vers sa variante sans code service plutôt que de produire une adresse tronquée.
TryBuildValue renvoie la seule chaîne, pour composer une adresse dérivée.
Attention : se tromper ici ne déclenche aucune erreur de validation. L'XML reste conforme et la fusion réussit, mais la facture n'atteint pas son destinataire.
Régime de TVA français (FrVatRegimeResolver)
Détermine la catégorie de TVA (BT-118 / BT-151), le code d'exonération (BT-121) et le motif littéral (BT-120) pour un vendeur français.
var regime = FrVatRegimeResolver.Resolve(
buyerCountryCode: "BE",
buyerIsEuropean: true,
businessProcess: BusinessProcessCode.ServicesInvoice);
// → AE / VATEX_EU_AE / "Autoliquidation - Service intracommunautaire UE - Art. 259B CGI"
| Situation | Catégorie | Code | Fondement |
|---|---|---|---|
| Acheteur hors UE | G |
VATEX_EU_G |
Art. 262 I CGI |
| Acheteur UE, services | AE |
VATEX_EU_AE |
Art. 259B CGI |
| Acheteur UE, biens | AE |
VATEX_EU_IC |
Art. 262 ter I CGI |
| FR, métaux et ferrailles | AE |
VATEX_FR_AE |
Art. 283-2 sexies CGI |
| FR, franchise en base | E |
VATEX_FR_FRANCHISE |
Art. 293 B CGI |
| FR, navire maritime | E |
VATEX_EU_148 |
Art. 262 II CGI |
| FR, cas général | S |
— | — |
La géographie de l'acheteur prime sur les exonérations de droit interne : un acheteur hors de France ne peut pas relever d'une franchise française. Un code pays null ou vide est traité comme la France. Le numéro IMO, s'il est fourni, est ajouté au motif du navire.
Lignes d'adresse (FacturXAddressLineBuilder)
EN 16931 n'accepte que trois lignes d'adresse de 50 caractères (BT-35 / BT-36 / BT-37).
var (one, two, three) = FacturXAddressLineBuilder.Split(
"Zone industrielle de la Pilaterie", "rue des Chateaux", "CS 53041");
La coupe se fait sur les espaces pour ne pas casser un mot. Un mot plus long que la limite est tronçonné, faute d'alternative. Ce qui dépasse trois lignes est abandonné : une quatrième ligne ferait échouer la validation XSD plutôt qu'enrichir l'adresse. Les fragments null ou blancs sont ignorés, on peut donc passer des champs optionnels tels quels.
Numéro de TVA intracommunautaire (FacturXVatIdentifier)
if (!FacturXVatIdentifier.HasCountryPrefix(vatId))
throw new InvalidOperationException($"BT-31 : préfixe pays manquant sur '{vatId}'.");
EN 16931 impose le préfixe pays sur deux lettres. Un numéro français saisi sans son FR
passe l'XSD, qui ne voit qu'une chaîne, mais sera rejeté à la réception. Le contrôle porte
sur la seule forme — la validité réelle du numéro relève de VIES.
API RAM-efficient (Stream)
IFacturXFusion, IFacturXImporter et IFacturXPdfaCompliancePreCheck exposent chacun
deux overloads :
// byte[] : simple, idéal pour des PDFs courts et un usage ponctuel
Task<byte[]> EmbedAsync(byte[] pdfBytes, string xml, FacturXProfile profile, CancellationToken ct);
// Stream : RAM-efficient, idéal pour services web et batches
Task EmbedAsync(Stream input, Stream output, string xml, FacturXProfile profile, CancellationToken ct);
Le caller peut alors streamer directement depuis un HttpRequest.Body ou un FileStream
vers la sortie sans tout charger en mémoire :
// Endpoint ASP.NET Core - peak RAM ~taille du PDF (vs ~3x en byte[])
[HttpPost("/api/invoices/facturx")]
public async Task EmbedAsync(IFacturXFusion fusion, HttpContext ctx)
{
ctx.Response.ContentType = "application/pdf";
await fusion.EmbedAsync(ctx.Request.Body, ctx.Response.Body, xml, profile, ctx.RequestAborted);
}
Les streams fournis ne sont pas disposés par la lib (responsabilité du caller).
Si l'input est non-seekable (HTTP body, NetworkStream), un buffer interne MemoryStream
est utilisé (limitation PdfSharp qui exige seekable). Pour les inputs seekable
(FileStream, MemoryStream), aucune allocation supplémentaire.
Validation XSD officielle (par profil)
IFacturXXsdValidator valide l'XML contre le schéma XSD officiel UN/CEFACT correspondant au
profil cible. Plus strict que le validateur structurel (vérifie types, multiplicités, ordre,
attributs) — équivalent de la validation officielle des PDPs.
using Access.It.Facturx.Services.Abstract;
using Access.It.Facturx.Models.Enums;
public class StrictFacturXValidator(IFacturXXsdValidator xsdValidator)
{
public async Task EnsureValidAsync(string xml, FacturXProfile profile, CancellationToken ct)
{
var report = await xsdValidator.ValidateAsync(xml, profile, ct);
if (!report.IsValid)
throw new InvalidOperationException(
$"Factur-X XSD invalide pour profil {profile} : {string.Join("; ", report.Errors.Select(e => e.Message))}");
}
}
Les 4 fichiers XSD officiels par profil (root + QualifiedDataType + ReusableAggregate +
UnqualifiedDataType) sont embarqués comme ressources. Le XmlSchemaSet est mis en cache
par profil (build coûteux mais réutilisable).
Lecture d'un Factur-X reçu
Use case côté consommateur : tu reçois une facture Factur-X via ta PDP / email, tu veux extraire l'XML structuré pour l'intégrer automatiquement dans ta compta.
using Access.It.Facturx.Services.Abstract;
public class IncomingInvoiceParser(IFacturXImporter importer)
{
public async Task<string?> ExtractInvoiceDataAsync(byte[] receivedPdfBytes, CancellationToken ct)
{
// Returns the CII XML string, or null if the PDF is not a Factur-X
return await importer.ReadEmbeddedXmlAsync(receivedPdfBytes, ct);
}
}
Stratégie de lookup (premier match) :
- Entrée nommée
factur-x.xmldans/Catalog/Names/EmbeddedFiles - Tout fichier embarqué dont le Subtype MIME est
text/xmlouapplication/xmlET qui contientCrossIndustryInvoice(filtrage par contenu)
Le roundtrip Fusion → Importer est garanti byte-exact (testé).
Profils Factur-X supportés
| Profil | Description |
|---|---|
Minimum |
Profil minimal (totaux + identité) |
Basic |
Données structurées de base |
EN16931 |
Profil cible légal européen complet (défaut) |
Extended |
EN 16931 + champs métier supplémentaires |
Couverture XSD / BR-*
Conformité XSD CII garantie par construction
- Ordre des éléments
SupplyChainTradeTransaction: lignes avant blocs header BusinessProcessSpecifiedDocumentContextParameter(suffixeParameterobligatoire)TradeParty:ID→Name→SpecifiedLegalOrganization→PostalTradeAddress→URIUniversalCommunication→SpecifiedTaxRegistrationPostalTradeAddress:PostcodeCode→LineOne→LineTwo→LineThree→CityName→CountryID- BR-O-02 : VAT IDs omis si une ligne utilise catégorie
O
Validation runtime par FacturXValidator (~20 BR critiques)
- BG-2, BG-4, BG-22, BG-23, BG-25 (blocs obligatoires)
- BT-1, BT-2, BT-3, BT-5, BT-24, BT-27, BT-40, BT-44, BT-55 (champs en-tête)
- BR-5, BR-6, BR-7, BR-9, BR-11 (validations Seller/Buyer/Currency)
- BR-12 à BR-16, BR-CO-15, BR-CO-25 (totaux + lignes)
Conformité PDF/A-3 (fusion)
pdfaid:part=3,pdfaid:conformance=B(XMP)fx:DocumentType=INVOICE,fx:DocumentFileName,fx:Version,fx:ConformanceLevel(XMP Factur-X)pdfaExtension:schemas(déclaration namespacefx:)/Catalog/AF(Associated Files PDF/A-3)/Catalog/Names/EmbeddedFiles(name tree avecfactur-x.xml)/Catalog/OutputIntentsavec profil sRGB IEC61966-2.1 ICC (GTS_PDFA1)/Filespecavec/Subtype=/text/xml+/AFRelationship=/Alternative/EmbeddedFilestream avec MIMEtext/xml
Limites connues
Matrice de conformité EN 16931 détaillée
Une matrice par règle BR-* (avec mécanisme de couverture par règle, statut détaillé,
gap analysis) est disponible dans EN16931-COVERAGE.md (à la racine du package).
Synthèse : ~40-45% des 138 BR-* Schematron couvertes structurellement, ~90% des règles
qui causent un rejet PDP en pratique (totaux BR-CO + codelist BR-CL + XSD + PDF/A).
Validation Schematron complète
La validation des 138 règles BR-* EN 16931 nécessite un moteur XSLT 2.0 :
- Saxon-HE NuGet est .NET Framework only
- SaxonCS .NET est commercial
Le Schematron officiel EN16931-CII-validation.xslt est embarqué comme ressource pour exécution par un sidecar Java/Saxon ou un job CI saxonche.
FacturXValidator couvre uniquement la validation structurelle critique (~20 BR-*).
Conformité PDF/A-3 stricte du PDF source
La fusion ajoute tous les marqueurs Factur-X requis, mais la conformité PDF/A-3 stricte dépend du PDF source :
- Fonts embedded (pas Helvetica built-in)
- Pas de glyphs
.notdef(caractères absents du font) CIDToGIDMapsur les CIDFonts- Pas de
Transparencygroupe sur les pages
Ces erreurs ISO 19005-1 sont à fixer côté générateur du PDF source (template Aspose / iText / Word / Carbone / etc.).
IFacturXPdfaCompliancePreCheck peut être appelé avant fusion pour signaler ces écarts récurrents :
using Access.It.Facturx.Services.Abstract;
public class PreFlight(IFacturXPdfaCompliancePreCheck preCheck)
{
public void Inspect(byte[] sourcePdf)
{
var report = preCheck.Inspect(sourcePdf);
foreach (var err in report.Errors)
Console.WriteLine($"[{err.RuleId}] {err.Location} → {err.Message}");
}
}
Détections couvertes :
ISO19005-1:6.3.4— Type 1 built-in (Helvetica, Times-Roman, Courier...) utilisé sans embeddingISO19005-1:6.3.5— Font sans/FontFile,/FontFile2ou/FontFile3dans/FontDescriptorISO19005-1:6.3.7— Type0 sans/DescendantFontsISO19005-1:6.3.8— CIDFontType2 sans/CIDToGIDMapISO19005-1:6.4—/Group /S=/Transparencysur une pageISO19005-1:6.5.3— Annotation sans/AP(warning)
Heuristique (pas un validateur ISO 19005-3 complet façon veraPDF), mais couvre 80% des cas récurrents avec Carbone / Word / Aspose.
Validation FNFE-MPE
Testé sur le validateur officiel services.fnfe-mpe.org :
- ✅ Structure XSD CII conforme
- ✅ XMP Factur-X (tous tags
fx:*+pdfaid:part=3) - ✅ OutputIntent sRGB (résout les erreurs DeviceRGB/DeviceGray ISO 19005-1)
- ✅ MIME type
text/xmlsur le filespec ET sur l'embedded file stream
Licence
Voir licence Access.It mono-repo.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.8)
- PdfSharp (>= 7.0.0-preview-1)
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.8)
- PdfSharp (>= 7.0.0-preview-1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.6.5 | 55 | 9/10/2026 |
| 10.6.4 | 45 | 9/10/2026 |
| 10.6.3 | 96 | 9/8/2026 |
| 10.6.2 | 89 | 9/8/2026 |
| 10.6.1 | 196 | 8/28/2026 |
| 10.6.0 | 96 | 8/28/2026 |
| 10.5.1 | 111 | 8/6/2026 |
| 10.5.0 | 108 | 8/4/2026 |
| 10.4.0 | 109 | 8/4/2026 |
| 10.3.1 | 109 | 7/30/2026 |
| 10.3.0 | 95 | 7/22/2026 |
| 10.2.0 | 107 | 7/22/2026 |
| 10.1.2 | 112 | 7/16/2026 |
| 10.1.1 | 109 | 7/15/2026 |
| 10.1.0 | 133 | 6/30/2026 |
| 10.0.3 | 120 | 6/19/2026 |
| 10.0.2 | 125 | 6/11/2026 |
| 10.0.2-beta.1 | 67 | 5/13/2026 |
| 10.0.1 | 130 | 3/29/2026 |
| 10.0.0 | 125 | 3/18/2026 |