chore(utils): currency epsilon (#14225)

* chore(utils): currency epsilon

* pending diff

* getEpsilonFromDecimalPrecision
This commit is contained in:
Carlos R. L. Rodrigues
2025-12-12 10:46:07 +01:00
committed by GitHub
parent 8bb2ac654c
commit b3cb904e9b
6 changed files with 65 additions and 9 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"@medusajs/core-flows": patch
"@medusajs/utils": patch
---
chore(utils): currency epsilon
@@ -1,4 +1,7 @@
import { MathBN, MEDUSA_EPSILON } from "@medusajs/framework/utils" import {
MathBN,
MEDUSA_DEFAULT_CURRENCY_EPSILON,
} from "@medusajs/framework/utils"
import { import {
getLastFulfillmentStatus, getLastFulfillmentStatus,
getLastPaymentStatus, getLastPaymentStatus,
@@ -113,7 +116,7 @@ describe("Aggregate Order Status", () => {
{ {
status: "authorized", status: "authorized",
captured_amount: 10, captured_amount: 10,
refunded_amount: MathBN.sub(10, MEDUSA_EPSILON), refunded_amount: MathBN.sub(10, MEDUSA_DEFAULT_CURRENCY_EPSILON),
amount: 10, amount: 10,
}, },
{ status: "canceled" }, { status: "canceled" },
@@ -1,5 +1,10 @@
import type { OrderDetailDTO } from "@medusajs/framework/types" 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) => { export const getLastPaymentStatus = (order: OrderDetailDTO) => {
const PaymentStatus = { const PaymentStatus = {
@@ -15,6 +20,11 @@ export const getLastPaymentStatus = (order: OrderDetailDTO) => {
PARTIALLY_AUTHORIZED: "partially_authorized", PARTIALLY_AUTHORIZED: "partially_authorized",
} }
const upperCurCode = order.currency_code?.toUpperCase() as string
const currencyEpsilon = getEpsilonFromDecimalPrecision(
defaultCurrencies[upperCurCode]?.decimal_digits
)
let paymentStatus = {} let paymentStatus = {}
for (const status in PaymentStatus) { for (const status in PaymentStatus) {
paymentStatus[PaymentStatus[status]] = 0 paymentStatus[PaymentStatus[status]] = 0
@@ -31,7 +41,7 @@ export const getLastPaymentStatus = (order: OrderDetailDTO) => {
paymentCollection.amount, paymentCollection.amount,
paymentCollection.captured_amount as number paymentCollection.captured_amount as number
), ),
MEDUSA_EPSILON currencyEpsilon
) )
paymentStatus[PaymentStatus.CAPTURED] += isGte ? 1 : 0.5 paymentStatus[PaymentStatus.CAPTURED] += isGte ? 1 : 0.5
} }
@@ -42,7 +52,7 @@ export const getLastPaymentStatus = (order: OrderDetailDTO) => {
paymentCollection.amount, paymentCollection.amount,
paymentCollection.refunded_amount as number paymentCollection.refunded_amount as number
), ),
MEDUSA_EPSILON currencyEpsilon
) )
paymentStatus[PaymentStatus.REFUNDED] += isGte ? 1 : 0.5 paymentStatus[PaymentStatus.REFUNDED] += isGte ? 1 : 0.5
} }
@@ -1,6 +1,7 @@
import { BigNumberInput, BigNumberRawValue, IBigNumber } from "@medusajs/types" import { BigNumberInput, BigNumberRawValue, IBigNumber } from "@medusajs/types"
import { BigNumber as BigNumberConstructor } from "bignumber.js" import { BigNumber as BigNumberConstructor } from "bignumber.js"
import { isBigNumber } from "../common/is-big-number" import { isBigNumber } from "../common/is-big-number"
import { isDefined } from "../common/is-defined"
import { isString } from "../common/is-string" import { isString } from "../common/is-string"
type BigNumberJS = InstanceType<typeof BigNumberConstructor> type BigNumberJS = InstanceType<typeof BigNumberConstructor>
@@ -150,3 +151,18 @@ export class BigNumber implements IBigNumber {
export const MEDUSA_EPSILON = new BigNumber( export const MEDUSA_EPSILON = new BigNumber(
process.env.MEDUSA_EPSILON || "0.0001" 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())
}
+15 -2
View File
@@ -1,5 +1,6 @@
import { BigNumberInput, CartLikeWithTotals } from "@medusajs/types" 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 { calculateCreditLinesTotal } from "../credit-lines"
import { GetItemTotalInput, getLineItemsTotals } from "../line-item" import { GetItemTotalInput, getLineItemsTotals } from "../line-item"
import { MathBN } from "../math" import { MathBN } from "../math"
@@ -14,6 +15,7 @@ interface TotalsConfig {
} }
export interface DecorateCartLikeInputDTO { export interface DecorateCartLikeInputDTO {
currency_code?: string
credit_lines?: { credit_lines?: {
amount: BigNumberInput amount: BigNumberInput
}[] }[]
@@ -198,6 +200,7 @@ export function decorateCartTotals(
creditLines, creditLines,
includesTax: false, includesTax: false,
taxRate: creditLinesSumTaxRate, taxRate: creditLinesSumTaxRate,
currencyCode: cartLike.currency_code,
}) })
const taxTotal = MathBN.add(itemsTaxTotal, shippingTaxTotal) const taxTotal = MathBN.add(itemsTaxTotal, shippingTaxTotal)
@@ -276,6 +279,11 @@ export function decorateCartTotals(
...(cart.items?.map((item) => item.return_requested_total ?? 0) ?? [0]) ...(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( const pendingDifference = new BigNumber(
MathBN.sub( MathBN.sub(
MathBN.sub(cart.total, pendingReturnTotal), 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 return cart
@@ -1,16 +1,19 @@
import { BigNumberInput } from "@medusajs/types" import { BigNumberInput } from "@medusajs/types"
import { isDefined } from "../../common" 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" import { MathBN } from "../math"
export function calculateCreditLinesTotal({ export function calculateCreditLinesTotal({
creditLines, creditLines,
includesTax, includesTax,
taxRate, taxRate,
currencyCode,
}: { }: {
creditLines: { amount: BigNumberInput }[] creditLines: { amount: BigNumberInput }[]
includesTax?: boolean includesTax?: boolean
taxRate?: BigNumberInput taxRate?: BigNumberInput
currencyCode?: string
}) { }) {
// the sum of all creditLine amounts excluding tax // the sum of all creditLine amounts excluding tax
let creditLinesSubtotal = MathBN.convert(0) 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 { return {
creditLinesTotal: isZero ? MathBN.convert(0) : creditLinesTotal, creditLinesTotal: isZero ? MathBN.convert(0) : creditLinesTotal,
creditLinesSubtotal: isZero ? MathBN.convert(0) : creditLinesSubtotal, creditLinesSubtotal: isZero ? MathBN.convert(0) : creditLinesSubtotal,