Skip to content

Commit 1740505

Browse files
committed
feat: enable HighPrecisionMoney
chore: add changeset and refactor refactor: formatting refactor: formatting fix: clear the outdated preciseAmount on required CentPrecisionMoney types refactor: validate if calculcated amount matches the external price
1 parent a3156cc commit 1740505

File tree

8 files changed

+322
-46
lines changed

8 files changed

+322
-46
lines changed

.changeset/cold-phones-film.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@labdigital/commercetools-mock": minor
3+
---
4+
5+
Add HighPrecisionMoney support

src/repositories/cart/actions.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import type { Writable } from "#src/types.ts";
5252
import type { UpdateHandlerInterface } from "../abstract.ts";
5353
import { AbstractUpdateHandler, type RepositoryContext } from "../abstract.ts";
5454
import {
55+
calculateMoneyTotalCentAmount,
5556
createAddress,
5657
createCentPrecisionMoney,
5758
createCustomFields,
@@ -183,6 +184,10 @@ export class CartUpdateHandler
183184
`No valid price found for ${productId} for country ${resource.country} and currency ${currency}`,
184185
);
185186
}
187+
const totalPrice = createCentPrecisionMoney({
188+
currencyCode: price.value.currencyCode,
189+
centAmount: calculateMoneyTotalCentAmount(price.value, quantity),
190+
});
186191
resource.lineItems.push({
187192
id: uuidv4(),
188193
key,
@@ -196,11 +201,7 @@ export class CartUpdateHandler
196201
price: price,
197202
taxedPricePortions: [],
198203
perMethodTaxRate: [],
199-
totalPrice: {
200-
...price.value,
201-
type: "centPrecision",
202-
centAmount: price.value.centAmount * quantity,
203-
},
204+
totalPrice,
204205
quantity,
205206
discountedPricePerQuantity: [],
206207
lineItemMode: "Standard",
@@ -427,8 +428,11 @@ export class CartUpdateHandler
427428
}
428429
customLineItem.quantity = quantity;
429430
customLineItem.totalPrice = createCentPrecisionMoney({
430-
...customLineItem.money,
431-
centAmount: (customLineItem.money.centAmount ?? 0) * quantity,
431+
currencyCode: customLineItem.money.currencyCode,
432+
centAmount: calculateMoneyTotalCentAmount(
433+
customLineItem.money,
434+
quantity,
435+
),
432436
});
433437
};
434438

@@ -470,8 +474,11 @@ export class CartUpdateHandler
470474
}
471475
customLineItem.money = createTypedMoney(money);
472476
customLineItem.totalPrice = createCentPrecisionMoney({
473-
...money,
474-
centAmount: (money.centAmount ?? 0) * customLineItem.quantity,
477+
currencyCode: money.currencyCode,
478+
centAmount: calculateMoneyTotalCentAmount(
479+
money,
480+
customLineItem.quantity,
481+
),
475482
});
476483
};
477484

@@ -608,7 +615,7 @@ export class CartUpdateHandler
608615
shippingMethodName,
609616
price: createCentPrecisionMoney(shippingRate.price),
610617
shippingRate: {
611-
price: createTypedMoney(shippingRate.price),
618+
price: createCentPrecisionMoney(shippingRate.price),
612619
tiers: [],
613620
},
614621
taxCategory: tax
@@ -798,7 +805,7 @@ export class CartUpdateHandler
798805

799806
const lineItemTotal = calculateLineItemTotalPrice(lineItem);
800807
lineItem.totalPrice = createCentPrecisionMoney({
801-
...lineItem.price!.value,
808+
currencyCode: lineItem.price!.value.currencyCode,
802809
centAmount: lineItemTotal,
803810
});
804811
resource.totalPrice.centAmount = calculateCartTotalPrice(resource);

src/repositories/cart/helpers.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { v4 as uuidv4 } from "uuid";
1111
import { calculateTaxedPrice } from "#src/lib/tax.ts";
1212
import type { AbstractStorage } from "#src/storage/abstract.ts";
1313
import {
14+
calculateMoneyTotalCentAmount,
1415
createCentPrecisionMoney,
1516
createCustomFields,
1617
createTypedMoney,
@@ -40,8 +41,13 @@ export const selectPrice = ({
4041
});
4142
};
4243

43-
export const calculateLineItemTotalPrice = (lineItem: LineItem): number =>
44-
lineItem.price?.value.centAmount * lineItem.quantity;
44+
export const calculateLineItemTotalPrice = (lineItem: LineItem): number => {
45+
if (!lineItem.price?.value) {
46+
return 0;
47+
}
48+
49+
return calculateMoneyTotalCentAmount(lineItem.price.value, lineItem.quantity);
50+
};
4551

4652
export const calculateCartTotalPrice = (cart: Cart): number => {
4753
const lineItemsTotal = cart.lineItems.reduce(
@@ -83,8 +89,8 @@ export const createCustomLineItemFromDraft = (
8389
}
8490

8591
const totalPrice = createCentPrecisionMoney({
86-
...draft.money,
87-
centAmount: (draft.money.centAmount ?? 0) * quantity,
92+
currencyCode: draft.money.currencyCode,
93+
centAmount: calculateMoneyTotalCentAmount(draft.money, quantity),
8894
});
8995

9096
const taxedPrice = taxCategory

src/repositories/cart/index.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ import {
2424
AbstractResourceRepository,
2525
type RepositoryContext,
2626
} from "../abstract.ts";
27-
import { createAddress, createCustomFields } from "../helpers.ts";
27+
import {
28+
calculateMoneyTotalCentAmount,
29+
createAddress,
30+
createCentPrecisionMoney,
31+
createCustomFields,
32+
} from "../helpers.ts";
2833
import { CartUpdateHandler } from "./actions.ts";
2934
import {
3035
calculateCartTotalPrice,
@@ -238,6 +243,11 @@ export class CartRepository extends AbstractResourceRepository<"cart"> {
238243
);
239244
}
240245

246+
const totalPrice = createCentPrecisionMoney({
247+
currencyCode: price.value.currencyCode,
248+
centAmount: calculateMoneyTotalCentAmount(price.value, quant),
249+
});
250+
241251
return {
242252
id: uuidv4(),
243253
productId: product.id,
@@ -247,12 +257,7 @@ export class CartRepository extends AbstractResourceRepository<"cart"> {
247257
name: product.masterData.current.name,
248258
variant,
249259
price: price,
250-
totalPrice: {
251-
type: "centPrecision",
252-
currencyCode: price.value.currencyCode,
253-
fractionDigits: price.value.fractionDigits,
254-
centAmount: price.value.centAmount * quant,
255-
},
260+
totalPrice,
256261
taxedPricePortions: [],
257262
perMethodTaxRate: [],
258263
quantity: quant,

src/repositories/helpers.ts

Lines changed: 107 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type {
2828
StoreReference,
2929
StoreResourceIdentifier,
3030
Type,
31+
TypedMoney,
3132
} from "@commercetools/platform-sdk";
3233
import { Decimal } from "decimal.js/decimal";
3334
import type { Request } from "express";
@@ -109,19 +110,17 @@ export const roundDecimal = (decimal: Decimal, roundingMode: RoundingMode) => {
109110
}
110111
};
111112

112-
export const createCentPrecisionMoney = (value: _Money): CentPrecisionMoney => {
113+
export const getCurrencyFractionDigits = (currencyCode: string): number => {
113114
// Taken from https://docs.adyen.com/development-resources/currency-codes
114-
let fractionDigits = 2;
115-
switch (value.currencyCode.toUpperCase()) {
115+
switch (currencyCode.toUpperCase()) {
116116
case "BHD":
117117
case "IQD":
118118
case "JOD":
119119
case "KWD":
120120
case "LYD":
121121
case "OMR":
122122
case "TND":
123-
fractionDigits = 3;
124-
break;
123+
return 3;
125124
case "CVE":
126125
case "DJF":
127126
case "GNF":
@@ -137,29 +136,120 @@ export const createCentPrecisionMoney = (value: _Money): CentPrecisionMoney => {
137136
case "XAF":
138137
case "XOF":
139138
case "XPF":
140-
fractionDigits = 0;
141-
break;
139+
return 0;
142140
default:
143-
fractionDigits = 2;
141+
return 2;
144142
}
143+
};
144+
145+
export const calculateCentAmountFromPreciseAmount = (
146+
preciseAmount: number,
147+
fractionDigits: number,
148+
currencyCode: string,
149+
roundingMode: RoundingMode = "HalfEven",
150+
): number => {
151+
const centFractionDigits = getCurrencyFractionDigits(currencyCode);
152+
const diff = fractionDigits - centFractionDigits;
153+
const scale = new Decimal(10).pow(Math.abs(diff));
154+
const decimal =
155+
diff >= 0
156+
? new Decimal(preciseAmount).div(scale)
157+
: new Decimal(preciseAmount).mul(scale);
158+
159+
return roundDecimal(decimal, roundingMode).toNumber();
160+
};
145161

146-
if ((value as HighPrecisionMoney & HighPrecisionMoneyDraft).preciseAmount) {
147-
throw new Error("HighPrecisionMoney not supported");
162+
export const createCentPrecisionMoney = (value: _Money): CentPrecisionMoney => {
163+
const fractionDigits = getCurrencyFractionDigits(value.currencyCode);
164+
const preciseValue = value as HighPrecisionMoney & HighPrecisionMoneyDraft;
165+
let centAmount: number;
166+
167+
centAmount = value.centAmount ?? 0;
168+
169+
if (
170+
preciseValue.preciseAmount !== undefined &&
171+
preciseValue.fractionDigits !== undefined
172+
) {
173+
centAmount = calculateCentAmountFromPreciseAmount(
174+
preciseValue.preciseAmount,
175+
preciseValue.fractionDigits,
176+
value.currencyCode,
177+
"HalfEven",
178+
);
148179
}
149180

150181
return {
151182
type: "centPrecision",
152-
// centAmont is only optional on HighPrecisionMoney, so this should never
153-
// fallback to 0
154-
centAmount: value.centAmount ?? 0,
183+
centAmount,
155184
currencyCode: value.currencyCode,
156-
fractionDigits: fractionDigits,
185+
fractionDigits,
157186
};
158187
};
159188

160-
export const createTypedMoney = (value: _Money): CentPrecisionMoney => {
161-
const result = createCentPrecisionMoney(value);
162-
return result;
189+
export const createHighPrecisionMoney = (
190+
value: HighPrecisionMoney | HighPrecisionMoneyDraft,
191+
): HighPrecisionMoney => {
192+
if (value.preciseAmount === undefined) {
193+
throw new Error("HighPrecisionMoney requires preciseAmount");
194+
}
195+
196+
if (value.fractionDigits === undefined) {
197+
throw new Error("HighPrecisionMoney requires fractionDigits");
198+
}
199+
200+
const centAmount =
201+
value.centAmount ??
202+
calculateCentAmountFromPreciseAmount(
203+
value.preciseAmount,
204+
value.fractionDigits,
205+
value.currencyCode,
206+
"HalfEven",
207+
);
208+
209+
return {
210+
type: "highPrecision",
211+
centAmount,
212+
currencyCode: value.currencyCode,
213+
fractionDigits: value.fractionDigits,
214+
preciseAmount: value.preciseAmount,
215+
};
216+
};
217+
218+
export const createTypedMoney = (value: _Money): TypedMoney => {
219+
const preciseValue = value as HighPrecisionMoney & HighPrecisionMoneyDraft;
220+
if (
221+
("type" in value && value.type === "highPrecision") ||
222+
preciseValue.preciseAmount !== undefined
223+
) {
224+
return createHighPrecisionMoney(
225+
value as HighPrecisionMoney | HighPrecisionMoneyDraft,
226+
);
227+
}
228+
229+
return createCentPrecisionMoney(value);
230+
};
231+
232+
export const calculateMoneyTotalCentAmount = (
233+
money: _Money,
234+
quantity: number,
235+
roundingMode: RoundingMode = "HalfEven",
236+
): number => {
237+
const preciseValue = money as HighPrecisionMoney & HighPrecisionMoneyDraft;
238+
239+
if (
240+
preciseValue.preciseAmount === undefined ||
241+
preciseValue.fractionDigits === undefined
242+
) {
243+
return (money.centAmount ?? 0) * quantity;
244+
}
245+
246+
const totalPrecise = new Decimal(preciseValue.preciseAmount).mul(quantity);
247+
return calculateCentAmountFromPreciseAmount(
248+
totalPrecise.toNumber(),
249+
preciseValue.fractionDigits,
250+
money.currencyCode,
251+
roundingMode,
252+
);
163253
};
164254

165255
export const resolveStoreReference = (

src/repositories/order/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import type { Writable } from "#src/types.ts";
3535
import type { RepositoryContext } from "../abstract.ts";
3636
import { AbstractResourceRepository, type QueryParams } from "../abstract.ts";
3737
import {
38+
calculateMoneyTotalCentAmount,
3839
createAddress,
3940
createCentPrecisionMoney,
4041
createCustomFields,
@@ -260,8 +261,8 @@ export class OrderRepository extends AbstractResourceRepository<"order"> {
260261

261262
const quantity = draft.quantity ?? 1;
262263
const totalPrice = createCentPrecisionMoney({
263-
...draft.price.value,
264-
centAmount: (draft.price.value.centAmount ?? 0) * quantity,
264+
currencyCode: draft.price.value.currencyCode,
265+
centAmount: calculateMoneyTotalCentAmount(draft.price.value, quantity),
265266
});
266267

267268
const lineItem: LineItem = {
@@ -306,8 +307,8 @@ export class OrderRepository extends AbstractResourceRepository<"order"> {
306307
): CustomLineItem {
307308
const quantity = draft.quantity ?? 1;
308309
const totalPrice = createCentPrecisionMoney({
309-
...draft.money,
310-
centAmount: (draft.money.centAmount ?? 0) * quantity,
310+
currencyCode: draft.money.currencyCode,
311+
centAmount: calculateMoneyTotalCentAmount(draft.money, quantity),
311312
});
312313

313314
const lineItem: CustomLineItem = {

src/repositories/shipping-method/helpers.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import type {
22
ShippingRate,
33
ShippingRateDraft,
44
} from "@commercetools/platform-sdk";
5-
import { createTypedMoney } from "../helpers.ts";
5+
import { createCentPrecisionMoney } from "../helpers.ts";
66

77
export const transformShippingRate = (
88
rate: ShippingRateDraft,
99
): ShippingRate => ({
10-
price: createTypedMoney(rate.price),
11-
freeAbove: rate.freeAbove && createTypedMoney(rate.freeAbove),
10+
price: createCentPrecisionMoney(rate.price),
11+
freeAbove: rate.freeAbove && createCentPrecisionMoney(rate.freeAbove),
1212
tiers: rate.tiers || [],
1313
});

0 commit comments

Comments
 (0)