@getpeppr/cli 0.8.3 → 0.8.4
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 +11 -0
- package/README.md +6 -3
- package/dist/index.js +84 -72
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ All notable changes to `@getpeppr/cli` are documented here.
|
|
|
4
4
|
|
|
5
5
|
The CLI bundles `@getpeppr/sdk` into its published artifact (tsup `noExternal`), so `validate`, `convert` and `init` run the SDK's validators, country rules and UBL builder locally — an SDK release only reaches CLI users once the CLI is rebundled. Rebundle-only releases are listed with the SDK version they ship.
|
|
6
6
|
|
|
7
|
+
## [0.8.4] — 2026-08-22
|
|
8
|
+
|
|
9
|
+
`getpeppr init` now scaffolds an invoice that a fresh sandbox account can send:
|
|
10
|
+
the SPF Economie test receiver plus out-of-scope `O/0` lines. The previous
|
|
11
|
+
template targeted the correct receiver but still carried VAT-bearing and
|
|
12
|
+
reverse-charge lines, so a new sender with no VAT identifier received 422.
|
|
13
|
+
|
|
14
|
+
Every generated `O` line includes the builder-only exemption reason required by
|
|
15
|
+
the SDK's local validator. The quick `getpeppr send --to …` payload remains on
|
|
16
|
+
the same contract. This release bundles SDK 4.7.0.
|
|
17
|
+
|
|
7
18
|
## [0.8.3] — 2026-08-21
|
|
8
19
|
|
|
9
20
|
Rebundles `@getpeppr/sdk` **4.6.0** (was 4.5.0), so `getpeppr validate` and
|
package/README.md
CHANGED
|
@@ -241,7 +241,7 @@ Scaffolding (`init`), validation, and conversion run fully offline — no API ke
|
|
|
241
241
|
|
|
242
242
|
## Invoice format
|
|
243
243
|
|
|
244
|
-
The input file must be a JSON object matching the getpeppr `InvoiceInput` type. `number`, `to`, and `lines` are required for API sends. The `from` party is included in the scaffold because offline validation and UBL conversion need seller metadata; API sends use the legal entity linked to your key.
|
|
244
|
+
The input file must be a JSON object matching the getpeppr `InvoiceInput` type. `number`, `to`, and `lines` are required for API sends. The `from` party is included in the scaffold because offline validation and UBL conversion need seller metadata; API sends use the legal entity linked to your key. The generated file is an integration fixture: a fresh sandbox sender has no VAT identifier, so every line uses category `O` at rate `0`.
|
|
245
245
|
|
|
246
246
|
```json
|
|
247
247
|
{
|
|
@@ -271,14 +271,17 @@ The input file must be a JSON object matching the getpeppr `InvoiceInput` type.
|
|
|
271
271
|
"description": "Conseil en transformation numérique",
|
|
272
272
|
"quantity": 10,
|
|
273
273
|
"unitPrice": 950,
|
|
274
|
-
"vatRate":
|
|
274
|
+
"vatRate": 0,
|
|
275
|
+
"vatCategory": "O",
|
|
276
|
+
"taxExemptReason": "Integration test"
|
|
275
277
|
},
|
|
276
278
|
{
|
|
277
279
|
"description": "Software license — annual subscription",
|
|
278
280
|
"quantity": 1,
|
|
279
281
|
"unitPrice": 2400,
|
|
280
282
|
"vatRate": 0,
|
|
281
|
-
"vatCategory": "
|
|
283
|
+
"vatCategory": "O",
|
|
284
|
+
"taxExemptReason": "Integration test"
|
|
282
285
|
}
|
|
283
286
|
],
|
|
284
287
|
"paymentTerms": "Net 30 days",
|
package/dist/index.js
CHANGED
|
@@ -130,7 +130,7 @@ Validating: ${pc.bold(filename)}
|
|
|
130
130
|
return lines.join("\n");
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
//
|
|
133
|
+
// ../../../getpeppr/packages/sdk/dist/core/canonical-schemes.js
|
|
134
134
|
var ALIAS_TO_EAS = /* @__PURE__ */ new Map([
|
|
135
135
|
["AD:VAT", "9922"],
|
|
136
136
|
["AE:TIN", "0235"],
|
|
@@ -356,7 +356,7 @@ function countryForScheme(scheme) {
|
|
|
356
356
|
var CANONICAL_SCHEME_COUNT = ALIAS_TO_EAS.size;
|
|
357
357
|
var SCHEME_COUNTRY_COUNT = EAS_TO_COUNTRY.size;
|
|
358
358
|
|
|
359
|
-
//
|
|
359
|
+
// ../../../getpeppr/packages/sdk/dist/core/peppol-id.js
|
|
360
360
|
function isWellFormedPeppolId(peppolId) {
|
|
361
361
|
if (!peppolId.includes(":"))
|
|
362
362
|
return false;
|
|
@@ -382,7 +382,7 @@ function parsePeppolId(peppolId) {
|
|
|
382
382
|
};
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
-
//
|
|
385
|
+
// ../../../getpeppr/packages/sdk/dist/core/iso6523-icd-codes.js
|
|
386
386
|
var ICD_CODES = /* @__PURE__ */ new Set([
|
|
387
387
|
"0002",
|
|
388
388
|
"0003",
|
|
@@ -642,7 +642,7 @@ function canCarryPartyIdentification(scheme, context) {
|
|
|
642
642
|
return scheme === "SEPA" && context !== "AccountingCustomerParty";
|
|
643
643
|
}
|
|
644
644
|
|
|
645
|
-
//
|
|
645
|
+
// ../../../getpeppr/packages/sdk/dist/core/ubl-builder.js
|
|
646
646
|
var UBL_NS = "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2";
|
|
647
647
|
var CAC_NS = "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2";
|
|
648
648
|
var CBC_NS = "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2";
|
|
@@ -717,6 +717,28 @@ function formatVatRate(vatRate, field) {
|
|
|
717
717
|
}
|
|
718
718
|
return String(vatRate);
|
|
719
719
|
}
|
|
720
|
+
function formatBaseQuantity(baseQuantity, field) {
|
|
721
|
+
if (typeof baseQuantity !== "number" || !Number.isFinite(baseQuantity) || baseQuantity <= 0) {
|
|
722
|
+
throw new UblBuilderInputError("baseQuantity must be a finite number greater than zero.", field, "PEPPOL-EN16931-R121");
|
|
723
|
+
}
|
|
724
|
+
const numeric = String(baseQuantity);
|
|
725
|
+
const exponentMarker = numeric.search(/[eE]/);
|
|
726
|
+
if (exponentMarker === -1)
|
|
727
|
+
return numeric;
|
|
728
|
+
const coefficient = numeric.slice(0, exponentMarker);
|
|
729
|
+
const exponent = Number(numeric.slice(exponentMarker + 1));
|
|
730
|
+
const decimalPoint = coefficient.indexOf(".");
|
|
731
|
+
const digits = coefficient.replace(".", "");
|
|
732
|
+
const integerDigits = decimalPoint === -1 ? coefficient.length : decimalPoint;
|
|
733
|
+
const outputPoint = integerDigits + exponent;
|
|
734
|
+
if (outputPoint <= 0) {
|
|
735
|
+
return `0.${"0".repeat(-outputPoint)}${digits}`;
|
|
736
|
+
}
|
|
737
|
+
if (outputPoint >= digits.length) {
|
|
738
|
+
return `${digits}${"0".repeat(outputPoint - digits.length)}`;
|
|
739
|
+
}
|
|
740
|
+
return `${digits.slice(0, outputPoint)}.${digits.slice(outputPoint)}`;
|
|
741
|
+
}
|
|
720
742
|
function round2(n) {
|
|
721
743
|
return Math.round(n * 100) / 100;
|
|
722
744
|
}
|
|
@@ -974,7 +996,7 @@ function buildDocumentLineXml(line, index, currency, lineTag, qtyTag) {
|
|
|
974
996
|
</cac:Item>
|
|
975
997
|
<cac:Price>
|
|
976
998
|
<cbc:PriceAmount currencyID="${escapeXml(currency)}">${formatAmount(line.unitPrice)}</cbc:PriceAmount>
|
|
977
|
-
${line.baseQuantity
|
|
999
|
+
${line.baseQuantity !== void 0 ? `<cbc:BaseQuantity unitCode="${escapeXml(resolveUnitCode(line.baseQuantityUnit ?? line.unit ?? DEFAULT_UNIT))}">${formatBaseQuantity(line.baseQuantity, `lines[${index}].baseQuantity`)}</cbc:BaseQuantity>` : ""}
|
|
978
1000
|
</cac:Price>
|
|
979
1001
|
</cac:${lineTag}>`;
|
|
980
1002
|
}
|
|
@@ -1220,7 +1242,7 @@ function buildCreditNoteXml(input) {
|
|
|
1220
1242
|
</CreditNote>`;
|
|
1221
1243
|
}
|
|
1222
1244
|
|
|
1223
|
-
//
|
|
1245
|
+
// ../../../getpeppr/packages/sdk/dist/core/checksums/luhn.js
|
|
1224
1246
|
function isValidLuhn(input) {
|
|
1225
1247
|
if (typeof input !== "string")
|
|
1226
1248
|
return false;
|
|
@@ -1261,7 +1283,7 @@ function isValidLuhnSiret(input) {
|
|
|
1261
1283
|
return false;
|
|
1262
1284
|
}
|
|
1263
1285
|
|
|
1264
|
-
//
|
|
1286
|
+
// ../../../getpeppr/packages/sdk/dist/core/country-rules.js
|
|
1265
1287
|
function warn(field, message, ruleId) {
|
|
1266
1288
|
return { field, message, ruleId };
|
|
1267
1289
|
}
|
|
@@ -1402,7 +1424,7 @@ function validateCountryRules(input) {
|
|
|
1402
1424
|
return { errors, warnings };
|
|
1403
1425
|
}
|
|
1404
1426
|
|
|
1405
|
-
//
|
|
1427
|
+
// ../../../getpeppr/packages/sdk/dist/core/code-lists.js
|
|
1406
1428
|
var CURRENCIES = /* @__PURE__ */ new Map([
|
|
1407
1429
|
["EUR", { code: "EUR", name: "Euro", minorUnits: 2 }],
|
|
1408
1430
|
["USD", { code: "USD", name: "US Dollar", minorUnits: 2 }],
|
|
@@ -1527,7 +1549,7 @@ function getAllUnits() {
|
|
|
1527
1549
|
return Array.from(UNIT_CODES.entries()).map(([code, name]) => ({ code, name })).sort((a, b) => a.code.localeCompare(b.code));
|
|
1528
1550
|
}
|
|
1529
1551
|
|
|
1530
|
-
//
|
|
1552
|
+
// ../../../getpeppr/packages/sdk/dist/core/validator.js
|
|
1531
1553
|
function error(field, message, ruleId, suggestion) {
|
|
1532
1554
|
return { field, message, ruleId, suggestion };
|
|
1533
1555
|
}
|
|
@@ -1605,6 +1627,11 @@ function validateLine(line, index, isCreditNote = false) {
|
|
|
1605
1627
|
} else if (line.unitPrice < 0) {
|
|
1606
1628
|
errors.push(error(`${path}.unitPrice`, `Unit price cannot be negative, got ${line.unitPrice}`, void 0, "For discounts, use a negative quantity or a separate discount line"));
|
|
1607
1629
|
}
|
|
1630
|
+
if (line.baseQuantity !== void 0) {
|
|
1631
|
+
if (typeof line.baseQuantity !== "number" || !Number.isFinite(line.baseQuantity) || line.baseQuantity <= 0) {
|
|
1632
|
+
errors.push(error(`${path}.baseQuantity`, "Base quantity must be a finite number greater than zero", "PEPPOL-EN16931-R121", "Use a positive number for the item price base quantity"));
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1608
1635
|
if (line.vatRate === void 0 || line.vatRate === null) {
|
|
1609
1636
|
errors.push(error(`${path}.vatRate`, "VAT rate is required", "BR-CO-17"));
|
|
1610
1637
|
} else if (line.vatRate < 0 || line.vatRate > 100) {
|
|
@@ -1754,7 +1781,7 @@ function validateInvoice(input) {
|
|
|
1754
1781
|
};
|
|
1755
1782
|
}
|
|
1756
1783
|
|
|
1757
|
-
//
|
|
1784
|
+
// ../../../getpeppr/packages/sdk/dist/core/schematron.js
|
|
1758
1785
|
function violation(ruleId, severity, message, field) {
|
|
1759
1786
|
return { ruleId, severity, message, field };
|
|
1760
1787
|
}
|
|
@@ -2279,7 +2306,7 @@ var SDK_SCHEMATRON_RULE_IDS = [
|
|
|
2279
2306
|
"PEPPOL-EN16931-R080"
|
|
2280
2307
|
];
|
|
2281
2308
|
|
|
2282
|
-
//
|
|
2309
|
+
// ../../../getpeppr/packages/sdk/dist/core/status-precedence.js
|
|
2283
2310
|
var STATUS_PRECEDENCE = [
|
|
2284
2311
|
{ status: "failed", family: "terminal-failure" },
|
|
2285
2312
|
{ status: "rejected", family: "terminal-failure" },
|
|
@@ -2303,10 +2330,10 @@ function statusFamily(status) {
|
|
|
2303
2330
|
}
|
|
2304
2331
|
var TERMINAL_FAILURE_STATUSES = STATUS_PRECEDENCE.filter((e) => e.family === "terminal-failure").map((e) => e.status);
|
|
2305
2332
|
|
|
2306
|
-
//
|
|
2307
|
-
var SDK_VERSION = "4.
|
|
2333
|
+
// ../../../getpeppr/packages/sdk/dist/version.js
|
|
2334
|
+
var SDK_VERSION = "4.7.0";
|
|
2308
2335
|
|
|
2309
|
-
//
|
|
2336
|
+
// ../../../getpeppr/packages/sdk/dist/core/client.js
|
|
2310
2337
|
function findHeaderCaseInsensitive(headers, name) {
|
|
2311
2338
|
if (!headers)
|
|
2312
2339
|
return void 0;
|
|
@@ -2537,9 +2564,8 @@ var GetpepprAdapter = class {
|
|
|
2537
2564
|
clearTimeout(timeoutId);
|
|
2538
2565
|
}
|
|
2539
2566
|
}
|
|
2540
|
-
//
|
|
2541
|
-
//
|
|
2542
|
-
// { invoice: {...}, send_after_import: true }, createInvoice omits the flag.
|
|
2567
|
+
// sendInvoice and createInvoice hit the same endpoint. The latter adds the
|
|
2568
|
+
// legacy `_draft` marker, which the current gateway rejects explicitly.
|
|
2543
2569
|
async sendInvoice(input, options) {
|
|
2544
2570
|
const headers = {};
|
|
2545
2571
|
if (options?.idempotencyKey) {
|
|
@@ -3521,16 +3547,12 @@ var InvoiceOperations = class {
|
|
|
3521
3547
|
this.adapter = adapter;
|
|
3522
3548
|
}
|
|
3523
3549
|
/**
|
|
3524
|
-
*
|
|
3525
|
-
* Validates input client-side, then creates the invoice via the gateway.
|
|
3526
|
-
* Use `sendById()` to send the draft when ready.
|
|
3550
|
+
* Request draft creation from the gateway.
|
|
3527
3551
|
*
|
|
3528
|
-
* @
|
|
3529
|
-
*
|
|
3530
|
-
*
|
|
3531
|
-
*
|
|
3532
|
-
* await peppol.invoices.sendById(draft.id);
|
|
3533
|
-
* ```
|
|
3552
|
+
* @deprecated The current Storecove-backed gateway does not support drafts
|
|
3553
|
+
* and returns 422 `drafts_not_supported`. Submit the final document with
|
|
3554
|
+
* `invoices.send()` instead.
|
|
3555
|
+
* @throws {PeppolApiError} 422 with code `drafts_not_supported`
|
|
3534
3556
|
*/
|
|
3535
3557
|
async create(input, options) {
|
|
3536
3558
|
const validation = validateInvoice(input);
|
|
@@ -3545,14 +3567,11 @@ ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (
|
|
|
3545
3567
|
return result;
|
|
3546
3568
|
}
|
|
3547
3569
|
/**
|
|
3548
|
-
*
|
|
3549
|
-
* The invoice must have been previously created with `create()`.
|
|
3570
|
+
* Request sending of an existing draft invoice by ID.
|
|
3550
3571
|
*
|
|
3551
|
-
* @
|
|
3552
|
-
*
|
|
3553
|
-
*
|
|
3554
|
-
* ```
|
|
3555
|
-
* @throws {PeppolApiError} 501 if the gateway provider does not support draft sending
|
|
3572
|
+
* @deprecated The current Storecove-backed gateway has no draft lifecycle and
|
|
3573
|
+
* always returns 501. Submit the final document with `invoices.send()`.
|
|
3574
|
+
* @throws {PeppolApiError} 501 with the current gateway provider
|
|
3556
3575
|
*/
|
|
3557
3576
|
async sendById(id) {
|
|
3558
3577
|
return this.adapter.sendInvoiceById(id);
|
|
@@ -3723,52 +3742,39 @@ ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (
|
|
|
3723
3742
|
return this.adapter.importInvoice(options);
|
|
3724
3743
|
}
|
|
3725
3744
|
/**
|
|
3726
|
-
*
|
|
3745
|
+
* Request acknowledgement of a received invoice.
|
|
3727
3746
|
*
|
|
3728
|
-
* @
|
|
3729
|
-
*
|
|
3730
|
-
*
|
|
3731
|
-
* console.log(result.status); // "accepted"
|
|
3732
|
-
* ```
|
|
3733
|
-
* @throws {PeppolApiError} 501 if the gateway provider does not support acknowledgement
|
|
3747
|
+
* @deprecated The current Storecove-backed gateway does not support
|
|
3748
|
+
* acknowledgement and always returns 501.
|
|
3749
|
+
* @throws {PeppolApiError} 501 with the current gateway provider
|
|
3734
3750
|
*/
|
|
3735
3751
|
async acknowledge(id) {
|
|
3736
3752
|
return this.adapter.acknowledgeInvoice(id);
|
|
3737
3753
|
}
|
|
3738
3754
|
/**
|
|
3739
|
-
*
|
|
3740
|
-
* Only include the fields you want to change — partial updates are supported.
|
|
3755
|
+
* Request an update to an existing invoice.
|
|
3741
3756
|
*
|
|
3742
|
-
* @
|
|
3743
|
-
*
|
|
3744
|
-
*
|
|
3745
|
-
* dueDate: "2026-04-01",
|
|
3746
|
-
* lines: [
|
|
3747
|
-
* { id: "line-1", quantity: 5 },
|
|
3748
|
-
* { id: "line-2", _destroy: true },
|
|
3749
|
-
* ],
|
|
3750
|
-
* });
|
|
3751
|
-
* ```
|
|
3752
|
-
* @throws {PeppolApiError} 501 if the gateway provider does not support invoice updates
|
|
3757
|
+
* @deprecated Storecove documents are immutable after submission. The
|
|
3758
|
+
* current gateway always returns 501; issue a credit note instead.
|
|
3759
|
+
* @throws {PeppolApiError} 501 with the current gateway provider
|
|
3753
3760
|
*/
|
|
3754
3761
|
async update(id, input) {
|
|
3755
3762
|
return this.adapter.updateInvoice(id, input);
|
|
3756
3763
|
}
|
|
3757
3764
|
/**
|
|
3758
|
-
*
|
|
3765
|
+
* Request deletion of an invoice.
|
|
3759
3766
|
*
|
|
3760
|
-
* @
|
|
3761
|
-
*
|
|
3762
|
-
*
|
|
3763
|
-
* ```
|
|
3764
|
-
* @throws {PeppolApiError} 501 if the gateway provider does not support invoice deletion
|
|
3767
|
+
* @deprecated The current Storecove-backed gateway does not support invoice
|
|
3768
|
+
* deletion and always returns 501.
|
|
3769
|
+
* @throws {PeppolApiError} 501 with the current gateway provider
|
|
3765
3770
|
*/
|
|
3766
3771
|
async delete(id) {
|
|
3767
3772
|
return this.adapter.deleteInvoice(id);
|
|
3768
3773
|
}
|
|
3769
3774
|
/**
|
|
3770
|
-
*
|
|
3771
|
-
*
|
|
3775
|
+
* Report a French CTC invoice as paid.
|
|
3776
|
+
* Other state transitions are retained for API compatibility but the current
|
|
3777
|
+
* Storecove-backed gateway returns 501 for them.
|
|
3772
3778
|
*
|
|
3773
3779
|
* `"paid"` on a French CTC invoice reports the payment collection
|
|
3774
3780
|
* (« signalement d'encaissement ») to the tax authority via the gateway —
|
|
@@ -4448,14 +4454,17 @@ var INVOICE_TEMPLATE = {
|
|
|
4448
4454
|
description: "Conseil en transformation num\xE9rique",
|
|
4449
4455
|
quantity: 10,
|
|
4450
4456
|
unitPrice: 950,
|
|
4451
|
-
vatRate:
|
|
4457
|
+
vatRate: 0,
|
|
4458
|
+
vatCategory: "O",
|
|
4459
|
+
taxExemptReason: "Integration test"
|
|
4452
4460
|
},
|
|
4453
4461
|
{
|
|
4454
4462
|
description: "Software license \u2014 annual subscription",
|
|
4455
4463
|
quantity: 1,
|
|
4456
4464
|
unitPrice: 2400,
|
|
4457
4465
|
vatRate: 0,
|
|
4458
|
-
vatCategory: "
|
|
4466
|
+
vatCategory: "O",
|
|
4467
|
+
taxExemptReason: "Integration test"
|
|
4459
4468
|
}
|
|
4460
4469
|
],
|
|
4461
4470
|
paymentTerms: "Net 30 days",
|
|
@@ -4491,7 +4500,9 @@ var CREDIT_NOTE_TEMPLATE = {
|
|
|
4491
4500
|
description: "Avoir partiel \u2014 Conseil en transformation num\xE9rique",
|
|
4492
4501
|
quantity: 2,
|
|
4493
4502
|
unitPrice: 950,
|
|
4494
|
-
vatRate:
|
|
4503
|
+
vatRate: 0,
|
|
4504
|
+
vatCategory: "O",
|
|
4505
|
+
taxExemptReason: "Integration test"
|
|
4495
4506
|
}
|
|
4496
4507
|
],
|
|
4497
4508
|
note: "Avoir pour prestations non r\xE9alis\xE9es \u2014 r\xE9f. INV-2026-001"
|
|
@@ -4525,9 +4536,9 @@ function registerInitCommand(program2) {
|
|
|
4525
4536
|
3. Convert to XML: getpeppr convert ${filename}
|
|
4526
4537
|
4. Send: getpeppr send ${filename}
|
|
4527
4538
|
|
|
4528
|
-
${pc2.dim("Sandbox note:")}
|
|
4529
|
-
|
|
4530
|
-
|
|
4539
|
+
${pc2.dim("Sandbox note:")} ready for a fresh sandbox account. This template
|
|
4540
|
+
targets the test receiver and uses O/0 tax lines. Replace the fixture tax
|
|
4541
|
+
treatment with the real category after registering the sender's VAT details.
|
|
4531
4542
|
`);
|
|
4532
4543
|
process.exit(0);
|
|
4533
4544
|
}
|
|
@@ -5090,11 +5101,12 @@ function buildDefaultSendPayload(overrides = {}) {
|
|
|
5090
5101
|
quantity: 1,
|
|
5091
5102
|
unitPrice: amount,
|
|
5092
5103
|
vatRate: 0,
|
|
5093
|
-
// vatCategory "O" =
|
|
5094
|
-
//
|
|
5095
|
-
//
|
|
5096
|
-
//
|
|
5097
|
-
//
|
|
5104
|
+
// vatCategory "O" = outside the scope of VAT (UBL 2.1 / EN 16931).
|
|
5105
|
+
// A fresh sandbox sender has no VAT identifier, so this integration
|
|
5106
|
+
// fixture deliberately uses O/0. The SDK pre-validator requires the
|
|
5107
|
+
// builder-only reason; the send transport strips it so Storecove can
|
|
5108
|
+
// derive its own provider-specific text.
|
|
5109
|
+
// Do not fall back to VAT-bearing category "S": a fresh sandbox sender has no VAT number.
|
|
5098
5110
|
vatCategory: "O",
|
|
5099
5111
|
taxExemptReason: "Not subject to VAT"
|
|
5100
5112
|
}
|