@labdigital/commercetools-mock 3.0.0-beta.3 → 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.
@@ -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
- { money, name, slug, quantity, taxCategory, custom, priceMode, key },
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
- if (externalTaxRate) {
721
+ const isTaxExternal = resource.taxMode === "External";
722
+
723
+ if (externalTaxRate && !isTaxExternal) {
641
724
  throw new CommercetoolsError<InvalidOperationError>({
642
725
  code: "InvalidOperation",
643
- message: "External tax rate is not supported",
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 = taxCategory
648
- ? await this._storage.getByResourceIdentifier<"tax-category">(
649
- context.projectKey,
650
- taxCategory,
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: createCentPrecisionMoney(shippingRate.price),
756
+ price,
657
757
  shippingRate: {
658
- price: createCentPrecisionMoney(shippingRate.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 { calculateTaxedPrice } from "#src/lib/tax.ts";
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 taxCategoryRef = draft.taxCategory
76
- ? await getReferenceFromResourceIdentifier<TaxCategoryReference>(
77
- draft.taxCategory,
78
- projectKey,
79
- storage,
80
- )
81
- : undefined;
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 taxedPrice = taxCategory
104
- ? calculateTaxedPrice(
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
- : undefined;
111
-
112
- const taxRate = taxCategory
113
- ? taxCategory.rates.find(
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(),