@stackforge-eu/factur-x 1.1.0 → 1.2.0

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,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.0] — 2026-07-11
11
+
12
+ - **Updated to Factur-X 1.09 / ZUGFeRD 2.5** (spec dated 2026-06-10). Factur-X
13
+ 1.09 keeps the same UN/CEFACT CII **D22B** syntax, namespaces, and profile URNs
14
+ (`urn:factur-x.eu:1p0:*`) as 1.08, so this is a backward-compatible refresh:
15
+ XML produced by previous versions remains valid, and no input/output changes
16
+ are required.
17
+ - Bundled XSD schemas under `schema/` swapped to the official 1.09 set
18
+ (filenames normalized to the repo convention, internal `schemaLocation`
19
+ references rewritten to match). For MINIMUM / BASIC WL / BASIC / EN 16931 the
20
+ XSDs are content-identical to 1.08; the EXTENDED XSD gains only **optional**
21
+ additions (debited-account name, financial adjustment / charges on behalf of
22
+ a third party, an optional `listID` attribute on the allowance/charge reason
23
+ code).
24
+ - Reference `.sch` (Schematron) and `_codedb.xml` files updated to the 1.09
25
+ code lists and CEN validation rules (effective 2026-05-15). These are not
26
+ shipped in the npm package and not used at runtime — this library performs
27
+ XSD validation only.
28
+ - Documentation and code comments updated from "1.08 / 2.4" to "1.09 / 2.5".
29
+
30
+ - **New optional EXTENDED-profile input fields** for selected Factur-X 1.09
31
+ business terms (all additive, only emitted at `Profile.EXTENDED`):
32
+ - `PaymentInput.debtorAccountName` (BT-216) and `PaymentInput.debtorBic`
33
+ (BT-215) — debited-account name and its payment service provider BIC.
34
+ - `AllowanceChargeInput.exemptionReason` / `exemptionReasonCode`
35
+ (BT-173–176) — VAT exemption reason/code on document-level allowances and
36
+ charges.
37
+ - `FacturXInvoiceInput.financialAdjustments` (BG-34, new
38
+ `FinancialAdjustmentInput` with `reason` BT-180 + `amount` BT-179) —
39
+ charges collected on behalf of a third party.
40
+ - `InvoiceLineInput.manufacturer` (BG-X-94) — line-level product manufacturer
41
+ party (`ram:ManufacturerTradeParty`).
42
+ - Remaining 1.09 EXTENDED terms (e.g. document-level non-VAT tax codes,
43
+ logistic-service-charge exemption reasons) are still not emitted and can be
44
+ added later without breaking changes.
45
+
46
+ - **Fixed:** `ram:FormattedIssueDateTime` now emits a `qdt:DateTimeString` child
47
+ instead of `udt:DateTimeString`. It is typed as `qdt:FormattedDateTimeType` in
48
+ the CII XSD, so the previous `udt:` prefix failed XSD/Schematron validation for
49
+ every credit note or cancellation invoice referencing a dated preceding
50
+ invoice ([#5](https://github.com/StackForge-EU/factur-x/issues/5)).
51
+ - **Fixed:** XSD validation no longer exhausts libxml2's fixed-size input-callback
52
+ table. `validateXsd` now clears the table on each call, so validating many
53
+ documents in one process no longer causes spurious failures once ~15
54
+ validations have run.
55
+
10
56
  ## [1.1.0] — 2026-05-27
11
57
 
12
58
  - **BREAKING:** Every `ram:GlobalID` and the legal-organization `ram:ID` now
@@ -153,6 +199,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
153
199
  - Deno compatibility test suite
154
200
 
155
201
  [Unreleased]: https://github.com/StackForge-EU/factur-x/commits/main
202
+ [1.2.0]: https://github.com/StackForge-EU/factur-x/compare/v1.1.0...v1.2.0
156
203
  [1.1.0]: https://github.com/StackForge-EU/factur-x/compare/v1.0.8...v1.1.0
157
204
  [1.0.8]: https://github.com/StackForge-EU/factur-x/compare/v1.0.7...v1.0.8
158
205
  [1.0.7]: https://github.com/StackForge-EU/factur-x/compare/v1.0.6...v1.0.7
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  This TypeScript/Node.js package takes a simple, typed invoice input object and:
14
14
 
15
15
  1. Validates it against profile requirements
16
- 2. Generates UN/CEFACT CII XML (Factur-X 1.08 / ZUGFeRD 2.4)
16
+ 2. Generates UN/CEFACT CII XML (Factur-X 1.09 / ZUGFeRD 2.5)
17
17
  3. Validates the XML against official XSD schemas (via WASM)
18
18
  4. Embeds it into a PDF with PDF/A-3b metadata
19
19
  5. Or outputs standalone XRechnung XML for German B2G use
@@ -68,8 +68,8 @@ import { embedFacturX, Profile } from "jsr:@stackforge-eu/factur-x";
68
68
 
69
69
  | Flavor | Region | Output | Notes |
70
70
  | ------------------- | ----------- | -------- | ----------------------------- |
71
- | `Flavor.FACTUR_X` | France / EU | PDF/A-3 | Default, Factur-X 1.08 |
72
- | `Flavor.ZUGFERD` | Germany | PDF/A-3 | ZUGFeRD 2.4 (= Factur-X 1.08) |
71
+ | `Flavor.FACTUR_X` | France / EU | PDF/A-3 | Default, Factur-X 1.09 |
72
+ | `Flavor.ZUGFERD` | Germany | PDF/A-3 | ZUGFeRD 2.5 (= Factur-X 1.09) |
73
73
  | `Flavor.XRECHNUNG` | Germany B2G | XML only | PEPPOL business process |
74
74
  | `Flavor.CHRONO_PRO` | Belgium | PDF/A-3 | Chrono Pro conventions |
75
75
 
package/dist/index.cjs CHANGED
@@ -235,7 +235,7 @@ var FLAVOR_CONFIGS = {
235
235
  defaultTypeCode: "380" /* COMMERCIAL_INVOICE */,
236
236
  attachmentFilename: "factur-x.xml",
237
237
  embedInPdf: true,
238
- description: "German electronic invoice format. ZUGFeRD 2.4 uses factur-x.xml."
238
+ description: "German electronic invoice format. ZUGFeRD 2.5 uses factur-x.xml."
239
239
  },
240
240
  ["xrechnung" /* XRECHNUNG */]: {
241
241
  name: "XRechnung",
@@ -312,7 +312,8 @@ function tag(name, content, attrs) {
312
312
  return `<${name}${a}>${content}</${name}>`;
313
313
  }
314
314
  function dateEl(wrapper, isoDate) {
315
- return tag(wrapper, tag("udt:DateTimeString", formatDate(isoDate), { format: "102" }));
315
+ const child = wrapper === "ram:FormattedIssueDateTime" ? "qdt:DateTimeString" : "udt:DateTimeString";
316
+ return tag(wrapper, tag(child, formatDate(isoDate), { format: "102" }));
316
317
  }
317
318
  function buildAddress(addr) {
318
319
  let x = "";
@@ -393,6 +394,8 @@ function buildLineItem(line, profile) {
393
394
  if (line.description) prod += tag("ram:Description", escapeXml(line.description));
394
395
  if (line.originCountry && atLeast(profile, "EN16931" /* EN16931 */))
395
396
  prod += tag("ram:OriginTradeCountry", tag("ram:ID", escapeXml(line.originCountry)));
397
+ if (line.manufacturer && atLeast(profile, "EXTENDED" /* EXTENDED */))
398
+ prod += tag("ram:ManufacturerTradeParty", buildParty(line.manufacturer, profile));
396
399
  let agree = "";
397
400
  if (line.buyerOrderLineId && atLeast(profile, "EN16931" /* EN16931 */))
398
401
  agree += tag(
@@ -427,7 +430,7 @@ function buildLineItem(line, profile) {
427
430
  tag("ram:AssociatedDocumentLineDocument", doc) + tag("ram:SpecifiedTradeProduct", prod) + tag("ram:SpecifiedLineTradeAgreement", agree) + tag("ram:SpecifiedLineTradeDelivery", delivery) + tag("ram:SpecifiedLineTradeSettlement", settle)
428
431
  );
429
432
  }
430
- function buildAllowanceCharge(ac) {
433
+ function buildAllowanceCharge(ac, profile) {
431
434
  let x = tag("ram:ChargeIndicator", tag("udt:Indicator", ac.isCharge ? "true" : "false"));
432
435
  if (ac.percent !== void 0) x += tag("ram:CalculationPercent", ac.percent.toString());
433
436
  if (ac.baseAmount !== void 0) x += tag("ram:BasisAmount", fmtAmt(ac.baseAmount));
@@ -435,8 +438,13 @@ function buildAllowanceCharge(ac) {
435
438
  if (ac.reasonCode) x += tag("ram:ReasonCode", escapeXml(ac.reasonCode));
436
439
  if (ac.reason) x += tag("ram:Reason", escapeXml(ac.reason));
437
440
  if (ac.vatCategoryCode || ac.vatRatePercent !== void 0) {
441
+ const extended = atLeast(profile, "EXTENDED" /* EXTENDED */);
438
442
  let tx = tag("ram:TypeCode", "VAT");
443
+ if (extended && ac.exemptionReason)
444
+ tx += tag("ram:ExemptionReason", escapeXml(ac.exemptionReason));
439
445
  if (ac.vatCategoryCode) tx += tag("ram:CategoryCode", escapeXml(ac.vatCategoryCode));
446
+ if (extended && ac.exemptionReasonCode)
447
+ tx += tag("ram:ExemptionReasonCode", escapeXml(ac.exemptionReasonCode));
440
448
  if (ac.vatRatePercent !== void 0)
441
449
  tx += tag("ram:RateApplicablePercent", ac.vatRatePercent.toString());
442
450
  x += tag("ram:CategoryTradeTax", tx);
@@ -604,11 +612,12 @@ function buildXml(input, profile, flavor) {
604
612
  sett += tag("ram:PayeeTradeParty", buildParty(input.payee, profile));
605
613
  if (payment?.meansCode && atLeast(profile, "BASIC_WL" /* BASIC_WL */)) {
606
614
  let pm = tag("ram:TypeCode", escapeXml(payment.meansCode));
607
- if (payment.debtorIban)
608
- pm += tag(
609
- "ram:PayerPartyDebtorFinancialAccount",
610
- tag("ram:IBANID", escapeXml(payment.debtorIban))
611
- );
615
+ if (payment.debtorIban) {
616
+ let dacct = tag("ram:IBANID", escapeXml(payment.debtorIban));
617
+ if (payment.debtorAccountName && atLeast(profile, "EXTENDED" /* EXTENDED */))
618
+ dacct += tag("ram:AccountName", escapeXml(payment.debtorAccountName));
619
+ pm += tag("ram:PayerPartyDebtorFinancialAccount", dacct);
620
+ }
612
621
  if (payment.iban || payment.accountId || payment.accountName) {
613
622
  let acct = "";
614
623
  if (payment.iban) acct += tag("ram:IBANID", escapeXml(payment.iban));
@@ -617,6 +626,11 @@ function buildXml(input, profile, flavor) {
617
626
  acct += tag("ram:AccountName", escapeXml(payment.accountName));
618
627
  pm += tag("ram:PayeePartyCreditorFinancialAccount", acct);
619
628
  }
629
+ if (payment.debtorBic && atLeast(profile, "EXTENDED" /* EXTENDED */))
630
+ pm += tag(
631
+ "ram:PayerSpecifiedDebtorFinancialInstitution",
632
+ tag("ram:BICID", escapeXml(payment.debtorBic))
633
+ );
620
634
  if (payment.bic && atLeast(profile, "EN16931" /* EN16931 */))
621
635
  pm += tag(
622
636
  "ram:PayeeSpecifiedCreditorFinancialInstitution",
@@ -634,7 +648,7 @@ function buildXml(input, profile, flavor) {
634
648
  sett += tag("ram:BillingSpecifiedPeriod", bp);
635
649
  }
636
650
  if (input.allowancesCharges && atLeast(profile, "BASIC_WL" /* BASIC_WL */))
637
- for (const ac of input.allowancesCharges) sett += buildAllowanceCharge(ac);
651
+ for (const ac of input.allowancesCharges) sett += buildAllowanceCharge(ac, profile);
638
652
  const dueDate = payment?.dueDate ?? doc.dueDate;
639
653
  if (atLeast(profile, "BASIC_WL" /* BASIC_WL */) && (payment?.termsDescription || dueDate || payment?.mandateId)) {
640
654
  let pt = "";
@@ -645,6 +659,14 @@ function buildXml(input, profile, flavor) {
645
659
  sett += tag("ram:SpecifiedTradePaymentTerms", pt);
646
660
  }
647
661
  sett += buildMonetarySummation(totals, profile);
662
+ if (input.financialAdjustments && atLeast(profile, "EXTENDED" /* EXTENDED */)) {
663
+ for (const fa of input.financialAdjustments) {
664
+ sett += tag(
665
+ "ram:SpecifiedFinancialAdjustment",
666
+ tag("ram:Reason", escapeXml(fa.reason)) + tag("ram:ActualAmount", fmtAmt(fa.amount))
667
+ );
668
+ }
669
+ }
648
670
  if (input.references && atLeast(profile, "BASIC_WL" /* BASIC_WL */)) {
649
671
  for (const ref of input.references) {
650
672
  if (ref.type === "preceding") {
@@ -1077,6 +1099,7 @@ async function validateXsd(xml, profile, options) {
1077
1099
  XsdValidator,
1078
1100
  XmlValidateError,
1079
1101
  xmlRegisterInputProvider,
1102
+ xmlCleanupInputProvider,
1080
1103
  XmlBufferInputProvider
1081
1104
  } = await import('libxml2-wasm');
1082
1105
  const schemaDir = path__namespace.dirname(xsdPath);
@@ -1088,6 +1111,7 @@ async function validateXsd(xml, profile, options) {
1088
1111
  buffers[`file://${filePath}`] = buffers[filePath];
1089
1112
  buffers[file] = buffers[filePath];
1090
1113
  }
1114
+ xmlCleanupInputProvider();
1091
1115
  const provider = new XmlBufferInputProvider(buffers);
1092
1116
  xmlRegisterInputProvider(provider);
1093
1117
  let xsdDoc;
@@ -1117,6 +1141,7 @@ async function validateXsd(xml, profile, options) {
1117
1141
  xmlDoc?.dispose();
1118
1142
  validator?.dispose();
1119
1143
  xsdDoc?.dispose();
1144
+ xmlCleanupInputProvider();
1120
1145
  }
1121
1146
  }
1122
1147
  var AF_RELATIONSHIP_MAP = {
package/dist/index.d.cts CHANGED
@@ -633,6 +633,14 @@ interface InvoiceLineInput {
633
633
  * @since EN16931
634
634
  */
635
635
  buyerOrderLineId?: string;
636
+ /**
637
+ * Product manufacturer of this line item. Uses the standard trade-party
638
+ * shape; only id/globalId (BT-X-600), name (BT-X-598), and postal address
639
+ * (BG-X-93) are typically relevant.
640
+ * @see Factur-X EXTENDED BG-X-94, Factur-X 1.09
641
+ * @since EXTENDED
642
+ */
643
+ manufacturer?: TradePartyInput;
636
644
  }
637
645
  /**
638
646
  * Allowance or charge at header or line level.
@@ -666,6 +674,18 @@ interface AllowanceChargeInput {
666
674
  vatCategoryCode?: VatCategoryCode | (string & Record<never, never>);
667
675
  /** VAT rate for this allowance/charge */
668
676
  vatRatePercent?: number;
677
+ /**
678
+ * VAT exemption reason text for this document-level allowance/charge.
679
+ * @see Factur-X EXTENDED BT-173 (allowance) / BT-175 (charge), Factur-X 1.09
680
+ * @since EXTENDED
681
+ */
682
+ exemptionReason?: string;
683
+ /**
684
+ * VAT exemption reason code for this document-level allowance/charge.
685
+ * @see Factur-X EXTENDED BT-174 (allowance) / BT-176 (charge), Factur-X 1.09
686
+ * @since EXTENDED
687
+ */
688
+ exemptionReasonCode?: string;
669
689
  }
670
690
  /**
671
691
  * Payment information.
@@ -726,6 +746,22 @@ interface PaymentInput {
726
746
  * @see EN 16931 BT-91
727
747
  */
728
748
  debtorIban?: string;
749
+ /**
750
+ * Debtor account name (holder of the debited account). Only emitted when
751
+ * {@link PaymentInput.debtorIban} is also set, since the CII schema requires
752
+ * the IBAN on the debtor account element.
753
+ * @see Factur-X EXTENDED BT-216 ("Debited account name", Factur-X 1.09)
754
+ * @since EXTENDED
755
+ */
756
+ debtorAccountName?: string;
757
+ /**
758
+ * BIC/SWIFT of the debtor's payment service provider (the debited account's
759
+ * financial institution).
760
+ * @see Factur-X EXTENDED BT-215 ("Debited account payment service provider
761
+ * identifier", Factur-X 1.09)
762
+ * @since EXTENDED
763
+ */
764
+ debtorBic?: string;
729
765
  /**
730
766
  * Payment terms description (free text).
731
767
  * @see EN 16931 BT-20
@@ -974,6 +1010,30 @@ interface FacturXInvoiceInput {
974
1010
  * @since BASIC_WL
975
1011
  */
976
1012
  sellerTaxRepresentative?: TradePartyInput;
1013
+ /**
1014
+ * Charges collected on behalf of a third party (document level).
1015
+ * @see Factur-X EXTENDED BG-34, Factur-X 1.09
1016
+ * @since EXTENDED
1017
+ */
1018
+ financialAdjustments?: FinancialAdjustmentInput[];
1019
+ }
1020
+ /**
1021
+ * A charge collected on behalf of a third party.
1022
+ *
1023
+ * @see Factur-X EXTENDED BG-34 (`ram:SpecifiedFinancialAdjustment`), Factur-X 1.09
1024
+ * @since EXTENDED
1025
+ */
1026
+ interface FinancialAdjustmentInput {
1027
+ /**
1028
+ * Specification of the charge collected on behalf of a third party.
1029
+ * @see Factur-X EXTENDED BT-180
1030
+ */
1031
+ reason: string;
1032
+ /**
1033
+ * Charge amount collected on behalf of a third party.
1034
+ * @see Factur-X EXTENDED BT-179
1035
+ */
1036
+ amount: number;
977
1037
  }
978
1038
 
979
1039
  /**
@@ -1094,7 +1154,7 @@ declare function escapeXml(value: string): string;
1094
1154
  /**
1095
1155
  * Builds a UN/CEFACT CrossIndustryInvoice XML string from structured input.
1096
1156
  *
1097
- * The output conforms to the Factur-X 1.08 / ZUGFeRD 2.4 CII schema at the
1157
+ * The output conforms to the Factur-X 1.09 / ZUGFeRD 2.5 CII schema at the
1098
1158
  * requested {@link Profile} level. Elements that do not belong to the chosen
1099
1159
  * profile are silently omitted.
1100
1160
  *
@@ -1452,4 +1512,4 @@ interface XRechnungResult {
1452
1512
  */
1453
1513
  declare function toXRechnung(input: FacturXInvoiceInput, options?: XRechnungOptions): XRechnungResult;
1454
1514
 
1455
- export { type AddressInput, type AllowanceChargeInput, type BillingPeriodInput, type ContactInput, type DeliveryInput, type DocumentReferenceInput, DocumentTypeCode, type EmbedOptions, type EmbedResult, type ExtractOptions, type ExtractResult, FLAVOR_CONFIGS, FLAVOR_DEFAULT_TYPE_CODES, type FacturXInvoiceInput, Flavor, type FlavorConfig, type IdentifierWithSchemeInput, type InvoiceDocumentInput, type InvoiceLineInput, type InvoiceTotalsInput, type LegalOrganizationInput, type NoteInput, PROFILE_MAIN_XSD, PROFILE_SCHEMA_DIRS, PROFILE_URNS, type PaymentInput, Profile, type TaxRegistrationInput, type TradePartyInput, UnitCode, type ValidationError, type ValidationResult, type VatBreakdownInput, VatCategoryCode, XRECHNUNG_PROFILE_URN, type XRechnungOptions, type XRechnungResult, type XsdValidateOptions, type XsdValidationError, type XsdValidationResult, buildXml, embedFacturX, escapeXml, extractXml, getFlavorConfig, resolveBusinessProcessUrn, resolveTypeCode, toXRechnung, validateFlavorProfile, validateInput, validateXsd };
1515
+ export { type AddressInput, type AllowanceChargeInput, type BillingPeriodInput, type ContactInput, type DeliveryInput, type DocumentReferenceInput, DocumentTypeCode, type EmbedOptions, type EmbedResult, type ExtractOptions, type ExtractResult, FLAVOR_CONFIGS, FLAVOR_DEFAULT_TYPE_CODES, type FacturXInvoiceInput, type FinancialAdjustmentInput, Flavor, type FlavorConfig, type IdentifierWithSchemeInput, type InvoiceDocumentInput, type InvoiceLineInput, type InvoiceTotalsInput, type LegalOrganizationInput, type NoteInput, PROFILE_MAIN_XSD, PROFILE_SCHEMA_DIRS, PROFILE_URNS, type PaymentInput, Profile, type TaxRegistrationInput, type TradePartyInput, UnitCode, type ValidationError, type ValidationResult, type VatBreakdownInput, VatCategoryCode, XRECHNUNG_PROFILE_URN, type XRechnungOptions, type XRechnungResult, type XsdValidateOptions, type XsdValidationError, type XsdValidationResult, buildXml, embedFacturX, escapeXml, extractXml, getFlavorConfig, resolveBusinessProcessUrn, resolveTypeCode, toXRechnung, validateFlavorProfile, validateInput, validateXsd };
package/dist/index.d.ts CHANGED
@@ -633,6 +633,14 @@ interface InvoiceLineInput {
633
633
  * @since EN16931
634
634
  */
635
635
  buyerOrderLineId?: string;
636
+ /**
637
+ * Product manufacturer of this line item. Uses the standard trade-party
638
+ * shape; only id/globalId (BT-X-600), name (BT-X-598), and postal address
639
+ * (BG-X-93) are typically relevant.
640
+ * @see Factur-X EXTENDED BG-X-94, Factur-X 1.09
641
+ * @since EXTENDED
642
+ */
643
+ manufacturer?: TradePartyInput;
636
644
  }
637
645
  /**
638
646
  * Allowance or charge at header or line level.
@@ -666,6 +674,18 @@ interface AllowanceChargeInput {
666
674
  vatCategoryCode?: VatCategoryCode | (string & Record<never, never>);
667
675
  /** VAT rate for this allowance/charge */
668
676
  vatRatePercent?: number;
677
+ /**
678
+ * VAT exemption reason text for this document-level allowance/charge.
679
+ * @see Factur-X EXTENDED BT-173 (allowance) / BT-175 (charge), Factur-X 1.09
680
+ * @since EXTENDED
681
+ */
682
+ exemptionReason?: string;
683
+ /**
684
+ * VAT exemption reason code for this document-level allowance/charge.
685
+ * @see Factur-X EXTENDED BT-174 (allowance) / BT-176 (charge), Factur-X 1.09
686
+ * @since EXTENDED
687
+ */
688
+ exemptionReasonCode?: string;
669
689
  }
670
690
  /**
671
691
  * Payment information.
@@ -726,6 +746,22 @@ interface PaymentInput {
726
746
  * @see EN 16931 BT-91
727
747
  */
728
748
  debtorIban?: string;
749
+ /**
750
+ * Debtor account name (holder of the debited account). Only emitted when
751
+ * {@link PaymentInput.debtorIban} is also set, since the CII schema requires
752
+ * the IBAN on the debtor account element.
753
+ * @see Factur-X EXTENDED BT-216 ("Debited account name", Factur-X 1.09)
754
+ * @since EXTENDED
755
+ */
756
+ debtorAccountName?: string;
757
+ /**
758
+ * BIC/SWIFT of the debtor's payment service provider (the debited account's
759
+ * financial institution).
760
+ * @see Factur-X EXTENDED BT-215 ("Debited account payment service provider
761
+ * identifier", Factur-X 1.09)
762
+ * @since EXTENDED
763
+ */
764
+ debtorBic?: string;
729
765
  /**
730
766
  * Payment terms description (free text).
731
767
  * @see EN 16931 BT-20
@@ -974,6 +1010,30 @@ interface FacturXInvoiceInput {
974
1010
  * @since BASIC_WL
975
1011
  */
976
1012
  sellerTaxRepresentative?: TradePartyInput;
1013
+ /**
1014
+ * Charges collected on behalf of a third party (document level).
1015
+ * @see Factur-X EXTENDED BG-34, Factur-X 1.09
1016
+ * @since EXTENDED
1017
+ */
1018
+ financialAdjustments?: FinancialAdjustmentInput[];
1019
+ }
1020
+ /**
1021
+ * A charge collected on behalf of a third party.
1022
+ *
1023
+ * @see Factur-X EXTENDED BG-34 (`ram:SpecifiedFinancialAdjustment`), Factur-X 1.09
1024
+ * @since EXTENDED
1025
+ */
1026
+ interface FinancialAdjustmentInput {
1027
+ /**
1028
+ * Specification of the charge collected on behalf of a third party.
1029
+ * @see Factur-X EXTENDED BT-180
1030
+ */
1031
+ reason: string;
1032
+ /**
1033
+ * Charge amount collected on behalf of a third party.
1034
+ * @see Factur-X EXTENDED BT-179
1035
+ */
1036
+ amount: number;
977
1037
  }
978
1038
 
979
1039
  /**
@@ -1094,7 +1154,7 @@ declare function escapeXml(value: string): string;
1094
1154
  /**
1095
1155
  * Builds a UN/CEFACT CrossIndustryInvoice XML string from structured input.
1096
1156
  *
1097
- * The output conforms to the Factur-X 1.08 / ZUGFeRD 2.4 CII schema at the
1157
+ * The output conforms to the Factur-X 1.09 / ZUGFeRD 2.5 CII schema at the
1098
1158
  * requested {@link Profile} level. Elements that do not belong to the chosen
1099
1159
  * profile are silently omitted.
1100
1160
  *
@@ -1452,4 +1512,4 @@ interface XRechnungResult {
1452
1512
  */
1453
1513
  declare function toXRechnung(input: FacturXInvoiceInput, options?: XRechnungOptions): XRechnungResult;
1454
1514
 
1455
- export { type AddressInput, type AllowanceChargeInput, type BillingPeriodInput, type ContactInput, type DeliveryInput, type DocumentReferenceInput, DocumentTypeCode, type EmbedOptions, type EmbedResult, type ExtractOptions, type ExtractResult, FLAVOR_CONFIGS, FLAVOR_DEFAULT_TYPE_CODES, type FacturXInvoiceInput, Flavor, type FlavorConfig, type IdentifierWithSchemeInput, type InvoiceDocumentInput, type InvoiceLineInput, type InvoiceTotalsInput, type LegalOrganizationInput, type NoteInput, PROFILE_MAIN_XSD, PROFILE_SCHEMA_DIRS, PROFILE_URNS, type PaymentInput, Profile, type TaxRegistrationInput, type TradePartyInput, UnitCode, type ValidationError, type ValidationResult, type VatBreakdownInput, VatCategoryCode, XRECHNUNG_PROFILE_URN, type XRechnungOptions, type XRechnungResult, type XsdValidateOptions, type XsdValidationError, type XsdValidationResult, buildXml, embedFacturX, escapeXml, extractXml, getFlavorConfig, resolveBusinessProcessUrn, resolveTypeCode, toXRechnung, validateFlavorProfile, validateInput, validateXsd };
1515
+ export { type AddressInput, type AllowanceChargeInput, type BillingPeriodInput, type ContactInput, type DeliveryInput, type DocumentReferenceInput, DocumentTypeCode, type EmbedOptions, type EmbedResult, type ExtractOptions, type ExtractResult, FLAVOR_CONFIGS, FLAVOR_DEFAULT_TYPE_CODES, type FacturXInvoiceInput, type FinancialAdjustmentInput, Flavor, type FlavorConfig, type IdentifierWithSchemeInput, type InvoiceDocumentInput, type InvoiceLineInput, type InvoiceTotalsInput, type LegalOrganizationInput, type NoteInput, PROFILE_MAIN_XSD, PROFILE_SCHEMA_DIRS, PROFILE_URNS, type PaymentInput, Profile, type TaxRegistrationInput, type TradePartyInput, UnitCode, type ValidationError, type ValidationResult, type VatBreakdownInput, VatCategoryCode, XRECHNUNG_PROFILE_URN, type XRechnungOptions, type XRechnungResult, type XsdValidateOptions, type XsdValidationError, type XsdValidationResult, buildXml, embedFacturX, escapeXml, extractXml, getFlavorConfig, resolveBusinessProcessUrn, resolveTypeCode, toXRechnung, validateFlavorProfile, validateInput, validateXsd };
package/dist/index.js CHANGED
@@ -205,7 +205,7 @@ var FLAVOR_CONFIGS = {
205
205
  defaultTypeCode: "380" /* COMMERCIAL_INVOICE */,
206
206
  attachmentFilename: "factur-x.xml",
207
207
  embedInPdf: true,
208
- description: "German electronic invoice format. ZUGFeRD 2.4 uses factur-x.xml."
208
+ description: "German electronic invoice format. ZUGFeRD 2.5 uses factur-x.xml."
209
209
  },
210
210
  ["xrechnung" /* XRECHNUNG */]: {
211
211
  name: "XRechnung",
@@ -282,7 +282,8 @@ function tag(name, content, attrs) {
282
282
  return `<${name}${a}>${content}</${name}>`;
283
283
  }
284
284
  function dateEl(wrapper, isoDate) {
285
- return tag(wrapper, tag("udt:DateTimeString", formatDate(isoDate), { format: "102" }));
285
+ const child = wrapper === "ram:FormattedIssueDateTime" ? "qdt:DateTimeString" : "udt:DateTimeString";
286
+ return tag(wrapper, tag(child, formatDate(isoDate), { format: "102" }));
286
287
  }
287
288
  function buildAddress(addr) {
288
289
  let x = "";
@@ -363,6 +364,8 @@ function buildLineItem(line, profile) {
363
364
  if (line.description) prod += tag("ram:Description", escapeXml(line.description));
364
365
  if (line.originCountry && atLeast(profile, "EN16931" /* EN16931 */))
365
366
  prod += tag("ram:OriginTradeCountry", tag("ram:ID", escapeXml(line.originCountry)));
367
+ if (line.manufacturer && atLeast(profile, "EXTENDED" /* EXTENDED */))
368
+ prod += tag("ram:ManufacturerTradeParty", buildParty(line.manufacturer, profile));
366
369
  let agree = "";
367
370
  if (line.buyerOrderLineId && atLeast(profile, "EN16931" /* EN16931 */))
368
371
  agree += tag(
@@ -397,7 +400,7 @@ function buildLineItem(line, profile) {
397
400
  tag("ram:AssociatedDocumentLineDocument", doc) + tag("ram:SpecifiedTradeProduct", prod) + tag("ram:SpecifiedLineTradeAgreement", agree) + tag("ram:SpecifiedLineTradeDelivery", delivery) + tag("ram:SpecifiedLineTradeSettlement", settle)
398
401
  );
399
402
  }
400
- function buildAllowanceCharge(ac) {
403
+ function buildAllowanceCharge(ac, profile) {
401
404
  let x = tag("ram:ChargeIndicator", tag("udt:Indicator", ac.isCharge ? "true" : "false"));
402
405
  if (ac.percent !== void 0) x += tag("ram:CalculationPercent", ac.percent.toString());
403
406
  if (ac.baseAmount !== void 0) x += tag("ram:BasisAmount", fmtAmt(ac.baseAmount));
@@ -405,8 +408,13 @@ function buildAllowanceCharge(ac) {
405
408
  if (ac.reasonCode) x += tag("ram:ReasonCode", escapeXml(ac.reasonCode));
406
409
  if (ac.reason) x += tag("ram:Reason", escapeXml(ac.reason));
407
410
  if (ac.vatCategoryCode || ac.vatRatePercent !== void 0) {
411
+ const extended = atLeast(profile, "EXTENDED" /* EXTENDED */);
408
412
  let tx = tag("ram:TypeCode", "VAT");
413
+ if (extended && ac.exemptionReason)
414
+ tx += tag("ram:ExemptionReason", escapeXml(ac.exemptionReason));
409
415
  if (ac.vatCategoryCode) tx += tag("ram:CategoryCode", escapeXml(ac.vatCategoryCode));
416
+ if (extended && ac.exemptionReasonCode)
417
+ tx += tag("ram:ExemptionReasonCode", escapeXml(ac.exemptionReasonCode));
410
418
  if (ac.vatRatePercent !== void 0)
411
419
  tx += tag("ram:RateApplicablePercent", ac.vatRatePercent.toString());
412
420
  x += tag("ram:CategoryTradeTax", tx);
@@ -574,11 +582,12 @@ function buildXml(input, profile, flavor) {
574
582
  sett += tag("ram:PayeeTradeParty", buildParty(input.payee, profile));
575
583
  if (payment?.meansCode && atLeast(profile, "BASIC_WL" /* BASIC_WL */)) {
576
584
  let pm = tag("ram:TypeCode", escapeXml(payment.meansCode));
577
- if (payment.debtorIban)
578
- pm += tag(
579
- "ram:PayerPartyDebtorFinancialAccount",
580
- tag("ram:IBANID", escapeXml(payment.debtorIban))
581
- );
585
+ if (payment.debtorIban) {
586
+ let dacct = tag("ram:IBANID", escapeXml(payment.debtorIban));
587
+ if (payment.debtorAccountName && atLeast(profile, "EXTENDED" /* EXTENDED */))
588
+ dacct += tag("ram:AccountName", escapeXml(payment.debtorAccountName));
589
+ pm += tag("ram:PayerPartyDebtorFinancialAccount", dacct);
590
+ }
582
591
  if (payment.iban || payment.accountId || payment.accountName) {
583
592
  let acct = "";
584
593
  if (payment.iban) acct += tag("ram:IBANID", escapeXml(payment.iban));
@@ -587,6 +596,11 @@ function buildXml(input, profile, flavor) {
587
596
  acct += tag("ram:AccountName", escapeXml(payment.accountName));
588
597
  pm += tag("ram:PayeePartyCreditorFinancialAccount", acct);
589
598
  }
599
+ if (payment.debtorBic && atLeast(profile, "EXTENDED" /* EXTENDED */))
600
+ pm += tag(
601
+ "ram:PayerSpecifiedDebtorFinancialInstitution",
602
+ tag("ram:BICID", escapeXml(payment.debtorBic))
603
+ );
590
604
  if (payment.bic && atLeast(profile, "EN16931" /* EN16931 */))
591
605
  pm += tag(
592
606
  "ram:PayeeSpecifiedCreditorFinancialInstitution",
@@ -604,7 +618,7 @@ function buildXml(input, profile, flavor) {
604
618
  sett += tag("ram:BillingSpecifiedPeriod", bp);
605
619
  }
606
620
  if (input.allowancesCharges && atLeast(profile, "BASIC_WL" /* BASIC_WL */))
607
- for (const ac of input.allowancesCharges) sett += buildAllowanceCharge(ac);
621
+ for (const ac of input.allowancesCharges) sett += buildAllowanceCharge(ac, profile);
608
622
  const dueDate = payment?.dueDate ?? doc.dueDate;
609
623
  if (atLeast(profile, "BASIC_WL" /* BASIC_WL */) && (payment?.termsDescription || dueDate || payment?.mandateId)) {
610
624
  let pt = "";
@@ -615,6 +629,14 @@ function buildXml(input, profile, flavor) {
615
629
  sett += tag("ram:SpecifiedTradePaymentTerms", pt);
616
630
  }
617
631
  sett += buildMonetarySummation(totals, profile);
632
+ if (input.financialAdjustments && atLeast(profile, "EXTENDED" /* EXTENDED */)) {
633
+ for (const fa of input.financialAdjustments) {
634
+ sett += tag(
635
+ "ram:SpecifiedFinancialAdjustment",
636
+ tag("ram:Reason", escapeXml(fa.reason)) + tag("ram:ActualAmount", fmtAmt(fa.amount))
637
+ );
638
+ }
639
+ }
618
640
  if (input.references && atLeast(profile, "BASIC_WL" /* BASIC_WL */)) {
619
641
  for (const ref of input.references) {
620
642
  if (ref.type === "preceding") {
@@ -1047,6 +1069,7 @@ async function validateXsd(xml, profile, options) {
1047
1069
  XsdValidator,
1048
1070
  XmlValidateError,
1049
1071
  xmlRegisterInputProvider,
1072
+ xmlCleanupInputProvider,
1050
1073
  XmlBufferInputProvider
1051
1074
  } = await import('libxml2-wasm');
1052
1075
  const schemaDir = path.dirname(xsdPath);
@@ -1058,6 +1081,7 @@ async function validateXsd(xml, profile, options) {
1058
1081
  buffers[`file://${filePath}`] = buffers[filePath];
1059
1082
  buffers[file] = buffers[filePath];
1060
1083
  }
1084
+ xmlCleanupInputProvider();
1061
1085
  const provider = new XmlBufferInputProvider(buffers);
1062
1086
  xmlRegisterInputProvider(provider);
1063
1087
  let xsdDoc;
@@ -1087,6 +1111,7 @@ async function validateXsd(xml, profile, options) {
1087
1111
  xmlDoc?.dispose();
1088
1112
  validator?.dispose();
1089
1113
  xsdDoc?.dispose();
1114
+ xmlCleanupInputProvider();
1090
1115
  }
1091
1116
  }
1092
1117
  var AF_RELATIONSHIP_MAP = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackforge-eu/factur-x",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "Embed Factur-X, ZUGFeRD, and XRechnung structured invoice XML into PDF/A-3 documents",
6
6
  "keywords": [
package/schema/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Factur-X / ZUGFeRD Schema Files
2
2
 
3
- > **Source:** Factur-X 1.08 / ZUGFeRD 2.4 official specifications
4
- > **Reorganized from:** ZF24_EN/Schema
3
+ > **Source:** Factur-X 1.09 / ZUGFeRD 2.5 official specifications (2026-06-10)
4
+ > **Reorganized from:** the official `XSD_SCHEMATRON` distribution (filenames normalized; internal `schemaLocation` references rewritten to match)
5
5
 
6
6
  ---
7
7
 
@@ -26,10 +26,23 @@ The package validates generated XML **against the profile-specific XSD** before
26
26
 
27
27
  ## Version
28
28
 
29
- - **Factur-X:** 1.08
30
- - **ZUGFeRD:** 2.4
29
+ - **Factur-X:** 1.09
30
+ - **ZUGFeRD:** 2.5
31
31
  - **CII:** D22B
32
- - **Effective:** 2026-01-15
32
+ - **Effective:** 2026-05-15 (code lists & validation rules)
33
+
34
+ > Factur-X 1.09 keeps the same CII **D22B** syntax, namespaces, and profile URNs
35
+ > (`urn:factur-x.eu:1p0:*`) as 1.08, so it is fully backward compatible — XML that
36
+ > validates against 1.08 also validates against 1.09. For the MINIMUM, BASIC WL,
37
+ > BASIC, and EN 16931 profiles the XSDs are content-identical to 1.08; the EXTENDED
38
+ > XSD adds only **optional** elements (debited account name, financial adjustment /
39
+ > charges-on-behalf-of-a-third-party, an optional `listID` attribute).
40
+ >
41
+ > The `.sch` (Schematron) and `_codedb.xml` files carry the updated 1.09 code lists
42
+ > and CEN validation rules. They are kept here as reference only — this package
43
+ > performs **XSD validation**, not Schematron validation, and ships only `*.xsd`.
44
+ > The per-profile `Factur-X_1.08_*.xlsx` workbooks are legacy human-reference
45
+ > extracts and are not used at runtime.
33
46
 
34
47
  ---
35
48
 
@@ -16,7 +16,9 @@
16
16
  </xs:simpleType>
17
17
  <xs:complexType name="AllowanceChargeReasonCodeType">
18
18
  <xs:simpleContent>
19
- <xs:extension base="qdt:AllowanceChargeReasonCodeContentType"/>
19
+ <xs:extension base="qdt:AllowanceChargeReasonCodeContentType">
20
+ <xs:attribute name="listID" type="xs:token" default="4465_AllowanceChargeReasonCode"/>
21
+ </xs:extension>
20
22
  </xs:simpleContent>
21
23
  </xs:complexType>
22
24
  <xs:simpleType name="ContactTypeCodeContentType">
@@ -30,6 +30,12 @@
30
30
  <xs:complexType name="DebtorFinancialAccountType">
31
31
  <xs:sequence>
32
32
  <xs:element name="IBANID" type="udt:IDType"/>
33
+ <xs:element name="AccountName" type="udt:TextType" minOccurs="0"/>
34
+ </xs:sequence>
35
+ </xs:complexType>
36
+ <xs:complexType name="DebtorFinancialInstitutionType">
37
+ <xs:sequence>
38
+ <xs:element name="BICID" type="udt:IDType" minOccurs="0"/>
33
39
  </xs:sequence>
34
40
  </xs:complexType>
35
41
  <xs:complexType name="DocumentContextParameterType">
@@ -65,6 +71,12 @@
65
71
  <xs:element name="EffectiveSpecifiedPeriod" type="ram:SpecifiedPeriodType" minOccurs="0"/>
66
72
  </xs:sequence>
67
73
  </xs:complexType>
74
+ <xs:complexType name="FinancialAdjustmentType">
75
+ <xs:sequence>
76
+ <xs:element name="Reason" type="udt:TextType"/>
77
+ <xs:element name="ActualAmount" type="udt:AmountType"/>
78
+ </xs:sequence>
79
+ </xs:complexType>
68
80
  <xs:complexType name="HeaderTradeAgreementType">
69
81
  <xs:sequence>
70
82
  <xs:element name="BuyerReference" type="udt:TextType" minOccurs="0"/>
@@ -116,6 +128,7 @@
116
128
  <xs:element name="SpecifiedLogisticsServiceCharge" type="ram:LogisticsServiceChargeType" minOccurs="0" maxOccurs="unbounded"/>
117
129
  <xs:element name="SpecifiedTradePaymentTerms" type="ram:TradePaymentTermsType" minOccurs="0" maxOccurs="unbounded"/>
118
130
  <xs:element name="SpecifiedTradeSettlementHeaderMonetarySummation" type="ram:TradeSettlementHeaderMonetarySummationType"/>
131
+ <xs:element name="SpecifiedFinancialAdjustment" type="ram:FinancialAdjustmentType" minOccurs="0" maxOccurs="unbounded"/>
119
132
  <xs:element name="InvoiceReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0" maxOccurs="unbounded"/>
120
133
  <xs:element name="ReceivableSpecifiedTradeAccountingAccount" type="ram:TradeAccountingAccountType" minOccurs="0" maxOccurs="unbounded"/>
121
134
  <xs:element name="SpecifiedAdvancePayment" type="ram:AdvancePaymentType" minOccurs="0" maxOccurs="unbounded"/>
@@ -161,7 +174,7 @@
161
174
  <xs:element name="ApplicableTradeTax" type="ram:TradeTaxType" minOccurs="0" maxOccurs="unbounded"/>
162
175
  <xs:element name="BillingSpecifiedPeriod" type="ram:SpecifiedPeriodType" minOccurs="0"/>
163
176
  <xs:element name="SpecifiedTradeAllowanceCharge" type="ram:TradeAllowanceChargeType" minOccurs="0" maxOccurs="unbounded"/>
164
- <xs:element name="SpecifiedTradeSettlementLineMonetarySummation" type="ram:TradeSettlementLineMonetarySummationType"/>
177
+ <xs:element name="SpecifiedTradeSettlementLineMonetarySummation" type="ram:TradeSettlementLineMonetarySummationType" minOccurs="0"/>
165
178
  <xs:element name="InvoiceReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
166
179
  <xs:element name="AdditionalReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0" maxOccurs="unbounded"/>
167
180
  <xs:element name="ReceivableSpecifiedTradeAccountingAccount" type="ram:TradeAccountingAccountType" minOccurs="0" maxOccurs="unbounded"/>
@@ -195,9 +208,9 @@
195
208
  <xs:complexType name="ProductCharacteristicType">
196
209
  <xs:sequence>
197
210
  <xs:element name="TypeCode" type="udt:CodeType" minOccurs="0"/>
198
- <xs:element name="Description" type="udt:TextType"/>
211
+ <xs:element name="Description" type="udt:TextType" minOccurs="0"/>
199
212
  <xs:element name="ValueMeasure" type="udt:MeasureType" minOccurs="0"/>
200
- <xs:element name="Value" type="udt:TextType"/>
213
+ <xs:element name="Value" type="udt:TextType" minOccurs="0"/>
201
214
  </xs:sequence>
202
215
  </xs:complexType>
203
216
  <xs:complexType name="ProductClassificationType">
@@ -409,6 +422,7 @@
409
422
  <xs:element name="DesignatedProductClassification" type="ram:ProductClassificationType" minOccurs="0" maxOccurs="unbounded"/>
410
423
  <xs:element name="IndividualTradeProductInstance" type="ram:TradeProductInstanceType" minOccurs="0" maxOccurs="unbounded"/>
411
424
  <xs:element name="OriginTradeCountry" type="ram:TradeCountryType" minOccurs="0"/>
425
+ <xs:element name="ManufacturerTradeParty" type="ram:TradePartyType" minOccurs="0"/>
412
426
  <xs:element name="IncludedReferencedProduct" type="ram:ReferencedProductType" minOccurs="0" maxOccurs="unbounded"/>
413
427
  </xs:sequence>
414
428
  </xs:complexType>
@@ -433,7 +447,7 @@
433
447
  </xs:complexType>
434
448
  <xs:complexType name="TradeSettlementLineMonetarySummationType">
435
449
  <xs:sequence>
436
- <xs:element name="LineTotalAmount" type="udt:AmountType"/>
450
+ <xs:element name="LineTotalAmount" type="udt:AmountType" minOccurs="0"/>
437
451
  <xs:element name="ChargeTotalAmount" type="udt:AmountType" minOccurs="0"/>
438
452
  <xs:element name="AllowanceTotalAmount" type="udt:AmountType" minOccurs="0"/>
439
453
  <xs:element name="TaxTotalAmount" type="udt:AmountType" minOccurs="0" maxOccurs="2"/>
@@ -448,6 +462,7 @@
448
462
  <xs:element name="ApplicableTradeSettlementFinancialCard" type="ram:TradeSettlementFinancialCardType" minOccurs="0"/>
449
463
  <xs:element name="PayerPartyDebtorFinancialAccount" type="ram:DebtorFinancialAccountType" minOccurs="0"/>
450
464
  <xs:element name="PayeePartyCreditorFinancialAccount" type="ram:CreditorFinancialAccountType" minOccurs="0"/>
465
+ <xs:element name="PayerSpecifiedDebtorFinancialInstitution" type="ram:DebtorFinancialInstitutionType" minOccurs="0"/>
451
466
  <xs:element name="PayeeSpecifiedCreditorFinancialInstitution" type="ram:CreditorFinancialInstitutionType" minOccurs="0"/>
452
467
  </xs:sequence>
453
468
  </xs:complexType>