@getpeppr/cli 0.5.1 → 0.5.2

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/dist/index.js CHANGED
@@ -631,6 +631,47 @@ function buildCreditNoteXml(input) {
631
631
  </CreditNote>`;
632
632
  }
633
633
 
634
+ // ../sdk/dist/core/checksums/luhn.js
635
+ function isValidLuhn(input) {
636
+ if (typeof input !== "string")
637
+ return false;
638
+ if (!/^\d+$/.test(input))
639
+ return false;
640
+ let sum = 0;
641
+ let alt = false;
642
+ for (let i = input.length - 1; i >= 0; i--) {
643
+ let n = input.charCodeAt(i) - 48;
644
+ if (alt) {
645
+ n *= 2;
646
+ if (n > 9)
647
+ n -= 9;
648
+ }
649
+ sum += n;
650
+ alt = !alt;
651
+ }
652
+ return sum % 10 === 0;
653
+ }
654
+ var LA_POSTE_SIREN = "356000000";
655
+ function isValidLuhnSiret(input) {
656
+ if (typeof input !== "string")
657
+ return false;
658
+ if (!/^\d{14}$/.test(input))
659
+ return false;
660
+ const siren = input.slice(0, 9);
661
+ if (!isValidLuhn(siren))
662
+ return false;
663
+ if (isValidLuhn(input))
664
+ return true;
665
+ if (siren === LA_POSTE_SIREN) {
666
+ let sum = 0;
667
+ for (let i = 0; i < input.length; i++) {
668
+ sum += input.charCodeAt(i) - 48;
669
+ }
670
+ return sum % 5 === 0;
671
+ }
672
+ return false;
673
+ }
674
+
634
675
  // ../sdk/dist/core/country-rules.js
635
676
  function warn(field, message, ruleId) {
636
677
  return { field, message, ruleId };
@@ -665,15 +706,47 @@ function validateBelgiumSeller(input, _errors, warnings) {
665
706
  warnings.push(warn("paymentReference", "Belgian sellers typically include a structured communication reference (+++NNN/NNNN/NNNNN+++ format).", "BE-02"));
666
707
  }
667
708
  }
709
+ var FR_SIREN_RE = /^\d{9}$/;
668
710
  var FR_SIRET_RE = /^\d{14}$/;
669
- var FR_VAT_RE = /^FR[A-Z0-9]{2}\d{9}$/;
711
+ var FR_VAT_RE = /^FR[0-9A-HJ-NP-Z]{2}\d{9}$/;
712
+ var FR_VAT_NUMERIC_KEY_RE = /^FR(\d{2})(\d{9})$/;
713
+ var FR_SIREN_BASED_SCHEMES = ["0002", "0009", "0225"];
714
+ function frVatKey(siren) {
715
+ return (12 + 3 * (Number(siren) % 97)) % 97;
716
+ }
717
+ function isValidSirenOrSiret(id) {
718
+ if (FR_SIREN_RE.test(id))
719
+ return isValidLuhn(id);
720
+ if (FR_SIRET_RE.test(id))
721
+ return isValidLuhnSiret(id);
722
+ return false;
723
+ }
670
724
  function validateFrance(input, _errors, warnings) {
671
- const { companyId, vatNumber } = input.to ?? {};
672
- if (companyId && !FR_SIRET_RE.test(companyId)) {
673
- warnings.push(warn("to.companyId", `French company ID (SIRET) should be exactly 14 digits, got "${companyId}".`, "FR-01"));
725
+ const { companyId, companyIdScheme, vatNumber } = input.to ?? {};
726
+ const companyIdIsSiren = !companyIdScheme || FR_SIREN_BASED_SCHEMES.includes(companyIdScheme);
727
+ if (companyId != null && companyId !== "" && companyIdIsSiren) {
728
+ if (typeof companyId !== "string") {
729
+ warnings.push(warn("to.companyId", "French company ID should be a string of 9 (SIREN) or 14 (SIRET) digits.", "FR-01"));
730
+ } else if (!FR_SIREN_RE.test(companyId) && !FR_SIRET_RE.test(companyId)) {
731
+ warnings.push(warn("to.companyId", `French company ID should be a 9-digit SIREN or 14-digit SIRET, got "${companyId}".`, "FR-01"));
732
+ } else if (!isValidSirenOrSiret(companyId)) {
733
+ warnings.push(warn("to.companyId", `French company ID "${companyId}" has an invalid checksum. Verify the SIREN/SIRET.`, "FR-01"));
734
+ }
674
735
  }
675
- if (vatNumber && !FR_VAT_RE.test(vatNumber)) {
676
- warnings.push(warn("to.vatNumber", `French VAT number should match format FR + 2 characters + 9 digits (SIREN), got "${vatNumber}".`, "FR-02"));
736
+ if (vatNumber != null && vatNumber !== "") {
737
+ if (typeof vatNumber !== "string") {
738
+ warnings.push(warn("to.vatNumber", "French VAT number should be a string matching FR + 2 characters + 9 digits (SIREN).", "FR-02"));
739
+ } else if (!FR_VAT_RE.test(vatNumber)) {
740
+ warnings.push(warn("to.vatNumber", `French VAT number should match format FR + 2 characters + 9 digits (SIREN), got "${vatNumber}".`, "FR-02"));
741
+ } else {
742
+ const numericKey = FR_VAT_NUMERIC_KEY_RE.exec(vatNumber);
743
+ if (numericKey) {
744
+ const [, key, siren] = numericKey;
745
+ if (Number(key) !== frVatKey(siren)) {
746
+ warnings.push(warn("to.vatNumber", `French VAT number "${vatNumber}" has an invalid verification key \u2014 expected FR${String(frVatKey(siren)).padStart(2, "0")}${siren}. Likely a typo.`, "FR-03"));
747
+ }
748
+ }
749
+ }
677
750
  }
678
751
  }
679
752
  function validateItaly(input, _errors, warnings) {
@@ -1088,7 +1161,7 @@ function statusFamily(status) {
1088
1161
  var TERMINAL_FAILURE_STATUSES = STATUS_PRECEDENCE.filter((e) => e.family === "terminal-failure").map((e) => e.status);
1089
1162
 
1090
1163
  // ../sdk/dist/version.js
1091
- var SDK_VERSION = "2.3.0";
1164
+ var SDK_VERSION = "2.4.0";
1092
1165
 
1093
1166
  // ../sdk/dist/core/client.js
1094
1167
  function findHeaderCaseInsensitive(headers, name) {
@@ -2184,15 +2257,21 @@ ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (
2184
2257
  * Transition an invoice to a new state.
2185
2258
  * The gateway validates the state machine — invalid transitions return an error.
2186
2259
  *
2260
+ * `"paid"` on a French CTC invoice reports the payment collection
2261
+ * (« signalement d'encaissement ») to the tax authority via the gateway —
2262
+ * a legal obligation of the French mandate for service invoices. The full
2263
+ * amount is reported from the invoice's stored tax breakdown (no amount to
2264
+ * pass), at most once per invoice: replays return the same report (200),
2265
+ * a concurrent report returns 409, a non-French invoice returns 422.
2266
+ * The invoice's own status becomes `paid` later, when the network confirms
2267
+ * (webhook / polling), not synchronously with this call.
2268
+ *
2187
2269
  * @example
2188
2270
  * ```ts
2189
- * await peppol.invoices.markAs("inv-123", "accepted");
2190
- * await peppol.invoices.markAs("inv-123", "paid", {
2191
- * commit: "with_mail",
2192
- * reason: "Payment received",
2193
- * });
2271
+ * // France: report that the customer paid this invoice
2272
+ * await peppol.invoices.markAs("inv-123", "paid");
2194
2273
  * ```
2195
- * @throws {PeppolApiError} 501 if the gateway provider does not support state transitions
2274
+ * @throws {PeppolApiError} 422 for "paid" on a non-French-CTC invoice; 501 for states the provider does not support
2196
2275
  */
2197
2276
  async markAs(id, state, options) {
2198
2277
  return this.adapter.markInvoiceAs(id, state, options);