@stackforge-eu/factur-x 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.7] — 2026-05-27
11
+
12
+ - **`fx:ConformanceLevel` in the XMP metadata used the raw `Profile` enum
13
+ identifier instead of the Factur-X / ZUGFeRD spec label.** The XMP packet
14
+ emitted `EN16931` / `BASIC_WL` where the spec requires `EN 16931` /
15
+ `BASIC WL`, so strict ZUGFeRD / Factur-X validators (Mustangproject, the
16
+ FNFE-MPE check tool, veraPDF's Factur-X profile) rejected every PDF this
17
+ library produced even though the embedded CII XML was correct. Added a
18
+ `Profile` → XMP-label mapping and routed `buildXmpMetadata` through it in
19
+ `src/core/embed.ts` (thanks @sco-indy, #2).
20
+ - **`flavor: Flavor.XRECHNUNG` still advertised `EN 16931` in the XMP packet.**
21
+ Hybrid XRechnung PDFs carry the EN16931 profile internally but per ZUGFeRD
22
+ 2.x §2.4.4 must declare `XRECHNUNG` as the conformance level so the KoSIT /
23
+ ZUGFeRD validators route them through the XRechnung schematron set rather
24
+ than the vanilla Factur-X one. `resolveXmpConformanceLevel` in
25
+ `src/core/embed.ts` now special-cases the XRechnung flavor; the existing
26
+ `toXRechnung` → XML flow is unaffected. Regression test added in
27
+ `tests/embed.test.ts` covering all five profiles and the XRechnung flavor.
28
+
29
+ ## [1.0.6] — 2026-05-23
30
+
31
+ - **`SpecifiedTradeSettlementPaymentMeans` emitted its child elements in the
32
+ wrong order.** The EN16931 / CII XSD `TradeSettlementPaymentMeansType`
33
+ sequence requires `PayerPartyDebtorFinancialAccount` before
34
+ `PayeePartyCreditorFinancialAccount` and
35
+ `PayeeSpecifiedCreditorFinancialInstitution`, but the builder appended the
36
+ debtor account last. Any invoice combining a SEPA direct debit
37
+ (`payment.meansCode = "59"`) with `payment.debtorIban` plus a payee
38
+ IBAN/BIC failed XSD validation with `Element 'PayerPartyDebtorFinancialAccount':
39
+ This element is not expected`. Reordered the three blocks in
40
+ `src/core/xml-builder.ts`; regression test added in
41
+ `tests/xml-builder.test.ts`.
42
+
10
43
  ## [1.0.5] — 2026-05-12
11
44
 
12
45
  - **`ChargeIndicator`'s inner `Indicator` element was emitted in the wrong XML
@@ -80,6 +113,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
80
113
  - Deno compatibility test suite
81
114
 
82
115
  [Unreleased]: https://github.com/StackForge-EU/factur-x/commits/main
116
+ [1.0.7]: https://github.com/StackForge-EU/factur-x/compare/v1.0.6...v1.0.7
117
+ [1.0.6]: https://github.com/StackForge-EU/factur-x/compare/v1.0.5...v1.0.6
83
118
  [1.0.5]: https://github.com/StackForge-EU/factur-x/compare/v1.0.4...v1.0.5
84
119
  [1.0.4]: https://github.com/StackForge-EU/factur-x/compare/v1.0.3...v1.0.4
85
120
  [1.0.3]: https://github.com/StackForge-EU/factur-x/compare/v1.0.2...v1.0.3
package/dist/index.cjs CHANGED
@@ -591,6 +591,11 @@ function buildXml(input, profile, flavor) {
591
591
  sett += tag("ram:PayeeTradeParty", buildParty(input.payee, profile));
592
592
  if (payment?.meansCode && atLeast(profile, "BASIC_WL" /* BASIC_WL */)) {
593
593
  let pm = tag("ram:TypeCode", escapeXml(payment.meansCode));
594
+ if (payment.debtorIban)
595
+ pm += tag(
596
+ "ram:PayerPartyDebtorFinancialAccount",
597
+ tag("ram:IBANID", escapeXml(payment.debtorIban))
598
+ );
594
599
  if (payment.iban || payment.accountId || payment.accountName) {
595
600
  let acct = "";
596
601
  if (payment.iban) acct += tag("ram:IBANID", escapeXml(payment.iban));
@@ -604,11 +609,6 @@ function buildXml(input, profile, flavor) {
604
609
  "ram:PayeeSpecifiedCreditorFinancialInstitution",
605
610
  tag("ram:BICID", escapeXml(payment.bic))
606
611
  );
607
- if (payment.debtorIban)
608
- pm += tag(
609
- "ram:PayerPartyDebtorFinancialAccount",
610
- tag("ram:IBANID", escapeXml(payment.debtorIban))
611
- );
612
612
  sett += tag("ram:SpecifiedTradeSettlementPaymentMeans", pm);
613
613
  }
614
614
  if (input.vatBreakdown && atLeast(profile, "BASIC_WL" /* BASIC_WL */))
@@ -1111,6 +1111,17 @@ var AF_RELATIONSHIP_MAP = {
1111
1111
  Data: pdfLib.AFRelationship.Data,
1112
1112
  Source: pdfLib.AFRelationship.Source
1113
1113
  };
1114
+ var XMP_CONFORMANCE_LEVEL = {
1115
+ ["MINIMUM" /* MINIMUM */]: "MINIMUM",
1116
+ ["BASIC_WL" /* BASIC_WL */]: "BASIC WL",
1117
+ ["BASIC" /* BASIC */]: "BASIC",
1118
+ ["EN16931" /* EN16931 */]: "EN 16931",
1119
+ ["EXTENDED" /* EXTENDED */]: "EXTENDED"
1120
+ };
1121
+ function resolveXmpConformanceLevel(profile, flavor) {
1122
+ if (flavor === "xrechnung" /* XRECHNUNG */) return "XRECHNUNG";
1123
+ return XMP_CONFORMANCE_LEVEL[profile];
1124
+ }
1114
1125
  function ensureOutputIntent(pdfDoc, iccBytes, identifier) {
1115
1126
  const existing = pdfDoc.catalog.lookup(pdfLib.PDFName.of("OutputIntents"));
1116
1127
  if (existing instanceof pdfLib.PDFArray && existing.size() > 0) return;
@@ -1375,7 +1386,7 @@ async function embedFacturX(options) {
1375
1386
  createDate: isoDate,
1376
1387
  modifyDate: isoDate,
1377
1388
  documentFileName: flavorConfig.attachmentFilename,
1378
- conformanceLevel: profile
1389
+ conformanceLevel: resolveXmpConformanceLevel(profile, flavor)
1379
1390
  });
1380
1391
  const xmpBytes = new TextEncoder().encode(xmpXml);
1381
1392
  const metadataStream = pdfDoc.context.stream(xmpBytes, {
package/dist/index.js CHANGED
@@ -561,6 +561,11 @@ function buildXml(input, profile, flavor) {
561
561
  sett += tag("ram:PayeeTradeParty", buildParty(input.payee, profile));
562
562
  if (payment?.meansCode && atLeast(profile, "BASIC_WL" /* BASIC_WL */)) {
563
563
  let pm = tag("ram:TypeCode", escapeXml(payment.meansCode));
564
+ if (payment.debtorIban)
565
+ pm += tag(
566
+ "ram:PayerPartyDebtorFinancialAccount",
567
+ tag("ram:IBANID", escapeXml(payment.debtorIban))
568
+ );
564
569
  if (payment.iban || payment.accountId || payment.accountName) {
565
570
  let acct = "";
566
571
  if (payment.iban) acct += tag("ram:IBANID", escapeXml(payment.iban));
@@ -574,11 +579,6 @@ function buildXml(input, profile, flavor) {
574
579
  "ram:PayeeSpecifiedCreditorFinancialInstitution",
575
580
  tag("ram:BICID", escapeXml(payment.bic))
576
581
  );
577
- if (payment.debtorIban)
578
- pm += tag(
579
- "ram:PayerPartyDebtorFinancialAccount",
580
- tag("ram:IBANID", escapeXml(payment.debtorIban))
581
- );
582
582
  sett += tag("ram:SpecifiedTradeSettlementPaymentMeans", pm);
583
583
  }
584
584
  if (input.vatBreakdown && atLeast(profile, "BASIC_WL" /* BASIC_WL */))
@@ -1081,6 +1081,17 @@ var AF_RELATIONSHIP_MAP = {
1081
1081
  Data: AFRelationship.Data,
1082
1082
  Source: AFRelationship.Source
1083
1083
  };
1084
+ var XMP_CONFORMANCE_LEVEL = {
1085
+ ["MINIMUM" /* MINIMUM */]: "MINIMUM",
1086
+ ["BASIC_WL" /* BASIC_WL */]: "BASIC WL",
1087
+ ["BASIC" /* BASIC */]: "BASIC",
1088
+ ["EN16931" /* EN16931 */]: "EN 16931",
1089
+ ["EXTENDED" /* EXTENDED */]: "EXTENDED"
1090
+ };
1091
+ function resolveXmpConformanceLevel(profile, flavor) {
1092
+ if (flavor === "xrechnung" /* XRECHNUNG */) return "XRECHNUNG";
1093
+ return XMP_CONFORMANCE_LEVEL[profile];
1094
+ }
1084
1095
  function ensureOutputIntent(pdfDoc, iccBytes, identifier) {
1085
1096
  const existing = pdfDoc.catalog.lookup(PDFName.of("OutputIntents"));
1086
1097
  if (existing instanceof PDFArray && existing.size() > 0) return;
@@ -1345,7 +1356,7 @@ async function embedFacturX(options) {
1345
1356
  createDate: isoDate,
1346
1357
  modifyDate: isoDate,
1347
1358
  documentFileName: flavorConfig.attachmentFilename,
1348
- conformanceLevel: profile
1359
+ conformanceLevel: resolveXmpConformanceLevel(profile, flavor)
1349
1360
  });
1350
1361
  const xmpBytes = new TextEncoder().encode(xmpXml);
1351
1362
  const metadataStream = pdfDoc.context.stream(xmpBytes, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackforge-eu/factur-x",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "type": "module",
5
5
  "description": "Embed Factur-X, ZUGFeRD, and XRechnung structured invoice XML into PDF/A-3 documents",
6
6
  "keywords": [