@teleporthq/teleport-shared 0.43.65 → 0.43.67

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.
Files changed (41) hide show
  1. package/__tests__/utils/order-line-tax.ts +302 -0
  2. package/__tests__/utils/regional-pricing.ts +1056 -0
  3. package/dist/cjs/index.d.ts +2 -1
  4. package/dist/cjs/index.d.ts.map +1 -1
  5. package/dist/cjs/index.js +3 -1
  6. package/dist/cjs/index.js.map +1 -1
  7. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  8. package/dist/cjs/utils/regional-pricing.d.ts +46 -0
  9. package/dist/cjs/utils/regional-pricing.d.ts.map +1 -0
  10. package/dist/cjs/utils/regional-pricing.js +50 -0
  11. package/dist/cjs/utils/regional-pricing.js.map +1 -0
  12. package/dist/cjs/utils/storefront-tax.d.ts +17 -0
  13. package/dist/cjs/utils/storefront-tax.d.ts.map +1 -1
  14. package/dist/cjs/utils/storefront-tax.js +19 -1
  15. package/dist/cjs/utils/storefront-tax.js.map +1 -1
  16. package/dist/cjs/utils/string-utils.d.ts +8 -0
  17. package/dist/cjs/utils/string-utils.d.ts.map +1 -1
  18. package/dist/cjs/utils/string-utils.js +14 -1
  19. package/dist/cjs/utils/string-utils.js.map +1 -1
  20. package/dist/esm/index.d.ts +2 -1
  21. package/dist/esm/index.d.ts.map +1 -1
  22. package/dist/esm/index.js +2 -1
  23. package/dist/esm/index.js.map +1 -1
  24. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  25. package/dist/esm/utils/regional-pricing.d.ts +46 -0
  26. package/dist/esm/utils/regional-pricing.d.ts.map +1 -0
  27. package/dist/esm/utils/regional-pricing.js +46 -0
  28. package/dist/esm/utils/regional-pricing.js.map +1 -0
  29. package/dist/esm/utils/storefront-tax.d.ts +17 -0
  30. package/dist/esm/utils/storefront-tax.d.ts.map +1 -1
  31. package/dist/esm/utils/storefront-tax.js +17 -0
  32. package/dist/esm/utils/storefront-tax.js.map +1 -1
  33. package/dist/esm/utils/string-utils.d.ts +8 -0
  34. package/dist/esm/utils/string-utils.d.ts.map +1 -1
  35. package/dist/esm/utils/string-utils.js +12 -0
  36. package/dist/esm/utils/string-utils.js.map +1 -1
  37. package/package.json +3 -3
  38. package/src/index.ts +2 -0
  39. package/src/utils/regional-pricing.ts +597 -0
  40. package/src/utils/storefront-tax.ts +81 -0
  41. package/src/utils/string-utils.ts +12 -0
@@ -0,0 +1,1056 @@
1
+ import { generateRegionalPricingHelperCode } from '../../src/utils/regional-pricing'
2
+
3
+ /**
4
+ * Regional pricing — the ES5 copy the published storefront runs.
5
+ *
6
+ * ⚠️ The fixture table below is SHARED with the editor/canvas copy in
7
+ * teleport-gui (`features/e-commerce/utils/__tests__/regional-pricing-parity.spec.ts`).
8
+ * Two specs, one table: that is what proves the canvas and the deployed store
9
+ * quote the same shipping and tax for the same basket.
10
+ */
11
+
12
+ interface RegionalPricingApi {
13
+ normalize: (raw: unknown) => unknown
14
+ quote: (input: unknown) => Record<string, unknown>
15
+ parseCountries: (raw: unknown) => string[]
16
+ weightKg: (weight: unknown, unit: unknown) => number
17
+ readDestination: (countryCodes: unknown) => Record<string, unknown> | null
18
+ }
19
+
20
+ const loadApi = (): RegionalPricingApi =>
21
+ new Function(
22
+ generateRegionalPricingHelperCode() +
23
+ '\nreturn { normalize: __rpNormalizeConfig, quote: __rpQuote, parseCountries: __rpParseCountries,' +
24
+ ' weightKg: __rpWeightKg, readDestination: __rpReadCheckoutDestination };'
25
+ )() as RegionalPricingApi
26
+
27
+ // ── SHARED PARITY TABLE ─────────────────────────────────────────────────────
28
+ // Byte-identical in teleport-gui `regional-pricing-parity.spec.ts` and
29
+ // teleport-code-generators `teleport-shared/__tests__/utils/regional-pricing.ts`.
30
+ // Rows are what `/api/data/.../select` returns: strings for DECIMAL columns,
31
+ // driver booleans ('t'/'f'), nullable text.
32
+ interface RegionalQuoteFixture {
33
+ name: string
34
+ input: {
35
+ config: {
36
+ zones?: Array<Record<string, unknown>>
37
+ rates?: Array<Record<string, unknown>>
38
+ taxRates?: Array<Record<string, unknown>>
39
+ }
40
+ store: {
41
+ deliveryEnabled: boolean
42
+ storePickupEnabled: boolean
43
+ deliveryPrice: number
44
+ freeDeliveryEnabled: boolean
45
+ freeDeliveryThreshold: number
46
+ defaultTaxRate: number
47
+ defaultTaxIncluded: boolean
48
+ }
49
+ items: Array<Record<string, unknown>>
50
+ destination: { countryCode: string; countryName: string; region: string } | null
51
+ fulfillment: 'delivery' | 'pickup'
52
+ selectedRateId: string | null
53
+ currency: string
54
+ }
55
+ expected: Record<string, unknown>
56
+ }
57
+
58
+ const PARITY_ZONES: Array<Record<string, unknown>> = [
59
+ {
60
+ id: 'z-eu',
61
+ name: 'EU',
62
+ countries: 'DE, FR,at',
63
+ is_rest_of_world: false,
64
+ free_shipping_threshold: '80.00',
65
+ cod_enabled: 'f',
66
+ is_active: 't',
67
+ sort_order: 1,
68
+ },
69
+ {
70
+ id: 'z-row',
71
+ name: 'Rest of world',
72
+ countries: null,
73
+ is_rest_of_world: true,
74
+ free_shipping_threshold: null,
75
+ cod_enabled: true,
76
+ is_active: true,
77
+ sort_order: 9,
78
+ },
79
+ {
80
+ id: 'z-off',
81
+ name: 'Disabled',
82
+ countries: '["US"]',
83
+ is_rest_of_world: false,
84
+ is_active: false,
85
+ sort_order: 0,
86
+ },
87
+ ]
88
+
89
+ const PARITY_RATES: Array<Record<string, unknown>> = [
90
+ {
91
+ id: 'r-eu-std',
92
+ zone_id: 'z-eu',
93
+ name: 'Standard',
94
+ rate_type: 'flat',
95
+ price: '6.00',
96
+ estimated_days: '3-5 days',
97
+ is_active: true,
98
+ sort_order: 1,
99
+ },
100
+ {
101
+ id: 'r-eu-exp',
102
+ zone_id: 'z-eu',
103
+ name: 'Express',
104
+ rate_type: 'flat',
105
+ price: '15',
106
+ estimated_days: '1-2 days',
107
+ is_active: true,
108
+ sort_order: 2,
109
+ },
110
+ {
111
+ id: 'r-eu-bulk',
112
+ zone_id: 'z-eu',
113
+ name: 'Bulk order',
114
+ rate_type: 'price',
115
+ price: 0,
116
+ min_value: '200',
117
+ max_value: null,
118
+ is_active: 't',
119
+ sort_order: 3,
120
+ },
121
+ {
122
+ id: 'r-row-light',
123
+ zone_id: 'z-row',
124
+ name: 'Light parcel',
125
+ rate_type: 'weight',
126
+ price: 12,
127
+ min_value: null,
128
+ max_value: 2,
129
+ is_active: true,
130
+ sort_order: 1,
131
+ },
132
+ {
133
+ id: 'r-row-heavy',
134
+ zone_id: 'z-row',
135
+ name: 'Heavy parcel',
136
+ rate_type: 'weight',
137
+ price: 30,
138
+ min_value: 2,
139
+ max_value: null,
140
+ is_active: true,
141
+ sort_order: 2,
142
+ },
143
+ {
144
+ id: 'r-row-bulk',
145
+ zone_id: 'z-row',
146
+ name: 'Large order',
147
+ rate_type: 'price',
148
+ price: 20,
149
+ min_value: 150,
150
+ max_value: null,
151
+ is_active: true,
152
+ sort_order: 3,
153
+ },
154
+ ]
155
+
156
+ const PARITY_TAX_RATES: Array<Record<string, unknown>> = [
157
+ {
158
+ id: 't-de',
159
+ country_code: 'de',
160
+ region: null,
161
+ category_id: null,
162
+ rate: '19',
163
+ tax_mode: 'added',
164
+ tax_shipping: 't',
165
+ is_active: true,
166
+ },
167
+ {
168
+ id: 't-de-food',
169
+ country_code: 'DE',
170
+ region: null,
171
+ category_id: 'cat-food',
172
+ rate: 7,
173
+ tax_mode: 'inherit',
174
+ tax_shipping: false,
175
+ is_active: true,
176
+ },
177
+ {
178
+ id: 't-fr',
179
+ country_code: 'FR',
180
+ region: '',
181
+ category_id: '',
182
+ rate: 20,
183
+ tax_mode: 'included',
184
+ tax_shipping: true,
185
+ is_active: true,
186
+ },
187
+ {
188
+ id: 't-jp',
189
+ country_code: 'JP',
190
+ region: null,
191
+ category_id: null,
192
+ rate: 10,
193
+ tax_mode: 'added',
194
+ tax_shipping: false,
195
+ is_active: true,
196
+ },
197
+ {
198
+ id: 't-us',
199
+ country_code: 'US',
200
+ region: null,
201
+ category_id: null,
202
+ rate: 0,
203
+ tax_mode: 'inherit',
204
+ tax_shipping: false,
205
+ is_active: true,
206
+ },
207
+ {
208
+ id: 't-us-ca',
209
+ country_code: 'US',
210
+ region: 'California',
211
+ category_id: null,
212
+ rate: 7.25,
213
+ tax_mode: 'added',
214
+ tax_shipping: false,
215
+ is_active: true,
216
+ },
217
+ ]
218
+
219
+ const PARITY_STORE = {
220
+ deliveryEnabled: true,
221
+ storePickupEnabled: true,
222
+ deliveryPrice: 5,
223
+ freeDeliveryEnabled: true,
224
+ freeDeliveryThreshold: 100,
225
+ defaultTaxRate: 20,
226
+ defaultTaxIncluded: false,
227
+ }
228
+
229
+ const PARITY_FOOD_LINE = {
230
+ id: 'a',
231
+ productId: 'p1',
232
+ price: 10,
233
+ quantity: 2,
234
+ weight: 500,
235
+ weightUnit: 'g',
236
+ categoryIds: ['cat-food'],
237
+ }
238
+
239
+ const PARITY_TOOL_LINE = {
240
+ id: 'b',
241
+ productId: 'p2',
242
+ variantId: 'v2',
243
+ price: '25.99',
244
+ quantity: '1',
245
+ weight: '1.2',
246
+ weightUnit: 'kg',
247
+ categoryIds: '["cat-tools"]',
248
+ }
249
+
250
+ const PARITY_CONFIG = { zones: PARITY_ZONES, rates: PARITY_RATES, taxRates: PARITY_TAX_RATES }
251
+
252
+ const GERMANY = { countryCode: 'DE', countryName: 'Germany', region: '' }
253
+ const BRAZIL = { countryCode: 'BR', countryName: 'Brazil', region: '' }
254
+
255
+ const REGIONAL_QUOTE_FIXTURES: RegionalQuoteFixture[] = [
256
+ {
257
+ name: 'no tables: the flat fee and the default rate, rounded to cents',
258
+ input: {
259
+ config: {},
260
+ store: PARITY_STORE,
261
+ items: [PARITY_FOOD_LINE, PARITY_TOOL_LINE],
262
+ destination: null,
263
+ fulfillment: 'delivery',
264
+ selectedRateId: null,
265
+ currency: 'USD',
266
+ },
267
+ expected: {
268
+ status: 'ok',
269
+ fulfillment: 'delivery',
270
+ taxRegional: false,
271
+ zonesActive: false,
272
+ goodsNet: 45.99,
273
+ goodsTax: 9.2,
274
+ goodsGross: 55.19,
275
+ breakdown: [{ rate: 20, included: false, gross: 55.19, tax: 9.2 }],
276
+ shippingBase: 5,
277
+ shippingNet: 5,
278
+ shippingGross: 5,
279
+ shippingIsFree: false,
280
+ freeShippingThreshold: 100,
281
+ freeShippingRemaining: 44.81,
282
+ codAvailable: true,
283
+ total: 60.19,
284
+ },
285
+ },
286
+ {
287
+ name: 'no tables: tax-inclusive prices meet the free-delivery threshold exactly',
288
+ input: {
289
+ config: {},
290
+ store: { ...PARITY_STORE, defaultTaxIncluded: true },
291
+ items: [{ id: 'c', price: 100, quantity: 1 }],
292
+ destination: null,
293
+ fulfillment: 'delivery',
294
+ selectedRateId: null,
295
+ currency: 'USD',
296
+ },
297
+ expected: {
298
+ goodsTax: 0,
299
+ goodsGross: 100,
300
+ breakdown: [{ rate: 20, included: true, gross: 100, tax: 16.67 }],
301
+ shippingIsFree: true,
302
+ shippingNet: 0,
303
+ freeShippingProgress: 100,
304
+ freeShippingRemaining: 0,
305
+ total: 100,
306
+ },
307
+ },
308
+ {
309
+ name: 'store pickup: no shipping and no zone check, taxed by the address like any order',
310
+ input: {
311
+ config: PARITY_CONFIG,
312
+ store: PARITY_STORE,
313
+ items: [PARITY_FOOD_LINE],
314
+ destination: GERMANY,
315
+ fulfillment: 'pickup',
316
+ selectedRateId: null,
317
+ currency: 'EUR',
318
+ },
319
+ expected: {
320
+ status: 'ok',
321
+ fulfillment: 'pickup',
322
+ zonesActive: false,
323
+ taxRate: 19,
324
+ lines: [{ key: 'a', unitGross: 10.7, taxRate: 7, taxIncluded: false }],
325
+ goodsTax: 1.4,
326
+ shippingNet: 0,
327
+ shippingTax: 0,
328
+ zone: null,
329
+ options: [],
330
+ codAvailable: true,
331
+ total: 21.4,
332
+ },
333
+ },
334
+ {
335
+ name: 'Germany: category override, cheapest rate, zone threshold not reached, shipping taxed',
336
+ input: {
337
+ config: PARITY_CONFIG,
338
+ store: PARITY_STORE,
339
+ items: [PARITY_FOOD_LINE, PARITY_TOOL_LINE],
340
+ destination: GERMANY,
341
+ fulfillment: 'delivery',
342
+ selectedRateId: null,
343
+ currency: 'EUR',
344
+ },
345
+ expected: {
346
+ status: 'ok',
347
+ zonesActive: true,
348
+ taxRegional: true,
349
+ lines: [
350
+ { key: 'a', productId: 'p1', unitGross: 10.7, lineGross: 21.4, taxRate: 7 },
351
+ { key: 'b', productId: 'p2', variantId: 'v2', unitGross: 30.93, taxRate: 19 },
352
+ ],
353
+ goodsNet: 45.99,
354
+ goodsTax: 6.34,
355
+ goodsGross: 52.33,
356
+ taxRate: 19,
357
+ taxIncluded: false,
358
+ breakdown: [
359
+ { rate: 7, included: false, gross: 21.4, tax: 1.4 },
360
+ { rate: 19, included: false, gross: 30.93, tax: 4.94 },
361
+ ],
362
+ zone: { id: 'z-eu', name: 'EU' },
363
+ options: [
364
+ {
365
+ id: 'r-eu-std',
366
+ name: 'Standard',
367
+ estimatedDays: '3-5 days',
368
+ price: 6,
369
+ gross: 7.14,
370
+ isFree: false,
371
+ },
372
+ {
373
+ id: 'r-eu-exp',
374
+ name: 'Express',
375
+ estimatedDays: '1-2 days',
376
+ price: 15,
377
+ gross: 17.85,
378
+ isFree: false,
379
+ },
380
+ ],
381
+ selectedRateId: 'r-eu-std',
382
+ rateName: 'Standard',
383
+ freeShippingThreshold: 80,
384
+ freeShippingRemaining: 27.67,
385
+ shippingNet: 6,
386
+ shippingTax: 1.14,
387
+ shippingGross: 7.14,
388
+ codAvailable: false,
389
+ total: 59.47,
390
+ },
391
+ },
392
+ {
393
+ name: 'Germany: the chosen rate is kept, and the zone threshold makes it free',
394
+ input: {
395
+ config: PARITY_CONFIG,
396
+ store: PARITY_STORE,
397
+ items: [{ ...PARITY_TOOL_LINE, quantity: 3 }],
398
+ destination: GERMANY,
399
+ fulfillment: 'delivery',
400
+ selectedRateId: 'r-eu-exp',
401
+ currency: 'EUR',
402
+ },
403
+ expected: {
404
+ goodsGross: 92.79,
405
+ selectedRateId: 'r-eu-exp',
406
+ shippingBase: 15,
407
+ shippingIsFree: true,
408
+ shippingNet: 0,
409
+ shippingTax: 0,
410
+ options: [
411
+ { id: 'r-eu-std', gross: 0, isFree: true },
412
+ { id: 'r-eu-exp', gross: 0, isFree: true },
413
+ ],
414
+ total: 92.79,
415
+ },
416
+ },
417
+ {
418
+ name: 'rest of world: price tier included at its lower bound, a stale choice falls back to the cheapest',
419
+ input: {
420
+ config: PARITY_CONFIG,
421
+ store: PARITY_STORE,
422
+ items: [{ id: 'd', price: 150, quantity: 1 }],
423
+ destination: BRAZIL,
424
+ fulfillment: 'delivery',
425
+ selectedRateId: 'r-gone',
426
+ currency: 'USD',
427
+ },
428
+ expected: {
429
+ zone: { id: 'z-row', name: 'Rest of world' },
430
+ options: [
431
+ { id: 'r-row-light', price: 12 },
432
+ { id: 'r-row-bulk', price: 20 },
433
+ ],
434
+ selectedRateId: 'r-row-light',
435
+ freeShippingThreshold: null,
436
+ shippingNet: 12,
437
+ codAvailable: true,
438
+ total: 192,
439
+ },
440
+ },
441
+ {
442
+ name: 'rest of world: a weight tier upper bound is exclusive',
443
+ input: {
444
+ config: PARITY_CONFIG,
445
+ store: PARITY_STORE,
446
+ items: [{ id: 'e', price: 10, quantity: 1, weight: 2000, weightUnit: 'g' }],
447
+ destination: BRAZIL,
448
+ fulfillment: 'delivery',
449
+ selectedRateId: null,
450
+ currency: 'USD',
451
+ },
452
+ expected: {
453
+ options: [{ id: 'r-row-heavy', price: 30 }],
454
+ selectedRateId: 'r-row-heavy',
455
+ shippingNet: 30,
456
+ total: 42,
457
+ },
458
+ },
459
+ {
460
+ name: 'United States: a region row outranks the country row, shipping untaxed',
461
+ input: {
462
+ config: PARITY_CONFIG,
463
+ store: PARITY_STORE,
464
+ items: [{ id: 'f', price: 19.99, quantity: 2 }],
465
+ destination: { countryCode: 'US', countryName: 'United States', region: 'california' },
466
+ fulfillment: 'delivery',
467
+ selectedRateId: null,
468
+ currency: 'USD',
469
+ },
470
+ expected: {
471
+ lines: [{ key: 'f', unitGross: 21.44, lineGross: 42.88, taxRate: 7.25 }],
472
+ goodsTax: 2.9,
473
+ taxRate: 7.25,
474
+ shippingTax: 0,
475
+ shippingGross: 12,
476
+ total: 54.88,
477
+ },
478
+ },
479
+ {
480
+ name: 'United States: another region falls to the zero-rate country row',
481
+ input: {
482
+ config: PARITY_CONFIG,
483
+ store: PARITY_STORE,
484
+ items: [{ id: 'f', price: 19.99, quantity: 2 }],
485
+ destination: { countryCode: 'US', countryName: 'United States', region: 'Texas' },
486
+ fulfillment: 'delivery',
487
+ selectedRateId: null,
488
+ currency: 'USD',
489
+ },
490
+ expected: {
491
+ goodsTax: 0,
492
+ goodsGross: 39.98,
493
+ taxRate: 0,
494
+ breakdown: [],
495
+ total: 51.98,
496
+ },
497
+ },
498
+ {
499
+ name: 'France: prices include tax, nothing is added and the shipping tax is contained',
500
+ input: {
501
+ config: PARITY_CONFIG,
502
+ store: PARITY_STORE,
503
+ items: [PARITY_TOOL_LINE],
504
+ destination: { countryCode: 'FR', countryName: 'France', region: '' },
505
+ fulfillment: 'delivery',
506
+ selectedRateId: null,
507
+ currency: 'EUR',
508
+ },
509
+ expected: {
510
+ lines: [{ key: 'b', unitGross: 25.99, taxRate: 20, taxIncluded: true }],
511
+ goodsTax: 0,
512
+ goodsGross: 25.99,
513
+ taxIncluded: true,
514
+ breakdown: [{ rate: 20, included: true, gross: 25.99, tax: 4.33 }],
515
+ shippingNet: 6,
516
+ shippingTax: 1,
517
+ shippingGross: 6,
518
+ total: 31.99,
519
+ },
520
+ },
521
+ {
522
+ name: 'no zone lists the country and there is no rest of world: the store fee and threshold, as without zones',
523
+ input: {
524
+ config: { zones: [PARITY_ZONES[0]], rates: PARITY_RATES, taxRates: PARITY_TAX_RATES },
525
+ store: PARITY_STORE,
526
+ items: [PARITY_FOOD_LINE],
527
+ destination: BRAZIL,
528
+ fulfillment: 'delivery',
529
+ selectedRateId: null,
530
+ currency: 'USD',
531
+ },
532
+ expected: {
533
+ status: 'ok',
534
+ zonesActive: true,
535
+ zone: null,
536
+ options: [],
537
+ selectedRateId: null,
538
+ rateName: '',
539
+ goodsGross: 24,
540
+ shippingBase: 5,
541
+ shippingNet: 5,
542
+ shippingGross: 5,
543
+ freeShippingThreshold: 100,
544
+ freeShippingRemaining: 76,
545
+ codAvailable: true,
546
+ total: 29,
547
+ },
548
+ },
549
+ {
550
+ name: 'a zone whose rates do not fit the basket: no rate',
551
+ input: {
552
+ config: { zones: [PARITY_ZONES[1]], rates: [PARITY_RATES[4]], taxRates: [] },
553
+ store: PARITY_STORE,
554
+ items: [PARITY_FOOD_LINE],
555
+ destination: BRAZIL,
556
+ fulfillment: 'delivery',
557
+ selectedRateId: null,
558
+ currency: 'USD',
559
+ },
560
+ expected: {
561
+ status: 'no-rate',
562
+ taxRegional: false,
563
+ zone: { id: 'z-row', name: 'Rest of world' },
564
+ options: [],
565
+ shippingNet: 0,
566
+ },
567
+ },
568
+ {
569
+ name: 'a zone with no active rate ships at the store fee, with its own threshold and cash-on-delivery rule',
570
+ input: {
571
+ config: {
572
+ zones: [PARITY_ZONES[0]],
573
+ rates: [
574
+ PARITY_RATES[3],
575
+ {
576
+ id: 'r-eu-off',
577
+ zone_id: 'z-eu',
578
+ name: 'Paused',
579
+ rate_type: 'flat',
580
+ price: 99,
581
+ is_active: false,
582
+ sort_order: 1,
583
+ },
584
+ ],
585
+ taxRates: PARITY_TAX_RATES,
586
+ },
587
+ store: PARITY_STORE,
588
+ items: [PARITY_FOOD_LINE, PARITY_TOOL_LINE],
589
+ destination: GERMANY,
590
+ fulfillment: 'delivery',
591
+ selectedRateId: null,
592
+ currency: 'EUR',
593
+ },
594
+ expected: {
595
+ status: 'ok',
596
+ zone: { id: 'z-eu', name: 'EU' },
597
+ options: [],
598
+ selectedRateId: null,
599
+ rateName: '',
600
+ shippingBase: 5,
601
+ shippingNet: 5,
602
+ shippingTax: 0.95,
603
+ shippingGross: 5.95,
604
+ freeShippingThreshold: 80,
605
+ freeShippingRemaining: 27.67,
606
+ codAvailable: false,
607
+ total: 58.28,
608
+ },
609
+ },
610
+ {
611
+ name: 'a zone with no active rate: its own threshold makes the store fee free, not the store threshold',
612
+ input: {
613
+ config: { zones: [PARITY_ZONES[0]], rates: [], taxRates: PARITY_TAX_RATES },
614
+ store: PARITY_STORE,
615
+ items: [{ ...PARITY_TOOL_LINE, quantity: 3 }],
616
+ destination: GERMANY,
617
+ fulfillment: 'delivery',
618
+ selectedRateId: null,
619
+ currency: 'EUR',
620
+ },
621
+ expected: {
622
+ status: 'ok',
623
+ goodsGross: 92.79,
624
+ shippingBase: 5,
625
+ shippingIsFree: true,
626
+ shippingNet: 0,
627
+ shippingGross: 0,
628
+ total: 92.79,
629
+ },
630
+ },
631
+ {
632
+ name: 'a rest-of-world zone with no rate ships at the store fee and ignores the store threshold',
633
+ input: {
634
+ config: { zones: [PARITY_ZONES[1]], rates: [], taxRates: [] },
635
+ store: PARITY_STORE,
636
+ items: [{ id: 'd', price: 150, quantity: 1 }],
637
+ destination: BRAZIL,
638
+ fulfillment: 'delivery',
639
+ selectedRateId: null,
640
+ currency: 'USD',
641
+ },
642
+ expected: {
643
+ status: 'ok',
644
+ zone: { id: 'z-row', name: 'Rest of world' },
645
+ goodsGross: 180,
646
+ freeShippingThreshold: null,
647
+ shippingIsFree: false,
648
+ shippingNet: 5,
649
+ codAvailable: true,
650
+ total: 185,
651
+ },
652
+ },
653
+ {
654
+ name: 'a zone marked do-not-ship refuses delivery to its countries',
655
+ input: {
656
+ config: {
657
+ zones: [
658
+ {
659
+ id: 'z-no',
660
+ name: 'No delivery',
661
+ countries: 'BR',
662
+ is_rest_of_world: false,
663
+ do_not_ship: 't',
664
+ is_active: true,
665
+ sort_order: 0,
666
+ },
667
+ PARITY_ZONES[1],
668
+ ],
669
+ rates: PARITY_RATES,
670
+ taxRates: [],
671
+ },
672
+ store: PARITY_STORE,
673
+ items: [PARITY_FOOD_LINE],
674
+ destination: BRAZIL,
675
+ fulfillment: 'delivery',
676
+ selectedRateId: null,
677
+ currency: 'USD',
678
+ },
679
+ expected: {
680
+ status: 'unavailable',
681
+ zone: { id: 'z-no', name: 'No delivery' },
682
+ options: [],
683
+ selectedRateId: null,
684
+ rateName: '',
685
+ freeShippingThreshold: null,
686
+ shippingNet: 0,
687
+ shippingGross: 0,
688
+ codAvailable: true,
689
+ total: 24,
690
+ },
691
+ },
692
+ {
693
+ name: 'a rest-of-world zone marked do-not-ship means "only my zones": an unlisted country is refused',
694
+ input: {
695
+ config: {
696
+ zones: [PARITY_ZONES[0], { ...PARITY_ZONES[1], do_not_ship: true }],
697
+ rates: PARITY_RATES,
698
+ taxRates: PARITY_TAX_RATES,
699
+ },
700
+ store: PARITY_STORE,
701
+ items: [PARITY_FOOD_LINE],
702
+ destination: BRAZIL,
703
+ fulfillment: 'delivery',
704
+ selectedRateId: null,
705
+ currency: 'USD',
706
+ },
707
+ expected: {
708
+ status: 'unavailable',
709
+ zone: { id: 'z-row', name: 'Rest of world' },
710
+ options: [],
711
+ shippingNet: 0,
712
+ total: 24,
713
+ },
714
+ },
715
+ {
716
+ name: 'a rest-of-world zone marked do-not-ship: a listed country still ships with its own zone',
717
+ input: {
718
+ config: {
719
+ zones: [PARITY_ZONES[0], { ...PARITY_ZONES[1], do_not_ship: true }],
720
+ rates: PARITY_RATES,
721
+ taxRates: PARITY_TAX_RATES,
722
+ },
723
+ store: PARITY_STORE,
724
+ items: [PARITY_FOOD_LINE],
725
+ destination: GERMANY,
726
+ fulfillment: 'delivery',
727
+ selectedRateId: null,
728
+ currency: 'EUR',
729
+ },
730
+ expected: {
731
+ status: 'ok',
732
+ zone: { id: 'z-eu', name: 'EU' },
733
+ selectedRateId: 'r-eu-std',
734
+ shippingNet: 6,
735
+ },
736
+ },
737
+ {
738
+ name: 'store pickup is never refused, even from a do-not-ship zone',
739
+ input: {
740
+ config: {
741
+ zones: [
742
+ {
743
+ id: 'z-no',
744
+ name: 'No delivery',
745
+ countries: 'BR',
746
+ is_rest_of_world: false,
747
+ do_not_ship: true,
748
+ is_active: true,
749
+ sort_order: 0,
750
+ },
751
+ ],
752
+ rates: [],
753
+ taxRates: [],
754
+ },
755
+ store: PARITY_STORE,
756
+ items: [PARITY_FOOD_LINE],
757
+ destination: BRAZIL,
758
+ fulfillment: 'pickup',
759
+ selectedRateId: null,
760
+ currency: 'USD',
761
+ },
762
+ expected: {
763
+ status: 'ok',
764
+ fulfillment: 'pickup',
765
+ zonesActive: false,
766
+ zone: null,
767
+ shippingNet: 0,
768
+ total: 24,
769
+ },
770
+ },
771
+ {
772
+ name: 'zones configured but no address yet: pending',
773
+ input: {
774
+ config: PARITY_CONFIG,
775
+ store: PARITY_STORE,
776
+ items: [PARITY_FOOD_LINE],
777
+ destination: null,
778
+ fulfillment: 'delivery',
779
+ selectedRateId: null,
780
+ currency: 'EUR',
781
+ },
782
+ expected: {
783
+ status: 'pending',
784
+ taxRate: 20,
785
+ shippingNet: 0,
786
+ shippingIsFree: false,
787
+ total: 24,
788
+ },
789
+ },
790
+ {
791
+ name: 'a zero-decimal currency rounds regional tax to the whole unit',
792
+ input: {
793
+ config: { taxRates: [PARITY_TAX_RATES[3]] },
794
+ store: { ...PARITY_STORE, deliveryPrice: 500, freeDeliveryEnabled: false },
795
+ items: [{ id: 'g', price: 999, quantity: 3 }],
796
+ destination: { countryCode: 'JP', countryName: 'Japan', region: '' },
797
+ fulfillment: 'delivery',
798
+ selectedRateId: null,
799
+ currency: 'JPY',
800
+ },
801
+ expected: {
802
+ lines: [{ key: 'g', unitGross: 1099, lineGross: 3297 }],
803
+ goodsNet: 2997,
804
+ goodsTax: 300,
805
+ zonesActive: false,
806
+ shippingNet: 500,
807
+ freeShippingThreshold: null,
808
+ total: 3797,
809
+ },
810
+ },
811
+ {
812
+ name: 'inactive rows count as no rows',
813
+ input: {
814
+ config: { zones: [PARITY_ZONES[2]], rates: PARITY_RATES },
815
+ store: PARITY_STORE,
816
+ items: [PARITY_FOOD_LINE],
817
+ destination: { countryCode: 'US', countryName: 'United States', region: '' },
818
+ fulfillment: 'delivery',
819
+ selectedRateId: null,
820
+ currency: 'USD',
821
+ },
822
+ expected: {
823
+ taxRegional: false,
824
+ zonesActive: false,
825
+ shippingNet: 5,
826
+ freeShippingRemaining: 76,
827
+ total: 29,
828
+ },
829
+ },
830
+ {
831
+ name: 'a store that does not deliver prices every order as a pickup',
832
+ input: {
833
+ config: PARITY_CONFIG,
834
+ store: { ...PARITY_STORE, deliveryEnabled: false },
835
+ items: [PARITY_FOOD_LINE],
836
+ destination: GERMANY,
837
+ fulfillment: 'delivery',
838
+ selectedRateId: null,
839
+ currency: 'EUR',
840
+ },
841
+ expected: {
842
+ fulfillment: 'pickup',
843
+ zonesActive: false,
844
+ taxRate: 19,
845
+ shippingNet: 0,
846
+ total: 21.4,
847
+ },
848
+ },
849
+ {
850
+ name: 'a zero-decimal currency without regional tax keeps cent-precise goods and total',
851
+ input: {
852
+ config: {},
853
+ store: {
854
+ ...PARITY_STORE,
855
+ deliveryPrice: 500,
856
+ freeDeliveryEnabled: false,
857
+ defaultTaxRate: 10,
858
+ },
859
+ items: [{ id: 'j', price: 999, quantity: 1 }],
860
+ destination: { countryCode: 'JP', countryName: 'Japan', region: '' },
861
+ fulfillment: 'delivery',
862
+ selectedRateId: null,
863
+ currency: 'JPY',
864
+ },
865
+ expected: {
866
+ taxRegional: false,
867
+ taxDecimals: 2,
868
+ goodsGross: 1098.9,
869
+ shippingGross: 500,
870
+ total: 1598.9,
871
+ },
872
+ },
873
+ {
874
+ name: 'a tier with a negative or inverted range is skipped, never read as unbounded',
875
+ input: {
876
+ config: {
877
+ zones: [PARITY_ZONES[1]],
878
+ rates: [
879
+ {
880
+ id: 'r-bad-negative',
881
+ zone_id: 'z-row',
882
+ name: 'Negative bound',
883
+ rate_type: 'price',
884
+ price: 1,
885
+ min_value: null,
886
+ max_value: '-1',
887
+ is_active: true,
888
+ sort_order: 1,
889
+ },
890
+ {
891
+ id: 'r-bad-inverted',
892
+ zone_id: 'z-row',
893
+ name: 'Inverted range',
894
+ rate_type: 'weight',
895
+ price: 2,
896
+ min_value: 50,
897
+ max_value: 20,
898
+ is_active: true,
899
+ sort_order: 2,
900
+ },
901
+ {
902
+ id: 'r-flat',
903
+ zone_id: 'z-row',
904
+ name: 'Flat, stray bounds ignored',
905
+ rate_type: 'flat',
906
+ price: 9,
907
+ min_value: -5,
908
+ max_value: null,
909
+ is_active: true,
910
+ sort_order: 3,
911
+ },
912
+ ],
913
+ },
914
+ store: PARITY_STORE,
915
+ items: [{ id: 'k', price: 10, quantity: 1 }],
916
+ destination: BRAZIL,
917
+ fulfillment: 'delivery',
918
+ selectedRateId: null,
919
+ currency: 'USD',
920
+ },
921
+ expected: {
922
+ status: 'ok',
923
+ options: [{ id: 'r-flat' }],
924
+ selectedRateId: 'r-flat',
925
+ shippingNet: 9,
926
+ total: 21,
927
+ },
928
+ },
929
+ {
930
+ name: 'a region typed with other spacing and case still finds its row',
931
+ input: {
932
+ config: {
933
+ taxRates: [
934
+ PARITY_TAX_RATES[4],
935
+ {
936
+ id: 't-us-ny',
937
+ country_code: 'US',
938
+ region: 'New York',
939
+ category_id: null,
940
+ rate: 4,
941
+ tax_mode: 'added',
942
+ tax_shipping: false,
943
+ is_active: true,
944
+ },
945
+ ],
946
+ },
947
+ store: PARITY_STORE,
948
+ items: [{ id: 'l', price: 100, quantity: 1 }],
949
+ destination: { countryCode: 'US', countryName: 'United States', region: ' new YORK ' },
950
+ fulfillment: 'delivery',
951
+ selectedRateId: null,
952
+ currency: 'USD',
953
+ },
954
+ expected: {
955
+ taxRate: 4,
956
+ goodsTax: 4,
957
+ shippingIsFree: true,
958
+ total: 104,
959
+ },
960
+ },
961
+ ]
962
+ // ── end of shared parity table ──────────────────────────────────────────────
963
+
964
+ describe('regional pricing — storefront copy against the shared parity table', () => {
965
+ const api = loadApi()
966
+
967
+ // Titled through `%s`: this jest predates `$name` interpolation of object rows.
968
+ it.each(REGIONAL_QUOTE_FIXTURES.map((fixture) => [fixture.name, fixture] as const))(
969
+ '%s',
970
+ (_name, { input, expected }) => {
971
+ const quote = api.quote({ ...input, config: api.normalize(input.config) })
972
+ expect(quote).toMatchObject(expected)
973
+ }
974
+ )
975
+ })
976
+
977
+ describe('regional pricing — row parsing', () => {
978
+ const api = loadApi()
979
+
980
+ it('reads countries from a typed list or a JSON array, keeping only ISO codes', () => {
981
+ expect(api.parseCountries('de, FR;at it')).toEqual(['DE', 'FR', 'AT', 'IT'])
982
+ expect(api.parseCountries('["us","US","Canada"]')).toEqual(['US'])
983
+ expect(api.parseCountries(null)).toEqual([])
984
+ expect(api.parseCountries('[not json')).toEqual([])
985
+ })
986
+
987
+ it('converts every weight unit to kilograms and treats a missing weight as nothing', () => {
988
+ expect(api.weightKg('1500', 'g')).toBe(1.5)
989
+ expect(api.weightKg('1500', 'gr')).toBe(1.5)
990
+ expect(api.weightKg(2, 'lb')).toBeCloseTo(0.90718474, 8)
991
+ expect(api.weightKg(16, 'oz')).toBeCloseTo(0.45359237, 8)
992
+ expect(api.weightKg(3, 'KG')).toBe(3)
993
+ expect(api.weightKg(null, 'kg')).toBe(0)
994
+ expect(api.weightKg(-1, 'kg')).toBe(0)
995
+ })
996
+
997
+ it('never throws on a malformed quote input', () => {
998
+ expect(() => api.quote(null)).not.toThrow()
999
+ expect(api.quote(undefined)).toMatchObject({ status: 'ok', total: 0, lines: [] })
1000
+ })
1001
+ })
1002
+
1003
+ describe('regional pricing — reading the destination off the checkout form', () => {
1004
+ const api = loadApi()
1005
+ const CODES = { germany: 'DE', 'united states': 'US' }
1006
+ const originalDocument = (global as { document?: unknown }).document
1007
+
1008
+ const mountFields = (fields: Record<string, string>) => {
1009
+ ;(global as { document?: unknown }).document = {
1010
+ querySelector: (selector: string) => {
1011
+ const match = /^\[name="([^"]+)"\]$/.exec(selector)
1012
+ const name = match ? match[1] : ''
1013
+ return name in fields ? { value: fields[name] } : null
1014
+ },
1015
+ }
1016
+ }
1017
+
1018
+ afterEach(() => {
1019
+ ;(global as { document?: unknown }).document = originalDocument
1020
+ })
1021
+
1022
+ it('is null anywhere the checkout form is not on the page', () => {
1023
+ mountFields({})
1024
+ expect(api.readDestination(CODES)).toBeNull()
1025
+ })
1026
+
1027
+ it('uses the billing address until the shipping address block is in the page', () => {
1028
+ mountFields({ billingCountry: ' Germany ', state: 'Bavaria' })
1029
+ expect(api.readDestination(CODES)).toEqual({
1030
+ countryCode: 'DE',
1031
+ countryName: 'Germany',
1032
+ region: 'Bavaria',
1033
+ })
1034
+ })
1035
+
1036
+ it('uses the shipping address once its block is in the page', () => {
1037
+ mountFields({
1038
+ billingCountry: 'Germany',
1039
+ state: 'Bavaria',
1040
+ shippingCountry: 'United States',
1041
+ shippingState: 'California',
1042
+ })
1043
+ expect(api.readDestination(CODES)).toEqual({
1044
+ countryCode: 'US',
1045
+ countryName: 'United States',
1046
+ region: 'California',
1047
+ })
1048
+ })
1049
+
1050
+ it('accepts a typed ISO code and leaves an unknown country without one', () => {
1051
+ mountFields({ billingCountry: 'fr' })
1052
+ expect(api.readDestination(CODES)).toMatchObject({ countryCode: 'FR' })
1053
+ mountFields({ billingCountry: 'Atlantis' })
1054
+ expect(api.readDestination(CODES)).toMatchObject({ countryCode: '' })
1055
+ })
1056
+ })