From b3cb904e9bce3bfad649e500a9e1be0c9bff0e1a Mon Sep 17 00:00:00 2001 From: "Carlos R. L. Rodrigues" <37986729+carlos-r-l-rodrigues@users.noreply.github.com> Date: Fri, 12 Dec 2025 06:46:07 -0300 Subject: [PATCH] chore(utils): currency epsilon (#14225) * chore(utils): currency epsilon * pending diff * getEpsilonFromDecimalPrecision --- .changeset/busy-cars-bathe.md | 6 ++++++ .../utils/__tests__/aggregate-status.spec.ts | 7 +++++-- .../src/order/utils/aggregate-status.ts | 16 +++++++++++++--- packages/core/utils/src/totals/big-number.ts | 16 ++++++++++++++++ packages/core/utils/src/totals/cart/index.ts | 17 +++++++++++++++-- .../core/utils/src/totals/credit-lines/index.ts | 12 ++++++++++-- 6 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 .changeset/busy-cars-bathe.md diff --git a/.changeset/busy-cars-bathe.md b/.changeset/busy-cars-bathe.md new file mode 100644 index 0000000000..2b4634455c --- /dev/null +++ b/.changeset/busy-cars-bathe.md @@ -0,0 +1,6 @@ +--- +"@medusajs/core-flows": patch +"@medusajs/utils": patch +--- + +chore(utils): currency epsilon diff --git a/packages/core/core-flows/src/order/utils/__tests__/aggregate-status.spec.ts b/packages/core/core-flows/src/order/utils/__tests__/aggregate-status.spec.ts index b6b9d29e20..94e1d00efb 100644 --- a/packages/core/core-flows/src/order/utils/__tests__/aggregate-status.spec.ts +++ b/packages/core/core-flows/src/order/utils/__tests__/aggregate-status.spec.ts @@ -1,4 +1,7 @@ -import { MathBN, MEDUSA_EPSILON } from "@medusajs/framework/utils" +import { + MathBN, + MEDUSA_DEFAULT_CURRENCY_EPSILON, +} from "@medusajs/framework/utils" import { getLastFulfillmentStatus, getLastPaymentStatus, @@ -113,7 +116,7 @@ describe("Aggregate Order Status", () => { { status: "authorized", captured_amount: 10, - refunded_amount: MathBN.sub(10, MEDUSA_EPSILON), + refunded_amount: MathBN.sub(10, MEDUSA_DEFAULT_CURRENCY_EPSILON), amount: 10, }, { status: "canceled" }, diff --git a/packages/core/core-flows/src/order/utils/aggregate-status.ts b/packages/core/core-flows/src/order/utils/aggregate-status.ts index 4a60da5fbb..52c3edab1e 100644 --- a/packages/core/core-flows/src/order/utils/aggregate-status.ts +++ b/packages/core/core-flows/src/order/utils/aggregate-status.ts @@ -1,5 +1,10 @@ import type { OrderDetailDTO } from "@medusajs/framework/types" -import { isDefined, MathBN, MEDUSA_EPSILON } from "@medusajs/framework/utils" +import { + defaultCurrencies, + getEpsilonFromDecimalPrecision, + isDefined, + MathBN, +} from "@medusajs/framework/utils" export const getLastPaymentStatus = (order: OrderDetailDTO) => { const PaymentStatus = { @@ -15,6 +20,11 @@ export const getLastPaymentStatus = (order: OrderDetailDTO) => { PARTIALLY_AUTHORIZED: "partially_authorized", } + const upperCurCode = order.currency_code?.toUpperCase() as string + const currencyEpsilon = getEpsilonFromDecimalPrecision( + defaultCurrencies[upperCurCode]?.decimal_digits + ) + let paymentStatus = {} for (const status in PaymentStatus) { paymentStatus[PaymentStatus[status]] = 0 @@ -31,7 +41,7 @@ export const getLastPaymentStatus = (order: OrderDetailDTO) => { paymentCollection.amount, paymentCollection.captured_amount as number ), - MEDUSA_EPSILON + currencyEpsilon ) paymentStatus[PaymentStatus.CAPTURED] += isGte ? 1 : 0.5 } @@ -42,7 +52,7 @@ export const getLastPaymentStatus = (order: OrderDetailDTO) => { paymentCollection.amount, paymentCollection.refunded_amount as number ), - MEDUSA_EPSILON + currencyEpsilon ) paymentStatus[PaymentStatus.REFUNDED] += isGte ? 1 : 0.5 } diff --git a/packages/core/utils/src/totals/big-number.ts b/packages/core/utils/src/totals/big-number.ts index 4f4795bf2d..18eeb75dcb 100644 --- a/packages/core/utils/src/totals/big-number.ts +++ b/packages/core/utils/src/totals/big-number.ts @@ -1,6 +1,7 @@ import { BigNumberInput, BigNumberRawValue, IBigNumber } from "@medusajs/types" import { BigNumber as BigNumberConstructor } from "bignumber.js" import { isBigNumber } from "../common/is-big-number" +import { isDefined } from "../common/is-defined" import { isString } from "../common/is-string" type BigNumberJS = InstanceType @@ -150,3 +151,18 @@ export class BigNumber implements IBigNumber { export const MEDUSA_EPSILON = new BigNumber( process.env.MEDUSA_EPSILON || "0.0001" ) + +export const MEDUSA_DEFAULT_CURRENCY_EPSILON = new BigNumber( + process.env.MEDUSA_DEFAULT_CURRENCY_EPSILON || "0.01" +) + +export const getEpsilonFromDecimalPrecision = (decimalDigits?: number) => { + if (!isDefined(decimalDigits)) { + return MEDUSA_DEFAULT_CURRENCY_EPSILON + } + + const epsilon = new BigNumberJS(1).dividedBy( + new BigNumberJS(10).pow(decimalDigits) + ) + return new BigNumber(epsilon.toString()) +} diff --git a/packages/core/utils/src/totals/cart/index.ts b/packages/core/utils/src/totals/cart/index.ts index 16fae73892..ca5d3ecf6d 100644 --- a/packages/core/utils/src/totals/cart/index.ts +++ b/packages/core/utils/src/totals/cart/index.ts @@ -1,5 +1,6 @@ import { BigNumberInput, CartLikeWithTotals } from "@medusajs/types" -import { BigNumber } from "../big-number" +import { defaultCurrencies } from "../../defaults/currencies" +import { BigNumber, getEpsilonFromDecimalPrecision } from "../big-number" import { calculateCreditLinesTotal } from "../credit-lines" import { GetItemTotalInput, getLineItemsTotals } from "../line-item" import { MathBN } from "../math" @@ -14,6 +15,7 @@ interface TotalsConfig { } export interface DecorateCartLikeInputDTO { + currency_code?: string credit_lines?: { amount: BigNumberInput }[] @@ -198,6 +200,7 @@ export function decorateCartTotals( creditLines, includesTax: false, taxRate: creditLinesSumTaxRate, + currencyCode: cartLike.currency_code, }) const taxTotal = MathBN.add(itemsTaxTotal, shippingTaxTotal) @@ -276,6 +279,11 @@ export function decorateCartTotals( ...(cart.items?.map((item) => item.return_requested_total ?? 0) ?? [0]) ) + const upperCurCode = cart.currency_code?.toUpperCase() as string + const currencyEpsilon = getEpsilonFromDecimalPrecision( + defaultCurrencies[upperCurCode]?.decimal_digits + ) + const pendingDifference = new BigNumber( MathBN.sub( MathBN.sub(cart.total, pendingReturnTotal), @@ -283,7 +291,12 @@ export function decorateCartTotals( ) ) - cart.summary.pending_difference = pendingDifference + cart.summary.pending_difference = MathBN.lte( + MathBN.abs(pendingDifference), + currencyEpsilon + ) + ? MathBN.convert(0) + : pendingDifference } return cart diff --git a/packages/core/utils/src/totals/credit-lines/index.ts b/packages/core/utils/src/totals/credit-lines/index.ts index 0cfed6457c..90e5a43cb5 100644 --- a/packages/core/utils/src/totals/credit-lines/index.ts +++ b/packages/core/utils/src/totals/credit-lines/index.ts @@ -1,16 +1,19 @@ import { BigNumberInput } from "@medusajs/types" import { isDefined } from "../../common" -import { BigNumber, MEDUSA_EPSILON } from "../big-number" +import { defaultCurrencies } from "../../defaults/currencies" +import { BigNumber, getEpsilonFromDecimalPrecision } from "../big-number" import { MathBN } from "../math" export function calculateCreditLinesTotal({ creditLines, includesTax, taxRate, + currencyCode, }: { creditLines: { amount: BigNumberInput }[] includesTax?: boolean taxRate?: BigNumberInput + currencyCode?: string }) { // the sum of all creditLine amounts excluding tax let creditLinesSubtotal = MathBN.convert(0) @@ -46,7 +49,12 @@ export function calculateCreditLinesTotal({ } } - const isZero = MathBN.lte(creditLinesTotal, MEDUSA_EPSILON) + const upperCurCode = currencyCode?.toUpperCase() as string + const currencyEpsilon = getEpsilonFromDecimalPrecision( + defaultCurrencies[upperCurCode]?.decimal_digits + ) + + const isZero = MathBN.lte(creditLinesTotal, currencyEpsilon) return { creditLinesTotal: isZero ? MathBN.convert(0) : creditLinesTotal, creditLinesSubtotal: isZero ? MathBN.convert(0) : creditLinesSubtotal,