@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.
- package/__tests__/utils/order-line-tax.ts +302 -0
- package/__tests__/utils/regional-pricing.ts +1056 -0
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils/regional-pricing.d.ts +46 -0
- package/dist/cjs/utils/regional-pricing.d.ts.map +1 -0
- package/dist/cjs/utils/regional-pricing.js +50 -0
- package/dist/cjs/utils/regional-pricing.js.map +1 -0
- package/dist/cjs/utils/storefront-tax.d.ts +17 -0
- package/dist/cjs/utils/storefront-tax.d.ts.map +1 -1
- package/dist/cjs/utils/storefront-tax.js +19 -1
- package/dist/cjs/utils/storefront-tax.js.map +1 -1
- package/dist/cjs/utils/string-utils.d.ts +8 -0
- package/dist/cjs/utils/string-utils.d.ts.map +1 -1
- package/dist/cjs/utils/string-utils.js +14 -1
- package/dist/cjs/utils/string-utils.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils/regional-pricing.d.ts +46 -0
- package/dist/esm/utils/regional-pricing.d.ts.map +1 -0
- package/dist/esm/utils/regional-pricing.js +46 -0
- package/dist/esm/utils/regional-pricing.js.map +1 -0
- package/dist/esm/utils/storefront-tax.d.ts +17 -0
- package/dist/esm/utils/storefront-tax.d.ts.map +1 -1
- package/dist/esm/utils/storefront-tax.js +17 -0
- package/dist/esm/utils/storefront-tax.js.map +1 -1
- package/dist/esm/utils/string-utils.d.ts +8 -0
- package/dist/esm/utils/string-utils.d.ts.map +1 -1
- package/dist/esm/utils/string-utils.js +12 -0
- package/dist/esm/utils/string-utils.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +2 -0
- package/src/utils/regional-pricing.ts +597 -0
- package/src/utils/storefront-tax.ts +81 -0
- package/src/utils/string-utils.ts +12 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateOrderLineTaxHelperCode,
|
|
3
|
+
generateStorefrontTaxHelperCode,
|
|
4
|
+
} from '../../src/utils/storefront-tax'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* What one order line cost the buyer — the ES5 copy the generated API routes
|
|
8
|
+
* run (the merchant's order-notification email).
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ The fixture table below is SHARED with teleport-gui's
|
|
11
|
+
* `features/e-commerce/utils/__tests__/order-line-pricing-parity.spec.ts`, which
|
|
12
|
+
* runs it over the storefront's baked script and the editor's TypeScript. Edit
|
|
13
|
+
* the table in both files or in neither.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// ── SHARED PARITY TABLE ─────────────────────────────────────────────────────
|
|
17
|
+
// Byte-identical in teleport-gui `order-line-pricing-parity.spec.ts` and
|
|
18
|
+
// teleport-code-generators `teleport-shared/__tests__/utils/order-line-tax.ts`.
|
|
19
|
+
// `breakdown` is `teleport_orders.tax_breakdown` as the drivers hand it back
|
|
20
|
+
// (JSON text, or an object where the driver decodes JSON columns); the stored
|
|
21
|
+
// rate and flag are `teleport_order_items.tax_rate` / `tax_included` read
|
|
22
|
+
// through `to_jsonb(...) ->>` (text) or a typed fetch (number / boolean / 't').
|
|
23
|
+
interface OrderLineTaxFixture {
|
|
24
|
+
name: string
|
|
25
|
+
storefrontTaxRate: number
|
|
26
|
+
breakdown: unknown
|
|
27
|
+
productId: string
|
|
28
|
+
variantId: string
|
|
29
|
+
storedRate: unknown
|
|
30
|
+
storedIncluded: unknown
|
|
31
|
+
amount: number
|
|
32
|
+
expected: { rate: number; included: boolean; decimals: number; gross: number }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ROMANIAN_BREAKDOWN = JSON.stringify({
|
|
36
|
+
taxRate: 20,
|
|
37
|
+
taxIncluded: false,
|
|
38
|
+
taxDecimals: 2,
|
|
39
|
+
shippingTax: 0,
|
|
40
|
+
lines: [
|
|
41
|
+
{ productId: 'p1', variantId: '', taxRate: 20, taxIncluded: false },
|
|
42
|
+
{ productId: 'p2', variantId: 'v-blue', taxRate: 9, taxIncluded: true },
|
|
43
|
+
{ productId: 'p3', variantId: '', taxRate: -5, taxIncluded: false },
|
|
44
|
+
],
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const YEN_BREAKDOWN = JSON.stringify({
|
|
48
|
+
taxRate: 10,
|
|
49
|
+
taxIncluded: false,
|
|
50
|
+
taxDecimals: 0,
|
|
51
|
+
shippingTax: 0,
|
|
52
|
+
lines: [{ productId: 'p1', variantId: '', taxRate: 10, taxIncluded: false }],
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const ORDER_LINE_TAX_FIXTURES: OrderLineTaxFixture[] = [
|
|
56
|
+
{
|
|
57
|
+
name: 'no breakdown, no record: the store default is added',
|
|
58
|
+
storefrontTaxRate: 19,
|
|
59
|
+
breakdown: null,
|
|
60
|
+
productId: 'p1',
|
|
61
|
+
variantId: '',
|
|
62
|
+
storedRate: null,
|
|
63
|
+
storedIncluded: null,
|
|
64
|
+
amount: 19.99,
|
|
65
|
+
expected: { rate: 19, included: false, decimals: 2, gross: 23.79 },
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'no breakdown, no record, untaxed store: the amount is untouched, unrounded',
|
|
69
|
+
storefrontTaxRate: 0,
|
|
70
|
+
breakdown: null,
|
|
71
|
+
productId: 'p1',
|
|
72
|
+
variantId: '',
|
|
73
|
+
storedRate: null,
|
|
74
|
+
storedIncluded: null,
|
|
75
|
+
amount: 19.999,
|
|
76
|
+
expected: { rate: 0, included: false, decimals: 2, gross: 19.999 },
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'a breakdown line matched by product: its added rate',
|
|
80
|
+
storefrontTaxRate: 0,
|
|
81
|
+
breakdown: ROMANIAN_BREAKDOWN,
|
|
82
|
+
productId: 'p1',
|
|
83
|
+
variantId: '',
|
|
84
|
+
storedRate: null,
|
|
85
|
+
storedIncluded: null,
|
|
86
|
+
amount: 100,
|
|
87
|
+
expected: { rate: 20, included: false, decimals: 2, gross: 120 },
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'a breakdown line matched by product + variant: an included rate adds nothing',
|
|
91
|
+
storefrontTaxRate: 19,
|
|
92
|
+
breakdown: ROMANIAN_BREAKDOWN,
|
|
93
|
+
productId: 'p2',
|
|
94
|
+
variantId: 'v-blue',
|
|
95
|
+
storedRate: null,
|
|
96
|
+
storedIncluded: null,
|
|
97
|
+
amount: 47,
|
|
98
|
+
expected: { rate: 9, included: true, decimals: 2, gross: 47 },
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'a product the breakdown has no line for: the order standard rate',
|
|
102
|
+
storefrontTaxRate: 0,
|
|
103
|
+
breakdown: ROMANIAN_BREAKDOWN,
|
|
104
|
+
productId: 'p9',
|
|
105
|
+
variantId: '',
|
|
106
|
+
storedRate: null,
|
|
107
|
+
storedIncluded: null,
|
|
108
|
+
amount: 50,
|
|
109
|
+
expected: { rate: 20, included: false, decimals: 2, gross: 60 },
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'a variant the breakdown has no line for falls to the order rate, not the product line',
|
|
113
|
+
storefrontTaxRate: 0,
|
|
114
|
+
breakdown: ROMANIAN_BREAKDOWN,
|
|
115
|
+
productId: 'p2',
|
|
116
|
+
variantId: 'v-red',
|
|
117
|
+
storedRate: null,
|
|
118
|
+
storedIncluded: null,
|
|
119
|
+
amount: 50,
|
|
120
|
+
expected: { rate: 20, included: false, decimals: 2, gross: 60 },
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'a negative rate on a line reads as none',
|
|
124
|
+
storefrontTaxRate: 0,
|
|
125
|
+
breakdown: ROMANIAN_BREAKDOWN,
|
|
126
|
+
productId: 'p3',
|
|
127
|
+
variantId: '',
|
|
128
|
+
storedRate: null,
|
|
129
|
+
storedIncluded: null,
|
|
130
|
+
amount: 50,
|
|
131
|
+
expected: { rate: 0, included: false, decimals: 2, gross: 50 },
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'a zero-decimal currency rounds the gross to whole units',
|
|
135
|
+
storefrontTaxRate: 0,
|
|
136
|
+
breakdown: YEN_BREAKDOWN,
|
|
137
|
+
productId: 'p1',
|
|
138
|
+
variantId: '',
|
|
139
|
+
storedRate: null,
|
|
140
|
+
storedIncluded: null,
|
|
141
|
+
amount: 999,
|
|
142
|
+
expected: { rate: 10, included: false, decimals: 0, gross: 1099 },
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'a breakdown the driver already decoded works like the text',
|
|
146
|
+
storefrontTaxRate: 0,
|
|
147
|
+
breakdown: JSON.parse(ROMANIAN_BREAKDOWN),
|
|
148
|
+
productId: 'p1',
|
|
149
|
+
variantId: '',
|
|
150
|
+
storedRate: null,
|
|
151
|
+
storedIncluded: null,
|
|
152
|
+
amount: 100,
|
|
153
|
+
expected: { rate: 20, included: false, decimals: 2, gross: 120 },
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'a malformed breakdown falls back to the store default',
|
|
157
|
+
storefrontTaxRate: 19,
|
|
158
|
+
breakdown: '{not json',
|
|
159
|
+
productId: 'p1',
|
|
160
|
+
variantId: '',
|
|
161
|
+
storedRate: null,
|
|
162
|
+
storedIncluded: null,
|
|
163
|
+
amount: 100,
|
|
164
|
+
expected: { rate: 19, included: false, decimals: 2, gross: 119 },
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'a recorded rate wins over the breakdown and the store default',
|
|
168
|
+
storefrontTaxRate: 19,
|
|
169
|
+
breakdown: ROMANIAN_BREAKDOWN,
|
|
170
|
+
productId: 'p1',
|
|
171
|
+
variantId: '',
|
|
172
|
+
storedRate: '5.500',
|
|
173
|
+
storedIncluded: 'false',
|
|
174
|
+
amount: 100,
|
|
175
|
+
expected: { rate: 5.5, included: false, decimals: 2, gross: 105.5 },
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "a recorded included flag as the driver spells it ('t') adds nothing",
|
|
179
|
+
storefrontTaxRate: 19,
|
|
180
|
+
breakdown: null,
|
|
181
|
+
productId: 'p1',
|
|
182
|
+
variantId: '',
|
|
183
|
+
storedRate: 19,
|
|
184
|
+
storedIncluded: 't',
|
|
185
|
+
amount: 100,
|
|
186
|
+
expected: { rate: 19, included: true, decimals: 2, gross: 100 },
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'a recorded included flag as JSON text',
|
|
190
|
+
storefrontTaxRate: 0,
|
|
191
|
+
breakdown: null,
|
|
192
|
+
productId: 'p1',
|
|
193
|
+
variantId: '',
|
|
194
|
+
storedRate: '20',
|
|
195
|
+
storedIncluded: 'true',
|
|
196
|
+
amount: 100,
|
|
197
|
+
expected: { rate: 20, included: true, decimals: 2, gross: 100 },
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: 'a recorded rate of 0 is a record, not a missing one',
|
|
201
|
+
storefrontTaxRate: 19,
|
|
202
|
+
breakdown: null,
|
|
203
|
+
productId: 'p1',
|
|
204
|
+
variantId: '',
|
|
205
|
+
storedRate: '0.000',
|
|
206
|
+
storedIncluded: 'false',
|
|
207
|
+
amount: 19.999,
|
|
208
|
+
expected: { rate: 0, included: false, decimals: 2, gross: 19.999 },
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: 'an empty recorded rate (NULL through ->>) derives from the breakdown',
|
|
212
|
+
storefrontTaxRate: 0,
|
|
213
|
+
breakdown: ROMANIAN_BREAKDOWN,
|
|
214
|
+
productId: 'p1',
|
|
215
|
+
variantId: '',
|
|
216
|
+
storedRate: '',
|
|
217
|
+
storedIncluded: '',
|
|
218
|
+
amount: 100,
|
|
219
|
+
expected: { rate: 20, included: false, decimals: 2, gross: 120 },
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: 'a recorded rate keeps the decimals of a zero-decimal order',
|
|
223
|
+
storefrontTaxRate: 0,
|
|
224
|
+
breakdown: YEN_BREAKDOWN,
|
|
225
|
+
productId: 'p1',
|
|
226
|
+
variantId: '',
|
|
227
|
+
storedRate: 10,
|
|
228
|
+
storedIncluded: false,
|
|
229
|
+
amount: 999,
|
|
230
|
+
expected: { rate: 10, included: false, decimals: 0, gross: 1099 },
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: 'a nonsensical recorded rate is ignored like a missing one',
|
|
234
|
+
storefrontTaxRate: 19,
|
|
235
|
+
breakdown: null,
|
|
236
|
+
productId: 'p1',
|
|
237
|
+
variantId: '',
|
|
238
|
+
storedRate: 'abc',
|
|
239
|
+
storedIncluded: 'false',
|
|
240
|
+
amount: 100,
|
|
241
|
+
expected: { rate: 19, included: false, decimals: 2, gross: 119 },
|
|
242
|
+
},
|
|
243
|
+
]
|
|
244
|
+
// ── END SHARED PARITY TABLE ─────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
interface OrderLineTaxApi {
|
|
247
|
+
orderLineTaxOf: (
|
|
248
|
+
breakdown: unknown,
|
|
249
|
+
productId: unknown,
|
|
250
|
+
variantId: unknown,
|
|
251
|
+
storedRate: unknown,
|
|
252
|
+
storedIncluded: unknown
|
|
253
|
+
) => { rate: number; included: boolean; decimals: number }
|
|
254
|
+
grossAmountAt: (amount: unknown, tax: unknown) => number
|
|
255
|
+
paidAmountAt: (paid: unknown, net: unknown, tax: unknown) => number
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const loadApi = (storefrontTaxRate: number): OrderLineTaxApi =>
|
|
259
|
+
new Function(
|
|
260
|
+
generateStorefrontTaxHelperCode(storefrontTaxRate) +
|
|
261
|
+
generateOrderLineTaxHelperCode() +
|
|
262
|
+
'\nreturn { orderLineTaxOf: orderLineTaxOf, grossAmountAt: grossAmountAt, paidAmountAt: paidAmountAt };'
|
|
263
|
+
)() as OrderLineTaxApi
|
|
264
|
+
|
|
265
|
+
describe('order line tax — generated-route copy against the shared parity table', () => {
|
|
266
|
+
for (const fixture of ORDER_LINE_TAX_FIXTURES) {
|
|
267
|
+
it(fixture.name, () => {
|
|
268
|
+
const api = loadApi(fixture.storefrontTaxRate)
|
|
269
|
+
const tax = api.orderLineTaxOf(
|
|
270
|
+
fixture.breakdown,
|
|
271
|
+
fixture.productId,
|
|
272
|
+
fixture.variantId,
|
|
273
|
+
fixture.storedRate,
|
|
274
|
+
fixture.storedIncluded
|
|
275
|
+
)
|
|
276
|
+
expect(tax).toEqual({
|
|
277
|
+
rate: fixture.expected.rate,
|
|
278
|
+
included: fixture.expected.included,
|
|
279
|
+
decimals: fixture.expected.decimals,
|
|
280
|
+
})
|
|
281
|
+
expect(api.grossAmountAt(fixture.amount, tax)).toBe(fixture.expected.gross)
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
describe('order line tax — the recorded paid price', () => {
|
|
287
|
+
const api = loadApi(19)
|
|
288
|
+
const tax = api.orderLineTaxOf(null, 'p1', '', null, null)
|
|
289
|
+
|
|
290
|
+
it('is read verbatim when the line carries one, however the driver spells it', () => {
|
|
291
|
+
expect(api.paidAmountAt('120.00', 100, tax)).toBe(120)
|
|
292
|
+
expect(api.paidAmountAt(120, 100, tax)).toBe(120)
|
|
293
|
+
// A recorded 0 is a free line, not a missing record.
|
|
294
|
+
expect(api.paidAmountAt('0.00', 100, tax)).toBe(0)
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('is derived at the line tax when the record is absent', () => {
|
|
298
|
+
expect(api.paidAmountAt(null, 100, tax)).toBe(119)
|
|
299
|
+
expect(api.paidAmountAt('', 100, tax)).toBe(119)
|
|
300
|
+
expect(api.paidAmountAt(undefined, 19.99, tax)).toBe(23.79)
|
|
301
|
+
})
|
|
302
|
+
})
|