@labdigital/commercetools-mock 3.0.0-beta.2 → 3.0.0-beta.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/dist/{config-BcNSzPZz.d.mts → config-CN3DgmWu.d.mts} +7 -5
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +287 -53
- package/dist/index.mjs.map +1 -1
- package/dist/storage/sqlite.d.mts +1 -1
- package/package.json +1 -1
- package/src/lib/tax.test.ts +53 -0
- package/src/lib/tax.ts +90 -13
- package/src/repositories/cart/actions.ts +399 -10
- package/src/repositories/cart/helpers.ts +61 -18
- package/src/repositories/cart/index.test.ts +76 -0
- package/src/repositories/cart/index.ts +38 -5
- package/src/repositories/order/index.test.ts +6 -2
- package/src/repositories/order/index.ts +82 -25
- package/src/services/cart.test.ts +633 -0
- package/src/shipping.ts +21 -12
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
Address,
|
|
3
3
|
AddressDraft,
|
|
4
|
+
BaseResource,
|
|
4
5
|
Cart,
|
|
5
6
|
CartAddCustomLineItemAction,
|
|
6
7
|
CartAddItemShippingAddressAction,
|
|
@@ -16,10 +17,13 @@ import type {
|
|
|
16
17
|
CartSetAnonymousIdAction,
|
|
17
18
|
CartSetBillingAddressAction,
|
|
18
19
|
CartSetBillingAddressCustomTypeAction,
|
|
20
|
+
CartSetCartTotalTaxAction,
|
|
19
21
|
CartSetCountryAction,
|
|
20
22
|
CartSetCustomerEmailAction,
|
|
21
23
|
CartSetCustomerIdAction,
|
|
22
24
|
CartSetCustomFieldAction,
|
|
25
|
+
CartSetCustomLineItemTaxAmountAction,
|
|
26
|
+
CartSetCustomLineItemTaxRateAction,
|
|
23
27
|
CartSetCustomShippingMethodAction,
|
|
24
28
|
CartSetCustomTypeAction,
|
|
25
29
|
CartSetDirectDiscountsAction,
|
|
@@ -27,11 +31,15 @@ import type {
|
|
|
27
31
|
CartSetLineItemCustomTypeAction,
|
|
28
32
|
CartSetLineItemPriceAction,
|
|
29
33
|
CartSetLineItemShippingDetailsAction,
|
|
34
|
+
CartSetLineItemTaxAmountAction,
|
|
35
|
+
CartSetLineItemTaxRateAction,
|
|
30
36
|
CartSetLocaleAction,
|
|
31
37
|
CartSetShippingAddressAction,
|
|
32
38
|
CartSetShippingAddressCustomFieldAction,
|
|
33
39
|
CartSetShippingAddressCustomTypeAction,
|
|
34
40
|
CartSetShippingMethodAction,
|
|
41
|
+
CartSetShippingMethodTaxAmountAction,
|
|
42
|
+
CartSetShippingMethodTaxRateAction,
|
|
35
43
|
CartUpdateAction,
|
|
36
44
|
CustomFields,
|
|
37
45
|
GeneralError,
|
|
@@ -40,7 +48,10 @@ import type {
|
|
|
40
48
|
LineItem,
|
|
41
49
|
Product,
|
|
42
50
|
ProductVariant,
|
|
51
|
+
Project,
|
|
43
52
|
ReferencedResourceNotFoundError,
|
|
53
|
+
TaxRate,
|
|
54
|
+
UpdateAction,
|
|
44
55
|
} from "@commercetools/platform-sdk";
|
|
45
56
|
import type {
|
|
46
57
|
CartAddDiscountCodeAction,
|
|
@@ -50,6 +61,12 @@ import type {
|
|
|
50
61
|
import type { ShippingMethodResourceIdentifier } from "@commercetools/platform-sdk/dist/declarations/src/generated/models/shipping-method";
|
|
51
62
|
import { v4 as uuidv4 } from "uuid";
|
|
52
63
|
import { CommercetoolsError } from "#src/exceptions.ts";
|
|
64
|
+
import {
|
|
65
|
+
buildTaxedPriceFromExternalAmount,
|
|
66
|
+
calculateTaxedPriceFromRate,
|
|
67
|
+
calculateTaxTotals,
|
|
68
|
+
taxRateFromExternalDraft,
|
|
69
|
+
} from "#src/lib/tax.ts";
|
|
53
70
|
import type { Writable } from "#src/types.ts";
|
|
54
71
|
import type { UpdateHandlerInterface } from "../abstract.ts";
|
|
55
72
|
import { AbstractUpdateHandler, type RepositoryContext } from "../abstract.ts";
|
|
@@ -63,6 +80,7 @@ import {
|
|
|
63
80
|
import {
|
|
64
81
|
calculateCartTotalPrice,
|
|
65
82
|
calculateLineItemTotalPrice,
|
|
83
|
+
computeItemTaxedPrice,
|
|
66
84
|
createCustomLineItemFromDraft,
|
|
67
85
|
createDiscountCodeInfoFromCode,
|
|
68
86
|
selectPrice,
|
|
@@ -79,6 +97,30 @@ export class CartUpdateHandler
|
|
|
79
97
|
super(storage);
|
|
80
98
|
this.repository = repository;
|
|
81
99
|
}
|
|
100
|
+
|
|
101
|
+
async apply<R extends BaseResource | Project>(
|
|
102
|
+
context: RepositoryContext,
|
|
103
|
+
resource: R,
|
|
104
|
+
version: number,
|
|
105
|
+
actions: UpdateAction[],
|
|
106
|
+
): Promise<R> {
|
|
107
|
+
const updated = await super.apply(context, resource, version, actions);
|
|
108
|
+
if (updated.version !== resource.version) {
|
|
109
|
+
const cart = updated as unknown as Writable<Cart>;
|
|
110
|
+
if (cart.taxMode === "ExternalAmount") {
|
|
111
|
+
// Cart total tax is set explicitly via setCartTotalTax — never
|
|
112
|
+
// aggregate from line items. Shipping subtotal is just a mirror of
|
|
113
|
+
// shippingInfo.taxedPrice and should still be kept in sync.
|
|
114
|
+
cart.taxedShippingPrice = cart.shippingInfo?.taxedPrice;
|
|
115
|
+
} else {
|
|
116
|
+
const { taxedPrice, taxedShippingPrice } = calculateTaxTotals(cart);
|
|
117
|
+
cart.taxedPrice = taxedPrice;
|
|
118
|
+
cart.taxedShippingPrice = taxedShippingPrice;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return updated;
|
|
122
|
+
}
|
|
123
|
+
|
|
82
124
|
addItemShippingAddress(
|
|
83
125
|
context: RepositoryContext,
|
|
84
126
|
resource: Writable<Cart>,
|
|
@@ -105,6 +147,7 @@ export class CartUpdateHandler
|
|
|
105
147
|
quantity = 1,
|
|
106
148
|
addedAt,
|
|
107
149
|
key,
|
|
150
|
+
externalTaxRate,
|
|
108
151
|
}: CartAddLineItemAction,
|
|
109
152
|
) {
|
|
110
153
|
let product: Product | null = null;
|
|
@@ -169,6 +212,7 @@ export class CartUpdateHandler
|
|
|
169
212
|
if (x.productId === product?.id && x.variant.id === variant?.id) {
|
|
170
213
|
x.quantity += quantity;
|
|
171
214
|
x.totalPrice.centAmount = calculateLineItemTotalPrice(x);
|
|
215
|
+
x.taxedPrice = computeItemTaxedPrice(x, resource.taxRoundingMode);
|
|
172
216
|
}
|
|
173
217
|
});
|
|
174
218
|
} else {
|
|
@@ -197,6 +241,18 @@ export class CartUpdateHandler
|
|
|
197
241
|
currencyCode: price.value.currencyCode,
|
|
198
242
|
centAmount: calculateMoneyTotalCentAmount(price.value, quantity),
|
|
199
243
|
});
|
|
244
|
+
const taxRate =
|
|
245
|
+
resource.taxMode === "External" && externalTaxRate
|
|
246
|
+
? taxRateFromExternalDraft(externalTaxRate)
|
|
247
|
+
: undefined;
|
|
248
|
+
const taxedPrice = taxRate
|
|
249
|
+
? calculateTaxedPriceFromRate(
|
|
250
|
+
totalPrice.centAmount,
|
|
251
|
+
totalPrice.currencyCode,
|
|
252
|
+
taxRate,
|
|
253
|
+
resource.taxRoundingMode,
|
|
254
|
+
)
|
|
255
|
+
: undefined;
|
|
200
256
|
resource.lineItems.push({
|
|
201
257
|
id: uuidv4(),
|
|
202
258
|
key,
|
|
@@ -208,6 +264,8 @@ export class CartUpdateHandler
|
|
|
208
264
|
name: product.masterData.current.name,
|
|
209
265
|
variant,
|
|
210
266
|
price: price,
|
|
267
|
+
taxRate,
|
|
268
|
+
taxedPrice,
|
|
211
269
|
taxedPricePortions: [],
|
|
212
270
|
perMethodTaxRate: [],
|
|
213
271
|
totalPrice,
|
|
@@ -268,6 +326,7 @@ export class CartUpdateHandler
|
|
|
268
326
|
if (x.id === lineItemId && quantity) {
|
|
269
327
|
x.quantity = quantity;
|
|
270
328
|
x.totalPrice.centAmount = calculateLineItemTotalPrice(x);
|
|
329
|
+
x.taxedPrice = computeItemTaxedPrice(x, resource.taxRoundingMode);
|
|
271
330
|
}
|
|
272
331
|
});
|
|
273
332
|
}
|
|
@@ -349,6 +408,7 @@ export class CartUpdateHandler
|
|
|
349
408
|
if (x.id === lineItemId && quantity) {
|
|
350
409
|
x.quantity -= quantity;
|
|
351
410
|
x.totalPrice.centAmount = calculateLineItemTotalPrice(x);
|
|
411
|
+
x.taxedPrice = computeItemTaxedPrice(x, resource.taxRoundingMode);
|
|
352
412
|
}
|
|
353
413
|
});
|
|
354
414
|
}
|
|
@@ -366,6 +426,7 @@ export class CartUpdateHandler
|
|
|
366
426
|
slug,
|
|
367
427
|
quantity = 1,
|
|
368
428
|
taxCategory,
|
|
429
|
+
externalTaxRate,
|
|
369
430
|
custom,
|
|
370
431
|
priceMode = "Standard",
|
|
371
432
|
key,
|
|
@@ -373,9 +434,21 @@ export class CartUpdateHandler
|
|
|
373
434
|
) {
|
|
374
435
|
const customLineItem = await createCustomLineItemFromDraft(
|
|
375
436
|
context.projectKey,
|
|
376
|
-
{
|
|
437
|
+
{
|
|
438
|
+
money,
|
|
439
|
+
name,
|
|
440
|
+
slug,
|
|
441
|
+
quantity,
|
|
442
|
+
taxCategory,
|
|
443
|
+
externalTaxRate,
|
|
444
|
+
custom,
|
|
445
|
+
priceMode,
|
|
446
|
+
key,
|
|
447
|
+
},
|
|
377
448
|
this._storage,
|
|
378
449
|
resource.shippingAddress?.country ?? resource.country,
|
|
450
|
+
resource.taxMode,
|
|
451
|
+
resource.taxRoundingMode,
|
|
379
452
|
);
|
|
380
453
|
|
|
381
454
|
resource.customLineItems.push(customLineItem);
|
|
@@ -466,6 +539,10 @@ export class CartUpdateHandler
|
|
|
466
539
|
quantity,
|
|
467
540
|
),
|
|
468
541
|
});
|
|
542
|
+
customLineItem.taxedPrice = computeItemTaxedPrice(
|
|
543
|
+
customLineItem,
|
|
544
|
+
resource.taxRoundingMode,
|
|
545
|
+
);
|
|
469
546
|
};
|
|
470
547
|
|
|
471
548
|
if (customLineItemId) {
|
|
@@ -512,6 +589,10 @@ export class CartUpdateHandler
|
|
|
512
589
|
customLineItem.quantity,
|
|
513
590
|
),
|
|
514
591
|
});
|
|
592
|
+
customLineItem.taxedPrice = computeItemTaxedPrice(
|
|
593
|
+
customLineItem,
|
|
594
|
+
resource.taxRoundingMode,
|
|
595
|
+
);
|
|
515
596
|
};
|
|
516
597
|
|
|
517
598
|
if (customLineItemId) {
|
|
@@ -637,25 +718,44 @@ export class CartUpdateHandler
|
|
|
637
718
|
externalTaxRate,
|
|
638
719
|
}: CartSetCustomShippingMethodAction,
|
|
639
720
|
) {
|
|
640
|
-
|
|
721
|
+
const isTaxExternal = resource.taxMode === "External";
|
|
722
|
+
|
|
723
|
+
if (externalTaxRate && !isTaxExternal) {
|
|
641
724
|
throw new CommercetoolsError<InvalidOperationError>({
|
|
642
725
|
code: "InvalidOperation",
|
|
643
|
-
message:
|
|
726
|
+
message:
|
|
727
|
+
"An external tax rate can only be set for a Cart with External tax mode.",
|
|
644
728
|
});
|
|
645
729
|
}
|
|
646
730
|
|
|
647
|
-
const tax =
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
731
|
+
const tax =
|
|
732
|
+
!isTaxExternal && taxCategory
|
|
733
|
+
? await this._storage.getByResourceIdentifier<"tax-category">(
|
|
734
|
+
context.projectKey,
|
|
735
|
+
taxCategory,
|
|
736
|
+
)
|
|
737
|
+
: undefined;
|
|
738
|
+
|
|
739
|
+
const taxRate =
|
|
740
|
+
isTaxExternal && externalTaxRate
|
|
741
|
+
? taxRateFromExternalDraft(externalTaxRate)
|
|
742
|
+
: undefined;
|
|
743
|
+
|
|
744
|
+
const price = createCentPrecisionMoney(shippingRate.price);
|
|
745
|
+
const taxedPrice = taxRate
|
|
746
|
+
? calculateTaxedPriceFromRate(
|
|
747
|
+
price.centAmount,
|
|
748
|
+
price.currencyCode,
|
|
749
|
+
taxRate,
|
|
750
|
+
resource.taxRoundingMode,
|
|
651
751
|
)
|
|
652
752
|
: undefined;
|
|
653
753
|
|
|
654
754
|
resource.shippingInfo = {
|
|
655
755
|
shippingMethodName,
|
|
656
|
-
price
|
|
756
|
+
price,
|
|
657
757
|
shippingRate: {
|
|
658
|
-
price
|
|
758
|
+
price,
|
|
659
759
|
tiers: [],
|
|
660
760
|
},
|
|
661
761
|
taxCategory: tax
|
|
@@ -664,6 +764,8 @@ export class CartUpdateHandler
|
|
|
664
764
|
id: tax?.id,
|
|
665
765
|
}
|
|
666
766
|
: undefined,
|
|
767
|
+
taxRate,
|
|
768
|
+
taxedPrice,
|
|
667
769
|
shippingMethodState: "MatchesCart",
|
|
668
770
|
};
|
|
669
771
|
}
|
|
@@ -840,6 +942,10 @@ export class CartUpdateHandler
|
|
|
840
942
|
currencyCode: lineItem.price!.value.currencyCode,
|
|
841
943
|
centAmount: lineItemTotal,
|
|
842
944
|
});
|
|
945
|
+
lineItem.taxedPrice = computeItemTaxedPrice(
|
|
946
|
+
lineItem,
|
|
947
|
+
resource.taxRoundingMode,
|
|
948
|
+
);
|
|
843
949
|
resource.totalPrice.centAmount = calculateCartTotalPrice(resource);
|
|
844
950
|
}
|
|
845
951
|
|
|
@@ -952,19 +1058,302 @@ export class CartUpdateHandler
|
|
|
952
1058
|
async setShippingMethod(
|
|
953
1059
|
context: RepositoryContext,
|
|
954
1060
|
resource: Writable<Cart>,
|
|
955
|
-
{ shippingMethod }: CartSetShippingMethodAction,
|
|
1061
|
+
{ shippingMethod, externalTaxRate }: CartSetShippingMethodAction,
|
|
956
1062
|
) {
|
|
957
1063
|
if (shippingMethod) {
|
|
958
1064
|
resource.shippingInfo = await this.repository.createShippingInfo(
|
|
959
1065
|
context,
|
|
960
1066
|
resource,
|
|
961
1067
|
shippingMethod,
|
|
1068
|
+
externalTaxRate ?? undefined,
|
|
962
1069
|
);
|
|
963
1070
|
} else {
|
|
964
1071
|
resource.shippingInfo = undefined;
|
|
965
1072
|
}
|
|
966
1073
|
}
|
|
967
1074
|
|
|
1075
|
+
setLineItemTaxRate(
|
|
1076
|
+
_context: RepositoryContext,
|
|
1077
|
+
resource: Writable<Cart>,
|
|
1078
|
+
{ lineItemId, lineItemKey, externalTaxRate }: CartSetLineItemTaxRateAction,
|
|
1079
|
+
) {
|
|
1080
|
+
if (resource.taxMode !== "External") {
|
|
1081
|
+
throw new CommercetoolsError<InvalidOperationError>({
|
|
1082
|
+
code: "InvalidOperation",
|
|
1083
|
+
message:
|
|
1084
|
+
"A line item tax rate can only be set for a Cart with External tax mode.",
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
const lineItem = resource.lineItems.find(
|
|
1089
|
+
(x) =>
|
|
1090
|
+
(lineItemId && x.id === lineItemId) ||
|
|
1091
|
+
(lineItemKey && x.key === lineItemKey),
|
|
1092
|
+
);
|
|
1093
|
+
if (!lineItem) {
|
|
1094
|
+
throw new CommercetoolsError<GeneralError>({
|
|
1095
|
+
code: "General",
|
|
1096
|
+
message: lineItemKey
|
|
1097
|
+
? `A line item with key '${lineItemKey}' not found.`
|
|
1098
|
+
: `A line item with ID '${lineItemId}' not found.`,
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
const writable = lineItem as Writable<LineItem>;
|
|
1103
|
+
writable.taxRate = externalTaxRate
|
|
1104
|
+
? taxRateFromExternalDraft(externalTaxRate)
|
|
1105
|
+
: undefined;
|
|
1106
|
+
writable.taxedPrice = computeItemTaxedPrice(
|
|
1107
|
+
writable,
|
|
1108
|
+
resource.taxRoundingMode,
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
setCustomLineItemTaxRate(
|
|
1113
|
+
_context: RepositoryContext,
|
|
1114
|
+
resource: Writable<Cart>,
|
|
1115
|
+
{
|
|
1116
|
+
customLineItemId,
|
|
1117
|
+
customLineItemKey,
|
|
1118
|
+
externalTaxRate,
|
|
1119
|
+
}: CartSetCustomLineItemTaxRateAction,
|
|
1120
|
+
) {
|
|
1121
|
+
if (resource.taxMode !== "External") {
|
|
1122
|
+
throw new CommercetoolsError<InvalidOperationError>({
|
|
1123
|
+
code: "InvalidOperation",
|
|
1124
|
+
message:
|
|
1125
|
+
"A custom line item tax rate can only be set for a Cart with External tax mode.",
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
const customLineItem = resource.customLineItems.find(
|
|
1130
|
+
(x) =>
|
|
1131
|
+
(customLineItemId && x.id === customLineItemId) ||
|
|
1132
|
+
(customLineItemKey && x.key === customLineItemKey),
|
|
1133
|
+
);
|
|
1134
|
+
if (!customLineItem) {
|
|
1135
|
+
throw new CommercetoolsError<GeneralError>({
|
|
1136
|
+
code: "General",
|
|
1137
|
+
message: customLineItemKey
|
|
1138
|
+
? `A custom line item with key '${customLineItemKey}' not found.`
|
|
1139
|
+
: `A custom line item with ID '${customLineItemId}' not found.`,
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
const writable = customLineItem as Writable<CustomLineItem>;
|
|
1144
|
+
writable.taxRate = externalTaxRate
|
|
1145
|
+
? taxRateFromExternalDraft(externalTaxRate)
|
|
1146
|
+
: undefined;
|
|
1147
|
+
writable.taxedPrice = computeItemTaxedPrice(
|
|
1148
|
+
writable,
|
|
1149
|
+
resource.taxRoundingMode,
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
setShippingMethodTaxRate(
|
|
1154
|
+
_context: RepositoryContext,
|
|
1155
|
+
resource: Writable<Cart>,
|
|
1156
|
+
{ externalTaxRate }: CartSetShippingMethodTaxRateAction,
|
|
1157
|
+
) {
|
|
1158
|
+
if (resource.taxMode !== "External") {
|
|
1159
|
+
throw new CommercetoolsError<InvalidOperationError>({
|
|
1160
|
+
code: "InvalidOperation",
|
|
1161
|
+
message:
|
|
1162
|
+
"A shipping method tax rate can only be set for a Cart with External tax mode.",
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
if (!resource.shippingInfo) {
|
|
1166
|
+
throw new CommercetoolsError<InvalidOperationError>({
|
|
1167
|
+
code: "InvalidOperation",
|
|
1168
|
+
message: "Cart has no shipping method.",
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const shippingInfo = resource.shippingInfo as Writable<
|
|
1173
|
+
typeof resource.shippingInfo
|
|
1174
|
+
>;
|
|
1175
|
+
const taxRate: TaxRate | undefined = externalTaxRate
|
|
1176
|
+
? taxRateFromExternalDraft(externalTaxRate)
|
|
1177
|
+
: undefined;
|
|
1178
|
+
shippingInfo.taxRate = taxRate;
|
|
1179
|
+
shippingInfo.taxedPrice = taxRate
|
|
1180
|
+
? calculateTaxedPriceFromRate(
|
|
1181
|
+
shippingInfo.price.centAmount,
|
|
1182
|
+
shippingInfo.price.currencyCode,
|
|
1183
|
+
taxRate,
|
|
1184
|
+
resource.taxRoundingMode,
|
|
1185
|
+
)
|
|
1186
|
+
: undefined;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
setLineItemTaxAmount(
|
|
1190
|
+
_context: RepositoryContext,
|
|
1191
|
+
resource: Writable<Cart>,
|
|
1192
|
+
{
|
|
1193
|
+
lineItemId,
|
|
1194
|
+
lineItemKey,
|
|
1195
|
+
externalTaxAmount,
|
|
1196
|
+
}: CartSetLineItemTaxAmountAction,
|
|
1197
|
+
) {
|
|
1198
|
+
if (resource.taxMode !== "ExternalAmount") {
|
|
1199
|
+
throw new CommercetoolsError<InvalidOperationError>({
|
|
1200
|
+
code: "InvalidOperation",
|
|
1201
|
+
message:
|
|
1202
|
+
"A line item tax amount can only be set for a Cart with ExternalAmount tax mode.",
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const lineItem = resource.lineItems.find(
|
|
1207
|
+
(x) =>
|
|
1208
|
+
(lineItemId && x.id === lineItemId) ||
|
|
1209
|
+
(lineItemKey && x.key === lineItemKey),
|
|
1210
|
+
);
|
|
1211
|
+
if (!lineItem) {
|
|
1212
|
+
throw new CommercetoolsError<GeneralError>({
|
|
1213
|
+
code: "General",
|
|
1214
|
+
message: lineItemKey
|
|
1215
|
+
? `A line item with key '${lineItemKey}' not found.`
|
|
1216
|
+
: `A line item with ID '${lineItemId}' not found.`,
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const writable = lineItem as Writable<LineItem>;
|
|
1221
|
+
if (externalTaxAmount) {
|
|
1222
|
+
writable.taxRate = taxRateFromExternalDraft(externalTaxAmount.taxRate);
|
|
1223
|
+
writable.taxedPrice = buildTaxedPriceFromExternalAmount(
|
|
1224
|
+
externalTaxAmount,
|
|
1225
|
+
resource.taxRoundingMode,
|
|
1226
|
+
);
|
|
1227
|
+
} else {
|
|
1228
|
+
writable.taxRate = undefined;
|
|
1229
|
+
writable.taxedPrice = undefined;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
setCustomLineItemTaxAmount(
|
|
1234
|
+
_context: RepositoryContext,
|
|
1235
|
+
resource: Writable<Cart>,
|
|
1236
|
+
{
|
|
1237
|
+
customLineItemId,
|
|
1238
|
+
customLineItemKey,
|
|
1239
|
+
externalTaxAmount,
|
|
1240
|
+
}: CartSetCustomLineItemTaxAmountAction,
|
|
1241
|
+
) {
|
|
1242
|
+
if (resource.taxMode !== "ExternalAmount") {
|
|
1243
|
+
throw new CommercetoolsError<InvalidOperationError>({
|
|
1244
|
+
code: "InvalidOperation",
|
|
1245
|
+
message:
|
|
1246
|
+
"A custom line item tax amount can only be set for a Cart with ExternalAmount tax mode.",
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
const customLineItem = resource.customLineItems.find(
|
|
1251
|
+
(x) =>
|
|
1252
|
+
(customLineItemId && x.id === customLineItemId) ||
|
|
1253
|
+
(customLineItemKey && x.key === customLineItemKey),
|
|
1254
|
+
);
|
|
1255
|
+
if (!customLineItem) {
|
|
1256
|
+
throw new CommercetoolsError<GeneralError>({
|
|
1257
|
+
code: "General",
|
|
1258
|
+
message: customLineItemKey
|
|
1259
|
+
? `A custom line item with key '${customLineItemKey}' not found.`
|
|
1260
|
+
: `A custom line item with ID '${customLineItemId}' not found.`,
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
const writable = customLineItem as Writable<CustomLineItem>;
|
|
1265
|
+
if (externalTaxAmount) {
|
|
1266
|
+
writable.taxRate = taxRateFromExternalDraft(externalTaxAmount.taxRate);
|
|
1267
|
+
writable.taxedPrice = buildTaxedPriceFromExternalAmount(
|
|
1268
|
+
externalTaxAmount,
|
|
1269
|
+
resource.taxRoundingMode,
|
|
1270
|
+
);
|
|
1271
|
+
} else {
|
|
1272
|
+
writable.taxRate = undefined;
|
|
1273
|
+
writable.taxedPrice = undefined;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
setShippingMethodTaxAmount(
|
|
1278
|
+
_context: RepositoryContext,
|
|
1279
|
+
resource: Writable<Cart>,
|
|
1280
|
+
{ externalTaxAmount }: CartSetShippingMethodTaxAmountAction,
|
|
1281
|
+
) {
|
|
1282
|
+
if (resource.taxMode !== "ExternalAmount") {
|
|
1283
|
+
throw new CommercetoolsError<InvalidOperationError>({
|
|
1284
|
+
code: "InvalidOperation",
|
|
1285
|
+
message:
|
|
1286
|
+
"A shipping method tax amount can only be set for a Cart with ExternalAmount tax mode.",
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
if (!resource.shippingInfo) {
|
|
1290
|
+
throw new CommercetoolsError<InvalidOperationError>({
|
|
1291
|
+
code: "InvalidOperation",
|
|
1292
|
+
message: "Cart has no shipping method.",
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
const shippingInfo = resource.shippingInfo as Writable<
|
|
1297
|
+
typeof resource.shippingInfo
|
|
1298
|
+
>;
|
|
1299
|
+
if (externalTaxAmount) {
|
|
1300
|
+
shippingInfo.taxRate = taxRateFromExternalDraft(
|
|
1301
|
+
externalTaxAmount.taxRate,
|
|
1302
|
+
);
|
|
1303
|
+
shippingInfo.taxedPrice = buildTaxedPriceFromExternalAmount(
|
|
1304
|
+
externalTaxAmount,
|
|
1305
|
+
resource.taxRoundingMode,
|
|
1306
|
+
);
|
|
1307
|
+
} else {
|
|
1308
|
+
shippingInfo.taxRate = undefined;
|
|
1309
|
+
shippingInfo.taxedPrice = undefined;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
setCartTotalTax(
|
|
1314
|
+
_context: RepositoryContext,
|
|
1315
|
+
resource: Writable<Cart>,
|
|
1316
|
+
{ externalTotalGross, externalTaxPortions }: CartSetCartTotalTaxAction,
|
|
1317
|
+
) {
|
|
1318
|
+
if (resource.taxMode !== "ExternalAmount") {
|
|
1319
|
+
throw new CommercetoolsError<InvalidOperationError>({
|
|
1320
|
+
code: "InvalidOperation",
|
|
1321
|
+
message:
|
|
1322
|
+
"A cart total tax can only be set for a Cart with ExternalAmount tax mode.",
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
const totalGross = createCentPrecisionMoney(externalTotalGross);
|
|
1327
|
+
const currencyCode = totalGross.currencyCode;
|
|
1328
|
+
const portions =
|
|
1329
|
+
externalTaxPortions?.map((portion) => ({
|
|
1330
|
+
name: portion.name,
|
|
1331
|
+
rate: portion.rate,
|
|
1332
|
+
amount: createCentPrecisionMoney(portion.amount),
|
|
1333
|
+
})) ?? [];
|
|
1334
|
+
const totalTaxCentAmount = portions.reduce(
|
|
1335
|
+
(acc, portion) => acc + portion.amount.centAmount,
|
|
1336
|
+
0,
|
|
1337
|
+
);
|
|
1338
|
+
const totalNet = createCentPrecisionMoney({
|
|
1339
|
+
currencyCode,
|
|
1340
|
+
centAmount: totalGross.centAmount - totalTaxCentAmount,
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
resource.taxedPrice = {
|
|
1344
|
+
totalNet,
|
|
1345
|
+
totalGross,
|
|
1346
|
+
totalTax:
|
|
1347
|
+
totalTaxCentAmount > 0
|
|
1348
|
+
? createCentPrecisionMoney({
|
|
1349
|
+
currencyCode,
|
|
1350
|
+
centAmount: totalTaxCentAmount,
|
|
1351
|
+
})
|
|
1352
|
+
: undefined,
|
|
1353
|
+
taxPortions: portions,
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
|
|
968
1357
|
setShippingAddressCustomField(
|
|
969
1358
|
context: RepositoryContext,
|
|
970
1359
|
resource: Writable<Cart>,
|
|
@@ -6,12 +6,19 @@ import type {
|
|
|
6
6
|
DiscountCodeNonApplicableError,
|
|
7
7
|
LineItem,
|
|
8
8
|
Price,
|
|
9
|
+
RoundingMode,
|
|
9
10
|
TaxCategory,
|
|
10
11
|
TaxCategoryReference,
|
|
12
|
+
TaxedItemPrice,
|
|
13
|
+
TaxRate,
|
|
11
14
|
} from "@commercetools/platform-sdk";
|
|
12
15
|
import { v4 as uuidv4 } from "uuid";
|
|
13
16
|
import { CommercetoolsError } from "#src/exceptions.ts";
|
|
14
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
calculateTaxedPrice,
|
|
19
|
+
calculateTaxedPriceFromRate,
|
|
20
|
+
taxRateFromExternalDraft,
|
|
21
|
+
} from "#src/lib/tax.ts";
|
|
15
22
|
import type { AbstractStorage } from "#src/storage/abstract.ts";
|
|
16
23
|
import {
|
|
17
24
|
calculateMoneyTotalCentAmount,
|
|
@@ -52,6 +59,19 @@ export const calculateLineItemTotalPrice = (lineItem: LineItem): number => {
|
|
|
52
59
|
return calculateMoneyTotalCentAmount(lineItem.price.value, lineItem.quantity);
|
|
53
60
|
};
|
|
54
61
|
|
|
62
|
+
export const computeItemTaxedPrice = (
|
|
63
|
+
item: Pick<LineItem | CustomLineItem, "taxRate" | "totalPrice">,
|
|
64
|
+
roundingMode: RoundingMode = "HalfEven",
|
|
65
|
+
): TaxedItemPrice | undefined => {
|
|
66
|
+
if (!item.taxRate) return undefined;
|
|
67
|
+
return calculateTaxedPriceFromRate(
|
|
68
|
+
item.totalPrice.centAmount,
|
|
69
|
+
item.totalPrice.currencyCode,
|
|
70
|
+
item.taxRate,
|
|
71
|
+
roundingMode,
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
55
75
|
export const calculateCartTotalPrice = (cart: Cart): number => {
|
|
56
76
|
const lineItemsTotal = cart.lineItems.reduce(
|
|
57
77
|
(cur, item) => cur + item.totalPrice.centAmount,
|
|
@@ -69,16 +89,21 @@ export const createCustomLineItemFromDraft = async (
|
|
|
69
89
|
draft: CustomLineItemDraft,
|
|
70
90
|
storage: AbstractStorage,
|
|
71
91
|
country?: string,
|
|
92
|
+
taxMode: Cart["taxMode"] = "Platform",
|
|
93
|
+
roundingMode: RoundingMode = "HalfEven",
|
|
72
94
|
): Promise<CustomLineItem> => {
|
|
73
95
|
const quantity = draft.quantity ?? 1;
|
|
74
96
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
97
|
+
const isTaxExternal = taxMode === "External";
|
|
98
|
+
|
|
99
|
+
const taxCategoryRef =
|
|
100
|
+
!isTaxExternal && draft.taxCategory
|
|
101
|
+
? await getReferenceFromResourceIdentifier<TaxCategoryReference>(
|
|
102
|
+
draft.taxCategory,
|
|
103
|
+
projectKey,
|
|
104
|
+
storage,
|
|
105
|
+
)
|
|
106
|
+
: undefined;
|
|
82
107
|
|
|
83
108
|
let taxCategory: TaxCategory | undefined;
|
|
84
109
|
if (taxCategoryRef) {
|
|
@@ -100,20 +125,38 @@ export const createCustomLineItemFromDraft = async (
|
|
|
100
125
|
centAmount: calculateMoneyTotalCentAmount(draft.money, quantity),
|
|
101
126
|
});
|
|
102
127
|
|
|
103
|
-
const
|
|
104
|
-
|
|
128
|
+
const resolveTax = (): {
|
|
129
|
+
taxRate?: TaxRate;
|
|
130
|
+
taxedPrice?: TaxedItemPrice;
|
|
131
|
+
} => {
|
|
132
|
+
if (isTaxExternal) {
|
|
133
|
+
if (!draft.externalTaxRate) return {};
|
|
134
|
+
const taxRate = taxRateFromExternalDraft(draft.externalTaxRate);
|
|
135
|
+
return {
|
|
136
|
+
taxRate,
|
|
137
|
+
taxedPrice: calculateTaxedPriceFromRate(
|
|
138
|
+
totalPrice.centAmount,
|
|
139
|
+
totalPrice.currencyCode,
|
|
140
|
+
taxRate,
|
|
141
|
+
roundingMode,
|
|
142
|
+
),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (!taxCategory) return {};
|
|
146
|
+
return {
|
|
147
|
+
taxRate: taxCategory.rates.find(
|
|
148
|
+
(rate) => !rate.country || rate.country === country,
|
|
149
|
+
),
|
|
150
|
+
taxedPrice: calculateTaxedPrice(
|
|
105
151
|
totalPrice.centAmount,
|
|
106
152
|
taxCategory,
|
|
107
153
|
totalPrice.currencyCode,
|
|
108
154
|
country,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
(rate) => !rate.country || rate.country === country,
|
|
115
|
-
)
|
|
116
|
-
: undefined;
|
|
155
|
+
roundingMode,
|
|
156
|
+
),
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
const { taxRate, taxedPrice } = resolveTax();
|
|
117
160
|
|
|
118
161
|
return {
|
|
119
162
|
id: uuidv4(),
|