@labdigital/commercetools-mock 2.59.0 → 2.60.0
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.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +338 -172
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/tax.test.ts +119 -0
- package/src/lib/tax.ts +186 -0
- package/src/repositories/cart/actions.ts +67 -0
- package/src/repositories/cart/helpers.ts +1 -80
- package/src/repositories/cart/index.test.ts +48 -0
- package/src/repositories/cart/index.ts +16 -141
- package/src/repositories/order/index.test.ts +490 -0
- package/src/repositories/order/index.ts +145 -7
- package/src/services/cart.test.ts +211 -0
- package/src/shipping.ts +157 -0
package/package.json
CHANGED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TaxCategory,
|
|
3
|
+
TaxRate,
|
|
4
|
+
TaxedItemPrice,
|
|
5
|
+
} from "@commercetools/platform-sdk";
|
|
6
|
+
import { describe, expect, test } from "vitest";
|
|
7
|
+
import {
|
|
8
|
+
calculateTaxTotals,
|
|
9
|
+
calculateTaxedPrice,
|
|
10
|
+
calculateTaxedPriceFromRate,
|
|
11
|
+
} from "~src/lib/tax";
|
|
12
|
+
|
|
13
|
+
const money = (centAmount: number) => ({
|
|
14
|
+
type: "centPrecision" as const,
|
|
15
|
+
currencyCode: "EUR",
|
|
16
|
+
centAmount,
|
|
17
|
+
fractionDigits: 2,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const createTaxedItemPrice = (net: number, gross: number, rate = 0.21) => ({
|
|
21
|
+
totalNet: money(net),
|
|
22
|
+
totalGross: money(gross),
|
|
23
|
+
totalTax: money(gross - net),
|
|
24
|
+
taxPortions: [
|
|
25
|
+
{
|
|
26
|
+
rate,
|
|
27
|
+
amount: money(gross - net),
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("tax helpers", () => {
|
|
33
|
+
test("calculateTaxTotals aggregates line, custom, and shipping taxes", () => {
|
|
34
|
+
const lineTaxed = createTaxedItemPrice(1000, 1210);
|
|
35
|
+
const customTaxed = createTaxedItemPrice(500, 605);
|
|
36
|
+
const shippingTaxed: TaxedItemPrice = createTaxedItemPrice(300, 363);
|
|
37
|
+
|
|
38
|
+
const resource = {
|
|
39
|
+
lineItems: [{ taxedPrice: lineTaxed }] as any,
|
|
40
|
+
customLineItems: [{ taxedPrice: customTaxed }] as any,
|
|
41
|
+
shippingInfo: { taxedPrice: shippingTaxed } as any,
|
|
42
|
+
totalPrice: money(0),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const { taxedPrice, taxedShippingPrice } = calculateTaxTotals(resource);
|
|
46
|
+
|
|
47
|
+
expect(taxedPrice).toBeDefined();
|
|
48
|
+
expect(taxedPrice?.totalNet.centAmount).toBe(1000 + 500 + 300);
|
|
49
|
+
expect(taxedPrice?.totalGross.centAmount).toBe(1210 + 605 + 363);
|
|
50
|
+
expect(taxedPrice?.totalTax?.centAmount).toBe(210 + 105 + 63);
|
|
51
|
+
expect(taxedPrice?.taxPortions).toHaveLength(1);
|
|
52
|
+
expect(taxedPrice?.taxPortions?.[0].amount.centAmount).toBe(378);
|
|
53
|
+
expect(taxedShippingPrice).toEqual(shippingTaxed);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("calculateTaxedPriceFromRate handles net amounts", () => {
|
|
57
|
+
const rate: TaxRate = {
|
|
58
|
+
amount: 0.2,
|
|
59
|
+
includedInPrice: false,
|
|
60
|
+
name: "Standard",
|
|
61
|
+
country: "NL",
|
|
62
|
+
id: "rate",
|
|
63
|
+
subRates: [],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const taxed = calculateTaxedPriceFromRate(1000, "EUR", rate)!;
|
|
67
|
+
expect(taxed.totalNet.centAmount).toBe(1000);
|
|
68
|
+
expect(taxed.totalGross.centAmount).toBe(1200);
|
|
69
|
+
expect(taxed.totalTax?.centAmount).toBe(200);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("calculateTaxedPriceFromRate handles gross amounts", () => {
|
|
73
|
+
const rate: TaxRate = {
|
|
74
|
+
amount: 0.25,
|
|
75
|
+
includedInPrice: true,
|
|
76
|
+
name: "Gross",
|
|
77
|
+
id: "gross",
|
|
78
|
+
country: "BE",
|
|
79
|
+
subRates: [],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const taxed = calculateTaxedPriceFromRate(1250, "EUR", rate)!;
|
|
83
|
+
expect(taxed.totalGross.centAmount).toBe(1250);
|
|
84
|
+
expect(taxed.totalNet.centAmount).toBe(1000);
|
|
85
|
+
expect(taxed.totalTax?.centAmount).toBe(250);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("calculateTaxedPrice selects matching tax rate from category", () => {
|
|
89
|
+
const taxCategory: TaxCategory = {
|
|
90
|
+
id: "tax-cat",
|
|
91
|
+
version: 1,
|
|
92
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
93
|
+
lastModifiedAt: "2024-01-01T00:00:00.000Z",
|
|
94
|
+
name: "Standard",
|
|
95
|
+
rates: [
|
|
96
|
+
{
|
|
97
|
+
id: "default",
|
|
98
|
+
amount: 0.1,
|
|
99
|
+
includedInPrice: false,
|
|
100
|
+
country: "DE",
|
|
101
|
+
name: "DE",
|
|
102
|
+
subRates: [],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "nl",
|
|
106
|
+
amount: 0.21,
|
|
107
|
+
includedInPrice: false,
|
|
108
|
+
country: "NL",
|
|
109
|
+
name: "NL",
|
|
110
|
+
subRates: [],
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const taxed = calculateTaxedPrice(1000, taxCategory, "EUR", "NL")!;
|
|
116
|
+
expect(taxed.totalGross.centAmount).toBe(1210);
|
|
117
|
+
expect(taxed.totalTax?.centAmount).toBe(210);
|
|
118
|
+
});
|
|
119
|
+
});
|
package/src/lib/tax.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Cart,
|
|
3
|
+
TaxCategory,
|
|
4
|
+
TaxPortion,
|
|
5
|
+
TaxRate,
|
|
6
|
+
TaxedItemPrice,
|
|
7
|
+
TaxedPrice,
|
|
8
|
+
} from "@commercetools/platform-sdk";
|
|
9
|
+
import { createCentPrecisionMoney } from "~src/repositories/helpers";
|
|
10
|
+
|
|
11
|
+
type TaxableResource = Pick<
|
|
12
|
+
Cart,
|
|
13
|
+
"lineItems" | "customLineItems" | "shippingInfo" | "totalPrice"
|
|
14
|
+
>;
|
|
15
|
+
|
|
16
|
+
export const calculateTaxTotals = (
|
|
17
|
+
resource: TaxableResource,
|
|
18
|
+
): {
|
|
19
|
+
taxedPrice?: TaxedPrice;
|
|
20
|
+
taxedShippingPrice?: TaxedItemPrice;
|
|
21
|
+
} => {
|
|
22
|
+
const taxedItemPrices: TaxedItemPrice[] = [];
|
|
23
|
+
|
|
24
|
+
resource.lineItems.forEach((item) => {
|
|
25
|
+
if (item.taxedPrice) {
|
|
26
|
+
taxedItemPrices.push(item.taxedPrice);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
resource.customLineItems.forEach((item) => {
|
|
31
|
+
if (item.taxedPrice) {
|
|
32
|
+
taxedItemPrices.push(item.taxedPrice);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
let taxedShippingPrice: TaxedItemPrice | undefined;
|
|
37
|
+
if (resource.shippingInfo?.taxedPrice) {
|
|
38
|
+
taxedShippingPrice = resource.shippingInfo.taxedPrice;
|
|
39
|
+
taxedItemPrices.push(resource.shippingInfo.taxedPrice);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!taxedItemPrices.length) {
|
|
43
|
+
return {
|
|
44
|
+
taxedPrice: undefined,
|
|
45
|
+
taxedShippingPrice,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const currencyCode = resource.totalPrice.currencyCode;
|
|
50
|
+
const toMoney = (centAmount: number) =>
|
|
51
|
+
createCentPrecisionMoney({
|
|
52
|
+
currencyCode,
|
|
53
|
+
centAmount,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
let totalNet = 0;
|
|
57
|
+
let totalGross = 0;
|
|
58
|
+
let totalTax = 0;
|
|
59
|
+
|
|
60
|
+
const taxPortionsByRate = new Map<
|
|
61
|
+
string,
|
|
62
|
+
{ rate: number; name?: string; centAmount: number }
|
|
63
|
+
>();
|
|
64
|
+
|
|
65
|
+
taxedItemPrices.forEach((price) => {
|
|
66
|
+
totalNet += price.totalNet.centAmount;
|
|
67
|
+
totalGross += price.totalGross.centAmount;
|
|
68
|
+
const priceTax = price.totalTax
|
|
69
|
+
? price.totalTax.centAmount
|
|
70
|
+
: price.totalGross.centAmount - price.totalNet.centAmount;
|
|
71
|
+
totalTax += Math.max(priceTax, 0);
|
|
72
|
+
|
|
73
|
+
price.taxPortions?.forEach((portion) => {
|
|
74
|
+
const key = `${portion.rate}-${portion.name ?? ""}`;
|
|
75
|
+
const existing = taxPortionsByRate.get(key) ?? {
|
|
76
|
+
rate: portion.rate,
|
|
77
|
+
name: portion.name,
|
|
78
|
+
centAmount: 0,
|
|
79
|
+
};
|
|
80
|
+
existing.centAmount += portion.amount.centAmount;
|
|
81
|
+
taxPortionsByRate.set(key, existing);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const taxPortions: TaxPortion[] = Array.from(taxPortionsByRate.values()).map(
|
|
86
|
+
(portion) => ({
|
|
87
|
+
rate: portion.rate,
|
|
88
|
+
name: portion.name,
|
|
89
|
+
amount: toMoney(portion.centAmount),
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
taxedPrice: {
|
|
95
|
+
totalNet: toMoney(totalNet),
|
|
96
|
+
totalGross: toMoney(totalGross),
|
|
97
|
+
taxPortions,
|
|
98
|
+
totalTax: totalTax > 0 ? toMoney(totalTax) : undefined,
|
|
99
|
+
},
|
|
100
|
+
taxedShippingPrice,
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const buildTaxedPriceFromRate = (
|
|
105
|
+
amount: number,
|
|
106
|
+
currencyCode: string,
|
|
107
|
+
taxRate?: TaxRate,
|
|
108
|
+
): TaxedItemPrice | undefined => {
|
|
109
|
+
if (!taxRate) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const toMoney = (centAmount: number) =>
|
|
114
|
+
createCentPrecisionMoney({
|
|
115
|
+
type: "centPrecision",
|
|
116
|
+
currencyCode,
|
|
117
|
+
centAmount,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
let netAmount: number;
|
|
121
|
+
let grossAmount: number;
|
|
122
|
+
let taxAmount: number;
|
|
123
|
+
|
|
124
|
+
if (taxRate.includedInPrice) {
|
|
125
|
+
grossAmount = amount;
|
|
126
|
+
taxAmount = Math.round(
|
|
127
|
+
(grossAmount * taxRate.amount) / (1 + taxRate.amount),
|
|
128
|
+
);
|
|
129
|
+
netAmount = grossAmount - taxAmount;
|
|
130
|
+
} else {
|
|
131
|
+
netAmount = amount;
|
|
132
|
+
taxAmount = Math.round(netAmount * taxRate.amount);
|
|
133
|
+
grossAmount = netAmount + taxAmount;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
totalNet: toMoney(netAmount),
|
|
138
|
+
totalGross: toMoney(grossAmount),
|
|
139
|
+
totalTax: taxAmount > 0 ? toMoney(taxAmount) : undefined,
|
|
140
|
+
taxPortions:
|
|
141
|
+
taxAmount > 0
|
|
142
|
+
? [
|
|
143
|
+
{
|
|
144
|
+
rate: taxRate.amount,
|
|
145
|
+
name: taxRate.name,
|
|
146
|
+
amount: toMoney(taxAmount),
|
|
147
|
+
},
|
|
148
|
+
]
|
|
149
|
+
: [],
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const calculateTaxedPriceFromRate = (
|
|
154
|
+
amount: number,
|
|
155
|
+
currencyCode: string,
|
|
156
|
+
taxRate?: TaxRate,
|
|
157
|
+
): TaxedItemPrice | undefined =>
|
|
158
|
+
buildTaxedPriceFromRate(amount, currencyCode, taxRate);
|
|
159
|
+
|
|
160
|
+
export const calculateTaxedPrice = (
|
|
161
|
+
amount: number,
|
|
162
|
+
taxCategory: TaxCategory | undefined,
|
|
163
|
+
currency: string,
|
|
164
|
+
country: string | undefined,
|
|
165
|
+
): TaxedPrice | undefined => {
|
|
166
|
+
if (!taxCategory || !taxCategory.rates.length) {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const taxRate =
|
|
171
|
+
taxCategory.rates.find(
|
|
172
|
+
(rate) => !rate.country || rate.country === country,
|
|
173
|
+
) || taxCategory.rates[0];
|
|
174
|
+
|
|
175
|
+
const taxedItemPrice = buildTaxedPriceFromRate(amount, currency, taxRate);
|
|
176
|
+
if (!taxedItemPrice) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
totalNet: taxedItemPrice.totalNet,
|
|
182
|
+
totalGross: taxedItemPrice.totalGross,
|
|
183
|
+
taxPortions: taxedItemPrice.taxPortions,
|
|
184
|
+
totalTax: taxedItemPrice.totalTax,
|
|
185
|
+
};
|
|
186
|
+
};
|
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
CartSetDirectDiscountsAction,
|
|
29
29
|
CartSetLineItemCustomFieldAction,
|
|
30
30
|
CartSetLineItemCustomTypeAction,
|
|
31
|
+
CartSetLineItemPriceAction,
|
|
31
32
|
CartSetLineItemShippingDetailsAction,
|
|
32
33
|
CartSetLocaleAction,
|
|
33
34
|
CartSetShippingAddressAction,
|
|
@@ -740,6 +741,72 @@ export class CartUpdateHandler
|
|
|
740
741
|
}
|
|
741
742
|
}
|
|
742
743
|
|
|
744
|
+
setLineItemPrice(
|
|
745
|
+
context: RepositoryContext,
|
|
746
|
+
resource: Writable<Cart>,
|
|
747
|
+
{ lineItemId, lineItemKey, externalPrice }: CartSetLineItemPriceAction,
|
|
748
|
+
) {
|
|
749
|
+
const lineItem = resource.lineItems.find(
|
|
750
|
+
(x) =>
|
|
751
|
+
(lineItemId && x.id === lineItemId) ||
|
|
752
|
+
(lineItemKey && x.key === lineItemKey),
|
|
753
|
+
);
|
|
754
|
+
|
|
755
|
+
if (!lineItem) {
|
|
756
|
+
throw new CommercetoolsError<GeneralError>({
|
|
757
|
+
code: "General",
|
|
758
|
+
message: lineItemKey
|
|
759
|
+
? `A line item with key '${lineItemKey}' not found.`
|
|
760
|
+
: `A line item with ID '${lineItemId}' not found.`,
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (!externalPrice && lineItem.priceMode !== "ExternalPrice") {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (
|
|
769
|
+
externalPrice &&
|
|
770
|
+
externalPrice.currencyCode !== resource.totalPrice.currencyCode
|
|
771
|
+
) {
|
|
772
|
+
throw new CommercetoolsError<GeneralError>({
|
|
773
|
+
code: "General",
|
|
774
|
+
message: `Currency mismatch. Expected '${resource.totalPrice.currencyCode}' but got '${externalPrice.currencyCode}'.`,
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (externalPrice) {
|
|
779
|
+
lineItem.priceMode = "ExternalPrice";
|
|
780
|
+
const priceValue = createTypedMoney(externalPrice);
|
|
781
|
+
|
|
782
|
+
lineItem.price = lineItem.price ?? { id: uuidv4() };
|
|
783
|
+
lineItem.price.value = priceValue;
|
|
784
|
+
} else {
|
|
785
|
+
lineItem.priceMode = "Platform";
|
|
786
|
+
|
|
787
|
+
const price = selectPrice({
|
|
788
|
+
prices: lineItem.variant.prices,
|
|
789
|
+
currency: resource.totalPrice.currencyCode,
|
|
790
|
+
country: resource.country,
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
if (!price) {
|
|
794
|
+
throw new Error(
|
|
795
|
+
`No valid price found for ${lineItem.productId} for country ${resource.country} and currency ${resource.totalPrice.currencyCode}`,
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
lineItem.price = price;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const lineItemTotal = calculateLineItemTotalPrice(lineItem);
|
|
803
|
+
lineItem.totalPrice = createCentPrecisionMoney({
|
|
804
|
+
...lineItem.price!.value,
|
|
805
|
+
centAmount: lineItemTotal,
|
|
806
|
+
});
|
|
807
|
+
resource.totalPrice.centAmount = calculateCartTotalPrice(resource);
|
|
808
|
+
}
|
|
809
|
+
|
|
743
810
|
setLineItemShippingDetails(
|
|
744
811
|
context: RepositoryContext,
|
|
745
812
|
resource: Writable<Cart>,
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
Cart,
|
|
3
|
-
CentPrecisionMoney,
|
|
4
3
|
CustomLineItem,
|
|
5
4
|
CustomLineItemDraft,
|
|
6
5
|
LineItem,
|
|
7
6
|
Price,
|
|
8
7
|
TaxCategory,
|
|
9
8
|
TaxCategoryReference,
|
|
10
|
-
TaxedPrice,
|
|
11
9
|
} from "@commercetools/platform-sdk";
|
|
12
10
|
import { v4 as uuidv4 } from "uuid";
|
|
11
|
+
import { calculateTaxedPrice } from "~src/lib/tax";
|
|
13
12
|
import type { AbstractStorage } from "~src/storage/abstract";
|
|
14
13
|
import {
|
|
15
14
|
createCentPrecisionMoney,
|
|
@@ -56,84 +55,6 @@ export const calculateCartTotalPrice = (cart: Cart): number => {
|
|
|
56
55
|
return lineItemsTotal + customLineItemsTotal;
|
|
57
56
|
};
|
|
58
57
|
|
|
59
|
-
export const calculateTaxedPrice = (
|
|
60
|
-
amount: number,
|
|
61
|
-
taxCategory: TaxCategory | undefined,
|
|
62
|
-
currency: string,
|
|
63
|
-
country: string | undefined,
|
|
64
|
-
): TaxedPrice | undefined => {
|
|
65
|
-
if (!taxCategory || !taxCategory.rates.length) {
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Find the appropriate tax rate for the country
|
|
70
|
-
const taxRate =
|
|
71
|
-
taxCategory.rates.find(
|
|
72
|
-
(rate) => !rate.country || rate.country === country,
|
|
73
|
-
) || taxCategory.rates[0]; // Fallback to first rate if no country-specific rate found
|
|
74
|
-
|
|
75
|
-
if (!taxRate) {
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let netAmount: number;
|
|
80
|
-
let grossAmount: number;
|
|
81
|
-
let taxAmount: number;
|
|
82
|
-
|
|
83
|
-
if (taxRate.includedInPrice) {
|
|
84
|
-
// Amount is gross, calculate net
|
|
85
|
-
grossAmount = amount;
|
|
86
|
-
taxAmount = Math.round(
|
|
87
|
-
(grossAmount * taxRate.amount) / (1 + taxRate.amount),
|
|
88
|
-
);
|
|
89
|
-
netAmount = grossAmount - taxAmount;
|
|
90
|
-
} else {
|
|
91
|
-
// Amount is net, calculate gross
|
|
92
|
-
netAmount = amount;
|
|
93
|
-
taxAmount = Math.round(netAmount * taxRate.amount);
|
|
94
|
-
grossAmount = netAmount + taxAmount;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
totalNet: {
|
|
99
|
-
type: "centPrecision",
|
|
100
|
-
currencyCode: currency,
|
|
101
|
-
centAmount: netAmount,
|
|
102
|
-
fractionDigits: 2,
|
|
103
|
-
},
|
|
104
|
-
totalGross: {
|
|
105
|
-
type: "centPrecision",
|
|
106
|
-
currencyCode: currency,
|
|
107
|
-
centAmount: grossAmount,
|
|
108
|
-
fractionDigits: 2,
|
|
109
|
-
},
|
|
110
|
-
taxPortions:
|
|
111
|
-
taxAmount > 0
|
|
112
|
-
? [
|
|
113
|
-
{
|
|
114
|
-
rate: taxRate.amount,
|
|
115
|
-
amount: {
|
|
116
|
-
type: "centPrecision",
|
|
117
|
-
currencyCode: currency,
|
|
118
|
-
centAmount: taxAmount,
|
|
119
|
-
fractionDigits: 2,
|
|
120
|
-
},
|
|
121
|
-
name: taxRate.name,
|
|
122
|
-
},
|
|
123
|
-
]
|
|
124
|
-
: [],
|
|
125
|
-
totalTax:
|
|
126
|
-
taxAmount > 0
|
|
127
|
-
? {
|
|
128
|
-
type: "centPrecision",
|
|
129
|
-
currencyCode: currency,
|
|
130
|
-
centAmount: taxAmount,
|
|
131
|
-
fractionDigits: 2,
|
|
132
|
-
}
|
|
133
|
-
: undefined,
|
|
134
|
-
};
|
|
135
|
-
};
|
|
136
|
-
|
|
137
58
|
export const createCustomLineItemFromDraft = (
|
|
138
59
|
projectKey: string,
|
|
139
60
|
draft: CustomLineItemDraft,
|
|
@@ -389,6 +389,54 @@ describe("Cart repository", () => {
|
|
|
389
389
|
expect(customLineItem.taxRate?.includedInPrice).toBe(false);
|
|
390
390
|
expect(customLineItem.taxRate?.country).toBe("NL");
|
|
391
391
|
});
|
|
392
|
+
|
|
393
|
+
test("should calculate taxed price for the cart", () => {
|
|
394
|
+
storage.add("dummy", "tax-category", {
|
|
395
|
+
...getBaseResourceProperties(),
|
|
396
|
+
id: "cart-tax-category",
|
|
397
|
+
key: "cart-vat-tax",
|
|
398
|
+
name: "Cart VAT Tax",
|
|
399
|
+
rates: [
|
|
400
|
+
{
|
|
401
|
+
id: "cart-rate-1",
|
|
402
|
+
name: "Standard VAT",
|
|
403
|
+
amount: 0.21,
|
|
404
|
+
includedInPrice: false,
|
|
405
|
+
country: "NL",
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const cart: CartDraft = {
|
|
411
|
+
currency: "EUR",
|
|
412
|
+
country: "NL",
|
|
413
|
+
customLineItems: [
|
|
414
|
+
{
|
|
415
|
+
name: { en: "Gift Wrap" },
|
|
416
|
+
slug: "gift-wrap",
|
|
417
|
+
money: {
|
|
418
|
+
currencyCode: "EUR",
|
|
419
|
+
centAmount: 1000,
|
|
420
|
+
},
|
|
421
|
+
quantity: 1,
|
|
422
|
+
taxCategory: {
|
|
423
|
+
typeId: "tax-category" as const,
|
|
424
|
+
id: "cart-tax-category",
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const ctx = { projectKey: "dummy", storeKey: "dummyStore" };
|
|
431
|
+
const result = repository.create(ctx, cart);
|
|
432
|
+
|
|
433
|
+
expect(result.taxedPrice).toBeDefined();
|
|
434
|
+
expect(result.taxedPrice?.totalNet.centAmount).toBe(1000);
|
|
435
|
+
expect(result.taxedPrice?.totalGross.centAmount).toBe(1210);
|
|
436
|
+
expect(result.taxedPrice?.totalTax?.centAmount).toBe(210);
|
|
437
|
+
expect(result.taxedPrice?.taxPortions).toHaveLength(1);
|
|
438
|
+
expect(result.taxedPrice?.taxPortions?.[0].rate).toBe(0.21);
|
|
439
|
+
});
|
|
392
440
|
});
|
|
393
441
|
|
|
394
442
|
describe("createShippingInfo", () => {
|