@teleporthq/teleport-shared 0.43.63 → 0.43.66

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.
@@ -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
+ })