Skip to content

Commit ef7ef13

Browse files
feat: enable HighPrecisionMoney (#365)
Support HighPrecisionMoney next to the default CentPrecisionMoney
1 parent a3156cc commit ef7ef13

File tree

9 files changed

+435
-48
lines changed

9 files changed

+435
-48
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.test.ts

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1-
import type { BaseAddress } from "@commercetools/platform-sdk";
1+
import type {
2+
BaseAddress,
3+
HighPrecisionMoneyDraft,
4+
Money,
5+
} from "@commercetools/platform-sdk";
26
import { describe, expect, test } from "vitest";
37
import { InMemoryStorage } from "#src/storage/index.ts";
4-
import { createAddress } from "./helpers.ts";
8+
import {
9+
calculateCentAmountFromPreciseAmount,
10+
calculateMoneyTotalCentAmount,
11+
createAddress,
12+
createHighPrecisionMoney,
13+
} from "./helpers.ts";
514

615
describe("Helpers", () => {
716
const storage = new InMemoryStorage();
@@ -22,4 +31,106 @@ describe("Helpers", () => {
2231
expect(result?.id).toMatch(/^[a-z0-9]{8}$/);
2332
});
2433
});
34+
35+
describe("calculateCentAmountFromPreciseAmount", () => {
36+
test("rounds half even by default", () => {
37+
const centAmount = calculateCentAmountFromPreciseAmount(1005, 3, "EUR");
38+
expect(centAmount).toBe(100);
39+
});
40+
41+
test("supports HalfUp and HalfDown rounding modes", () => {
42+
const halfUp = calculateCentAmountFromPreciseAmount(
43+
1005,
44+
3,
45+
"EUR",
46+
"HalfUp",
47+
);
48+
const halfDown = calculateCentAmountFromPreciseAmount(
49+
1005,
50+
3,
51+
"EUR",
52+
"HalfDown",
53+
);
54+
expect(halfUp).toBe(101);
55+
expect(halfDown).toBe(100);
56+
});
57+
58+
test("uses currency fraction digits for conversion", () => {
59+
const centAmount = calculateCentAmountFromPreciseAmount(1234, 2, "JPY");
60+
expect(centAmount).toBe(12);
61+
});
62+
});
63+
64+
describe("createHighPrecisionMoney", () => {
65+
test("computes centAmount when missing", () => {
66+
const money = createHighPrecisionMoney({
67+
type: "highPrecision",
68+
currencyCode: "EUR",
69+
fractionDigits: 3,
70+
preciseAmount: 1015,
71+
});
72+
73+
expect(money.type).toBe("highPrecision");
74+
expect(money.centAmount).toBe(102);
75+
expect(money.preciseAmount).toBe(1015);
76+
expect(money.fractionDigits).toBe(3);
77+
});
78+
79+
test("throws when preciseAmount is missing", () => {
80+
expect(() =>
81+
createHighPrecisionMoney({
82+
type: "highPrecision",
83+
currencyCode: "EUR",
84+
fractionDigits: 3,
85+
} as HighPrecisionMoneyDraft),
86+
).toThrow("HighPrecisionMoney requires preciseAmount");
87+
});
88+
89+
test("throws when fractionDigits is missing", () => {
90+
expect(() =>
91+
createHighPrecisionMoney({
92+
type: "highPrecision",
93+
currencyCode: "EUR",
94+
preciseAmount: 1015,
95+
} as HighPrecisionMoneyDraft),
96+
).toThrow("HighPrecisionMoney requires fractionDigits");
97+
});
98+
});
99+
100+
describe("calculateMoneyTotalCentAmount", () => {
101+
test("uses preciseAmount when available", () => {
102+
const money: HighPrecisionMoneyDraft = {
103+
type: "highPrecision",
104+
currencyCode: "EUR",
105+
fractionDigits: 5,
106+
preciseAmount: 101499,
107+
centAmount: 101,
108+
};
109+
110+
const totalCentAmount = calculateMoneyTotalCentAmount(money, 2);
111+
expect(totalCentAmount).toBe(203);
112+
});
113+
114+
test("falls back to centAmount when preciseAmount is missing", () => {
115+
const money: Money = {
116+
currencyCode: "EUR",
117+
centAmount: 123,
118+
};
119+
120+
const totalCentAmount = calculateMoneyTotalCentAmount(money, 3);
121+
expect(totalCentAmount).toBe(369);
122+
});
123+
124+
test("supports rounding mode overrides", () => {
125+
const money: HighPrecisionMoneyDraft = {
126+
type: "highPrecision",
127+
currencyCode: "EUR",
128+
fractionDigits: 3,
129+
preciseAmount: 1005,
130+
};
131+
132+
const totalCentAmount = calculateMoneyTotalCentAmount(money, 1, "HalfUp");
133+
expect(totalCentAmount).toBe(101);
134+
});
135+
});
25136
});

0 commit comments

Comments
 (0)