chore(utils): currency epsilon (#14225)

* chore(utils): currency epsilon

* pending diff

* getEpsilonFromDecimalPrecision
This commit is contained in:
Carlos R. L. Rodrigues
2025-12-12 06:46:07 -03:00
committed by GitHub
parent 8bb2ac654c
commit b3cb904e9b
6 changed files with 65 additions and 9 deletions

View File

@@ -0,0 +1,6 @@
---
"@medusajs/core-flows": patch
"@medusajs/utils": patch
---
chore(utils): currency epsilon

View File

@@ -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" },

View File

@@ -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
}

View File

@@ -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<typeof BigNumberConstructor>
@@ -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())
}

View File

@@ -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

View File

@@ -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,