feat: Totals calculation (#6980)

This commit is contained in:
Carlos R. L. Rodrigues authored and GitHub committed 2024-04-07 16:50:40 +02:00
1 parent 7302d078a9
commit b6879f017a
48 files changed
+1674 -571

No files matched your search

@@ -1,6 +1,8 @@
import { BigNumberInput } from "@medusajs/types"
export interface CreateLineItemAdjustmentDTO { export interface CreateLineItemAdjustmentDTO {
item_id: string item_id: string
amount: number amount: BigNumberInput
code?: string code?: string
description?: string description?: string
promotion_id?: string promotion_id?: string
+5 -3
View File
@@ -1,3 +1,5 @@
import { BigNumberInput } from "@medusajs/types"
interface PartialUpsertLineItemDTO { interface PartialUpsertLineItemDTO {
subtitle?: string subtitle?: string
thumbnail?: string thumbnail?: string
@@ -20,13 +22,13 @@ interface PartialUpsertLineItemDTO {
is_discountable?: boolean is_discountable?: boolean
is_tax_inclusive?: boolean is_tax_inclusive?: boolean
compare_at_unit_price?: number compare_at_unit_price?: BigNumberInput
} }
export interface CreateLineItemDTO extends PartialUpsertLineItemDTO { export interface CreateLineItemDTO extends PartialUpsertLineItemDTO {
title: string title: string
quantity: number quantity: BigNumberInput
unit_price: number | string unit_price: BigNumberInput
cart_id: string cart_id: string
} }
@@ -1,7 +1,9 @@
import { BigNumberInput } from "@medusajs/types"
export interface CreateShippingMethodAdjustmentDTO { export interface CreateShippingMethodAdjustmentDTO {
shipping_method_id: string shipping_method_id: string
code: string code: string
amount: number amount: BigNumberInput
description?: string description?: string
promotion_id?: string promotion_id?: string
provider_id?: string provider_id?: string
@@ -10,7 +12,7 @@ export interface CreateShippingMethodAdjustmentDTO {
export interface UpdateShippingMethodAdjustmentDTO { export interface UpdateShippingMethodAdjustmentDTO {
id: string id: string
code?: string code?: string
amount?: number amount?: BigNumberInput
description?: string description?: string
promotion_id?: string promotion_id?: string
provider_id?: string provider_id?: string
+4 -2
View File
@@ -1,13 +1,15 @@
import { BigNumberInput } from "@medusajs/types"
export interface CreateShippingMethodDTO { export interface CreateShippingMethodDTO {
name: string name: string
shippingMethod_id: string shippingMethod_id: string
amount: number amount: BigNumberInput
data?: Record<string, unknown> data?: Record<string, unknown>
} }
export interface UpdateShippingMethodDTO { export interface UpdateShippingMethodDTO {
id: string id: string
name?: string name?: string
amount?: number amount?: BigNumberInput
data?: Record<string, unknown> data?: Record<string, unknown>
} }
@@ -1,11 +1,11 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPaymentModuleService } from "@medusajs/types" import { BigNumberInput, IPaymentModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk" import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type StepInput = { type StepInput = {
region_id: string region_id: string
currency_code: string currency_code: string
amount: number amount: BigNumberInput
metadata?: Record<string, unknown> metadata?: Record<string, unknown>
} }
@@ -1,11 +1,15 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPaymentModuleService, PaymentProviderContext } from "@medusajs/types" import {
BigNumberInput,
IPaymentModuleService,
PaymentProviderContext,
} from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk" import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput { interface StepInput {
payment_collection_id: string payment_collection_id: string
provider_id: string provider_id: string
amount: number amount: BigNumberInput
currency_code: string currency_code: string
context?: PaymentProviderContext context?: PaymentProviderContext
data?: Record<string, unknown> data?: Record<string, unknown>
@@ -1,13 +1,12 @@
import { ProductWorkflow, WorkflowTypes } from "@medusajs/types" import { BigNumberInput, ProductWorkflow, WorkflowTypes } from "@medusajs/types"
import { WorkflowArguments } from "@medusajs/workflows-sdk" import { WorkflowArguments } from "@medusajs/workflows-sdk"
type VariantPrice = { type VariantPrice = {
region_id?: string region_id?: string
currency_code?: string currency_code?: string
amount: number amount: BigNumberInput
min_quantity?: number min_quantity?: BigNumberInput
max_quantity?: number max_quantity?: BigNumberInput
} }
export type CreateProductVariantsPreparedData = { export type CreateProductVariantsPreparedData = {
@@ -22,7 +21,8 @@ export type CreateProductVariantsPreparedData = {
export async function createProductVariantsPrepareData({ export async function createProductVariantsPrepareData({
container, container,
data, data,
}: WorkflowArguments<WorkflowTypes.ProductWorkflow.CreateProductVariantsWorkflowInputDTO>): Promise<CreateProductVariantsPreparedData> { }: // eslint-disable-next-line max-len
WorkflowArguments<WorkflowTypes.ProductWorkflow.CreateProductVariantsWorkflowInputDTO>): Promise<CreateProductVariantsPreparedData> {
const productVariants: ProductWorkflow.CreateProductVariantsInputDTO[] = const productVariants: ProductWorkflow.CreateProductVariantsInputDTO[] =
data.productVariants || [] data.productVariants || []
@@ -1,13 +1,13 @@
import { Modules, ModulesDefinition } from "@medusajs/modules-sdk" import { Modules, ModulesDefinition } from "@medusajs/modules-sdk"
import { ProductTypes } from "@medusajs/types" import { BigNumberInput, ProductTypes } from "@medusajs/types"
import { WorkflowArguments } from "@medusajs/workflows-sdk" import { WorkflowArguments } from "@medusajs/workflows-sdk"
type VariantPrice = { type VariantPrice = {
region_id?: string region_id?: string
currency_code?: string currency_code?: string
amount: number amount: BigNumberInput
min_quantity?: number min_quantity?: BigNumberInput
max_quantity?: number max_quantity?: BigNumberInput
} }
type HandlerInput = { type HandlerInput = {
@@ -1,8 +1,13 @@
import { ProductTypes, SalesChannelTypes, WorkflowTypes } from "@medusajs/types" import {
BigNumberInput,
ProductTypes,
SalesChannelTypes,
WorkflowTypes,
} from "@medusajs/types"
import { import {
FeatureFlagUtils, FeatureFlagUtils,
kebabCase,
ShippingProfileUtils, ShippingProfileUtils,
kebabCase,
} from "@medusajs/utils" } from "@medusajs/utils"
import { WorkflowArguments } from "@medusajs/workflows-sdk" import { WorkflowArguments } from "@medusajs/workflows-sdk"
@@ -14,9 +19,9 @@ type VariantIndexAndPrices = {
prices: { prices: {
region_id?: string region_id?: string
currency_code?: string currency_code?: string
amount: number amount: BigNumberInput
min_quantity?: number min_quantity?: BigNumberInput
max_quantity?: number max_quantity?: BigNumberInput
}[] }[]
} }
@@ -1,14 +1,19 @@
import { Modules, ModulesDefinition } from "@medusajs/modules-sdk" import { Modules, ModulesDefinition } from "@medusajs/modules-sdk"
import { ProductTypes, ProductWorkflow, WorkflowTypes } from "@medusajs/types" import {
BigNumberInput,
ProductTypes,
ProductWorkflow,
WorkflowTypes,
} from "@medusajs/types"
import { MedusaV2Flag } from "@medusajs/utils" import { MedusaV2Flag } from "@medusajs/utils"
import { WorkflowArguments } from "@medusajs/workflows-sdk" import { WorkflowArguments } from "@medusajs/workflows-sdk"
type VariantPrice = { type VariantPrice = {
region_id?: string region_id?: string
currency_code?: string currency_code?: string
amount: number amount: BigNumberInput
min_quantity?: number min_quantity?: BigNumberInput
max_quantity?: number max_quantity?: BigNumberInput
} }
export type UpdateProductVariantsPreparedData = { export type UpdateProductVariantsPreparedData = {
@@ -1,4 +1,9 @@
import { ProductDTO, SalesChannelDTO, WorkflowTypes } from "@medusajs/types" import {
BigNumberInput,
ProductDTO,
SalesChannelDTO,
WorkflowTypes,
} from "@medusajs/types"
import { MedusaV2Flag } from "@medusajs/utils" import { MedusaV2Flag } from "@medusajs/utils"
import { WorkflowArguments } from "@medusajs/workflows-sdk" import { WorkflowArguments } from "@medusajs/workflows-sdk"
@@ -9,9 +14,9 @@ type ProductWithSalesChannelsDTO = ProductDTO & {
type VariantPrice = { type VariantPrice = {
region_id?: string region_id?: string
currency_code?: string currency_code?: string
amount: number amount: BigNumberInput
min_quantity?: number min_quantity?: BigNumberInput
max_quantity?: number max_quantity?: BigNumberInput
} }
export type UpdateProductsPreparedData = { export type UpdateProductsPreparedData = {
@@ -1,4 +1,4 @@
import { PricingTypes } from "@medusajs/types" import { BigNumberInput, PricingTypes } from "@medusajs/types"
import { MedusaV2Flag } from "@medusajs/utils" import { MedusaV2Flag } from "@medusajs/utils"
import { WorkflowArguments } from "@medusajs/workflows-sdk" import { WorkflowArguments } from "@medusajs/workflows-sdk"
@@ -6,9 +6,9 @@ type VariantPrice = {
id?: string id?: string
region_id?: string region_id?: string
currency_code: string currency_code: string
amount: number amount: BigNumberInput
min_quantity?: number min_quantity?: BigNumberInput
max_quantity?: number max_quantity?: BigNumberInput
rules: Record<string, string> rules: Record<string, string>
} }
@@ -1,11 +1,11 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPaymentModuleService } from "@medusajs/types" import { BigNumberInput, IPaymentModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk" import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type StepInput = { type StepInput = {
payment_id: string payment_id: string
captured_by?: string captured_by?: string
amount?: number amount?: BigNumberInput
} }
export const capturePaymentStepId = "capture-payment-step" export const capturePaymentStepId = "capture-payment-step"
@@ -1,11 +1,11 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPaymentModuleService } from "@medusajs/types" import { BigNumberInput, IPaymentModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk" import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type StepInput = { type StepInput = {
payment_id: string payment_id: string
created_by?: string created_by?: string
amount?: number amount?: BigNumberInput
} }
export const refundPaymentStepId = "refund-payment-step" export const refundPaymentStepId = "refund-payment-step"
@@ -1,4 +1,4 @@
import { PaymentDTO } from "@medusajs/types" import { BigNumberInput, PaymentDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { capturePaymentStep } from "../steps/capture-payment" import { capturePaymentStep } from "../steps/capture-payment"
@@ -9,7 +9,7 @@ export const capturePaymentWorkflow = createWorkflow(
input: WorkflowData<{ input: WorkflowData<{
payment_id: string payment_id: string
captured_by?: string captured_by?: string
amount?: number amount?: BigNumberInput
}> }>
): WorkflowData<PaymentDTO> => { ): WorkflowData<PaymentDTO> => {
const payment = capturePaymentStep(input) const payment = capturePaymentStep(input)
@@ -1,3 +1,4 @@
import { BigNumberInput } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { refundPaymentStep } from "../steps/refund-payment" import { refundPaymentStep } from "../steps/refund-payment"
@@ -8,7 +9,7 @@ export const refundPaymentWorkflow = createWorkflow(
input: WorkflowData<{ input: WorkflowData<{
payment_id: string payment_id: string
created_by?: string created_by?: string
amount?: number amount?: BigNumberInput
}> }>
) => { ) => {
const payment = refundPaymentStep(input) const payment = refundPaymentStep(input)
@@ -194,7 +194,8 @@ moduleIntegrationTestRunner({
it("should create an order, shipping method and items. Including taxes and adjustments associated with them", async function () { it("should create an order, shipping method and items. Including taxes and adjustments associated with them", async function () {
const createdOrder = await service.create(input) const createdOrder = await service.create(input)
expect(createdOrder).toEqual(expectation) const serializedOrder = JSON.parse(JSON.stringify(createdOrder))
expect(serializedOrder).toEqual(expectation)
}) })
it("should transform requested fields and relations to match the db schema and return the order", async function () { it("should transform requested fields and relations to match the db schema and return the order", async function () {
@@ -232,7 +233,8 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(getOrder).toEqual(expectation) const serializedOrder = JSON.parse(JSON.stringify(getOrder))
expect(serializedOrder).toEqual(expectation)
}) })
it("should return order transactions", async function () { it("should return order transactions", async function () {
@@ -247,7 +249,8 @@ moduleIntegrationTestRunner({
relations: ["transactions"], relations: ["transactions"],
}) })
expect(getOrder).toEqual( const serializedOrder = JSON.parse(JSON.stringify(getOrder))
expect(serializedOrder).toEqual(
expect.objectContaining({ expect.objectContaining({
id: createdOrder.id, id: createdOrder.id,
transactions: [ transactions: [
@@ -1,8 +1,8 @@
import { Modules } from "@medusajs/modules-sdk" import { Modules } from "@medusajs/modules-sdk"
import { CreateOrderDTO, IOrderModuleService } from "@medusajs/types" import { CreateOrderDTO, IOrderModuleService } from "@medusajs/types"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { ChangeActionType } from "../../src/utils"
import { BigNumber } from "@medusajs/utils" import { BigNumber } from "@medusajs/utils"
import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils"
import { ChangeActionType } from "../../src/utils"
jest.setTimeout(100000) jest.setTimeout(100000)
@@ -212,8 +212,10 @@ moduleIntegrationTestRunner({
], ],
relations: ["items", "shipping_methods", "transactions"], relations: ["items", "shipping_methods", "transactions"],
}) })
const serializedFinalOrder = JSON.parse(JSON.stringify(finalOrder))
expect(createdOrder.items).toEqual([ const serializedCreatedOrder = JSON.parse(JSON.stringify(createdOrder))
expect(serializedCreatedOrder.items).toEqual([
expect.objectContaining({ expect.objectContaining({
title: "Item 1", title: "Item 1",
unit_price: 8, unit_price: 8,
@@ -252,12 +254,12 @@ moduleIntegrationTestRunner({
}), }),
]) ])
expect(finalOrder).toEqual( expect(serializedFinalOrder).toEqual(
expect.objectContaining({ expect.objectContaining({
version: 1, version: 1,
}) })
) )
expect(finalOrder.items).toEqual([ expect(serializedFinalOrder.items).toEqual([
expect.objectContaining({ expect.objectContaining({
title: "Item 1", title: "Item 1",
subtitle: "Subtitle 1", subtitle: "Subtitle 1",
@@ -396,7 +398,8 @@ moduleIntegrationTestRunner({
], ],
}) })
await service.confirmOrderChange(orderChange.id, { await service.confirmOrderChange({
id: orderChange.id,
confirmed_by: "cx_agent_123", confirmed_by: "cx_agent_123",
}) })
@@ -416,13 +419,15 @@ moduleIntegrationTestRunner({
relations: ["items", "shipping_methods", "transactions"], relations: ["items", "shipping_methods", "transactions"],
}) })
expect(modified).toEqual( const serializedModifiedOrder = JSON.parse(JSON.stringify(modified))
expect(serializedModifiedOrder).toEqual(
expect.objectContaining({ expect.objectContaining({
version: 2, version: 2,
}) })
) )
expect(modified.items).toEqual([ expect(serializedModifiedOrder.items).toEqual([
expect.objectContaining({ expect.objectContaining({
quantity: 2, quantity: 2,
detail: expect.objectContaining({ detail: expect.objectContaining({
@@ -1,5 +1,8 @@
import { Modules } from "@medusajs/modules-sdk" import { Modules } from "@medusajs/modules-sdk"
import { IOrderModuleService } from "@medusajs/types" import {
CreateOrderLineItemTaxLineDTO,
IOrderModuleService,
} from "@medusajs/types"
import { OrderStatus } from "@medusajs/utils" import { OrderStatus } from "@medusajs/utils"
import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils" import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils"
@@ -134,7 +137,8 @@ moduleIntegrationTestRunner({
relations: ["items.item"], relations: ["items.item"],
}) })
expect(order).toEqual( const serialized = JSON.parse(JSON.stringify(order))
expect(serialized).toEqual(
expect.objectContaining({ expect.objectContaining({
id: createdOrder.id, id: createdOrder.id,
currency_code: "eur", currency_code: "eur",
@@ -179,7 +183,8 @@ moduleIntegrationTestRunner({
} }
) )
expect(orders).toEqual( const serialized = JSON.parse(JSON.stringify(orders))
expect(serialized).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
currency_code: "eur", currency_code: "eur",
@@ -398,7 +403,8 @@ moduleIntegrationTestRunner({
relations: ["items.item"], relations: ["items.item"],
}) })
expect(order.items[0]).toEqual( const serialized = JSON.parse(JSON.stringify(order))
expect(serialized.items[0]).toEqual(
expect.objectContaining({ expect.objectContaining({
quantity: 1, quantity: 1,
title: "test", title: "test",
@@ -436,7 +442,8 @@ moduleIntegrationTestRunner({
relations: ["items.item"], relations: ["items.item"],
}) })
expect(order.items).toEqual( const serialized = JSON.parse(JSON.stringify(order))
expect(serialized.items).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
title: "test", title: "test",
@@ -669,7 +676,8 @@ moduleIntegrationTestRunner({
relations: ["items.item"], relations: ["items.item"],
}) })
expect(order.items).toEqual( const serialized = JSON.parse(JSON.stringify(order))
expect(serialized.items).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
title: "changed-test", title: "changed-test",
@@ -948,7 +956,8 @@ moduleIntegrationTestRunner({
relations: ["items.item.adjustments"], relations: ["items.item.adjustments"],
}) })
expect(order.items).toEqual( const serialized = JSON.parse(JSON.stringify(order))
expect(serialized.items).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: itemOne.id, id: itemOne.id,
@@ -1071,7 +1080,8 @@ moduleIntegrationTestRunner({
relations: ["items.item.adjustments"], relations: ["items.item.adjustments"],
}) })
expect(order.items).toEqual( const serialized = JSON.parse(JSON.stringify(order))
expect(serialized.items).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: itemOne.id, id: itemOne.id,
@@ -1224,9 +1234,10 @@ moduleIntegrationTestRunner({
}, },
]) ])
const [checkOrderOne, checkOrderTwo] = await service.list( const [checkOrderOne, checkOrderTwo] = JSON.parse(
{}, JSON.stringify(
{ relations: ["items.item.adjustments"] } await service.list({}, { relations: ["items.item.adjustments"] })
)
) )
expect(checkOrderOne.items).toEqual( expect(checkOrderOne.items).toEqual(
@@ -1445,7 +1456,8 @@ moduleIntegrationTestRunner({
relations: ["shipping_methods.adjustments"], relations: ["shipping_methods.adjustments"],
}) })
expect(order.shipping_methods).toEqual( const serialized = JSON.parse(JSON.stringify(order))
expect(serialized.shipping_methods).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: shippingMethodOne.id, id: shippingMethodOne.id,
@@ -1572,7 +1584,8 @@ moduleIntegrationTestRunner({
relations: ["shipping_methods.adjustments"], relations: ["shipping_methods.adjustments"],
}) })
expect(order.shipping_methods).toEqual( const serialized = JSON.parse(JSON.stringify(order))
expect(serialized.shipping_methods).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: shippingMethodOne.id, id: shippingMethodOne.id,
@@ -1994,7 +2007,8 @@ moduleIntegrationTestRunner({
relations: ["items.item.tax_lines"], relations: ["items.item.tax_lines"],
}) })
expect(order.items).toEqual( const serialized = JSON.parse(JSON.stringify(order))
expect(serialized.items).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: itemOne.id, id: itemOne.id,
@@ -2111,7 +2125,8 @@ moduleIntegrationTestRunner({
relations: ["items.item.tax_lines"], relations: ["items.item.tax_lines"],
}) })
expect(order.items).toEqual( const serialized = JSON.parse(JSON.stringify(order))
expect(serialized.items).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: itemOne.id, id: itemOne.id,
@@ -2174,7 +2189,7 @@ moduleIntegrationTestRunner({
]) ])
) )
const taxLine = taxLines.find((tx) => tx.item_id === itemOne.id) const taxLine = taxLines.find((tx) => tx.item_id === itemOne.id)!
await service.setLineItemTaxLines(createdOrder.id, [ await service.setLineItemTaxLines(createdOrder.id, [
// update // update
@@ -2188,7 +2203,7 @@ moduleIntegrationTestRunner({
item_id: itemOne.id, item_id: itemOne.id,
rate: 25, rate: 25,
code: "TX-2", code: "TX-2",
}, } as CreateOrderLineItemTaxLineDTO,
// delete: should delete the initial tax line for itemOne // delete: should delete the initial tax line for itemOne
]) ])
@@ -2196,7 +2211,8 @@ moduleIntegrationTestRunner({
relations: ["items.item.tax_lines"], relations: ["items.item.tax_lines"],
}) })
expect(order.items).toEqual( const serialized = JSON.parse(JSON.stringify(order))
expect(serialized.items).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: itemOne.id, id: itemOne.id,
@@ -2356,9 +2372,10 @@ moduleIntegrationTestRunner({
}, },
]) ])
const [checkOrderOne, checkOrderTwo] = await service.list( const [checkOrderOne, checkOrderTwo] = JSON.parse(
{}, JSON.stringify(
{ relations: ["items.item.tax_lines"] } await service.list({}, { relations: ["items.item.tax_lines"] })
)
) )
expect(checkOrderOne.items).toEqual( expect(checkOrderOne.items).toEqual(
@@ -1670,7 +1670,7 @@ export default class OrderModuleService<
} }
return data.order_change_id return data.order_change_id
}) })
.filter(Boolean) .filter(Boolean) as string[]
if (orderChangeIds.length) { if (orderChangeIds.length) {
const ordChanges = await this.getAndValidateOrderChange_( const ordChanges = await this.getAndValidateOrderChange_(
+2 -2
View File
@@ -1,5 +1,5 @@
import { OrderTypes } from "@medusajs/types" import { OrderTypes } from "@medusajs/types"
import { deduplicate, isDefined } from "@medusajs/utils" import { decorateCartTotals, deduplicate, isDefined } from "@medusajs/utils"
export function formatOrder( export function formatOrder(
order order
@@ -23,7 +23,7 @@ export function formatOrder(
order.summary = order.summary?.[0]?.totals order.summary = order.summary?.[0]?.totals
return order return decorateCartTotals(order)
}) })
return isArray ? orders : orders[0] return isArray ? orders : orders[0]
@@ -13,6 +13,7 @@ import {
} from "@medusajs/types" } from "@medusajs/types"
import { import {
AbstractPaymentProvider, AbstractPaymentProvider,
BigNumber,
MedusaError, MedusaError,
PaymentActions, PaymentActions,
isPaymentProviderError, isPaymentProviderError,
@@ -115,7 +116,7 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeCredentials> {
const intentRequest: Stripe.PaymentIntentCreateParams = { const intentRequest: Stripe.PaymentIntentCreateParams = {
description, description,
amount: Math.round(amount), amount: Math.round(new BigNumber(amount).numeric),
currency: currency_code, currency: currency_code,
metadata: { resource_id: resource_id ?? "Medusa Payment" }, metadata: { resource_id: resource_id ?? "Medusa Payment" },
capture_method: this.options_.capture ? "automatic" : "manual", capture_method: this.options_.capture ? "automatic" : "manual",
@@ -259,6 +260,8 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeCredentials> {
): Promise<PaymentProviderError | PaymentProviderSessionResponse> { ): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
const { context, data, amount } = input const { context, data, amount } = input
const amountNumeric = Math.round(new BigNumber(amount).numeric)
const stripeId = context.customer?.metadata?.stripe_id const stripeId = context.customer?.metadata?.stripe_id
if (stripeId !== data.customer) { if (stripeId !== data.customer) {
@@ -272,14 +275,14 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeCredentials> {
return result return result
} else { } else {
if (amount && data.amount === Math.round(amount)) { if (amount && data.amount === amountNumeric) {
return { data } return { data }
} }
try { try {
const id = data.id as string const id = data.id as string
const sessionData = (await this.stripe_.paymentIntents.update(id, { const sessionData = (await this.stripe_.paymentIntents.update(id, {
amount: Math.round(amount), amount: amountNumeric,
})) as unknown as PaymentProviderSessionResponse["data"] })) as unknown as PaymentProviderSessionResponse["data"]
return { data: sessionData } return { data: sessionData }
+17 -14
View File
@@ -27,9 +27,11 @@ import {
UpsertPaymentCollectionDTO, UpsertPaymentCollectionDTO,
} from "@medusajs/types" } from "@medusajs/types"
import { import {
BigNumber,
InjectManager, InjectManager,
InjectTransactionManager, InjectTransactionManager,
isString, isString,
MathBN,
MedusaContext, MedusaContext,
MedusaError, MedusaError,
ModulesSdkUtils, ModulesSdkUtils,
@@ -43,7 +45,6 @@ import {
PaymentSession, PaymentSession,
Refund, Refund,
} from "@models" } from "@models"
import BigNumber from "bignumber.js"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
import PaymentProviderService from "./payment-provider" import PaymentProviderService from "./payment-provider"
@@ -532,15 +533,14 @@ export default class PaymentModuleService<
} }
const capturedAmount = payment.captures.reduce((captureAmount, next) => { const capturedAmount = payment.captures.reduce((captureAmount, next) => {
const amountAsBigNumber = new BigNumber(next.raw_amount.value) return MathBN.add(captureAmount, next.raw_amount)
return captureAmount.plus(amountAsBigNumber) }, MathBN.convert(0))
}, BigNumber(0))
const authorizedAmount = BigNumber(payment.raw_amount.value) const authorizedAmount = new BigNumber(payment.raw_amount)
const newCaptureAmount = BigNumber(data.amount) const newCaptureAmount = new BigNumber(data.amount)
const remainingToCapture = authorizedAmount.minus(capturedAmount) const remainingToCapture = MathBN.sub(authorizedAmount, capturedAmount)
if (newCaptureAmount.gt(remainingToCapture)) { if (MathBN.gt(newCaptureAmount, remainingToCapture)) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
`You cannot capture more than the authorized amount substracted by what is already captured.` `You cannot capture more than the authorized amount substracted by what is already captured.`
@@ -567,7 +567,10 @@ export default class PaymentModuleService<
) )
// When the entire authorized amount has been captured, we mark it fully capture by setting the captured_at field // When the entire authorized amount has been captured, we mark it fully capture by setting the captured_at field
if (capturedAmount.plus(newCaptureAmount).eq(authorizedAmount)) { const totalCaptured = MathBN.convert(
MathBN.add(capturedAmount, newCaptureAmount)
)
if (MathBN.gte(totalCaptured, authorizedAmount)) {
await this.paymentService_.update( await this.paymentService_.update(
{ id: payment.id, captured_at: new Date() }, { id: payment.id, captured_at: new Date() },
sharedContext sharedContext
@@ -600,12 +603,12 @@ export default class PaymentModuleService<
} }
const capturedAmount = payment.captures.reduce((captureAmount, next) => { const capturedAmount = payment.captures.reduce((captureAmount, next) => {
const amountAsBigNumber = new BigNumber(next.raw_amount.value) const amountAsBigNumber = new BigNumber(next.raw_amount)
return captureAmount.plus(amountAsBigNumber) return MathBN.add(captureAmount, amountAsBigNumber)
}, BigNumber(0)) }, MathBN.convert(0))
const refundAmount = BigNumber(data.amount) const refundAmount = new BigNumber(data.amount)
if (capturedAmount.lt(refundAmount)) { if (MathBN.lt(capturedAmount, refundAmount)) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
`You cannot refund more than what is captured on the payment.` `You cannot refund more than what is captured on the payment.`
+68 -47
View File
@@ -1,5 +1,6 @@
import { BaseFilterable } from "../dal" import { BaseFilterable } from "../dal"
import { OperatorMap } from "../dal/utils" import { OperatorMap } from "../dal/utils"
import { BigNumberValue } from "../totals"
/** /**
* The adjustment line details. * The adjustment line details.
@@ -19,7 +20,7 @@ export interface AdjustmentLineDTO {
/** /**
* The amount to adjust the original amount with. * The amount to adjust the original amount with.
*/ */
amount: number amount: BigNumberValue
/** /**
* The ID of the associated cart. * The ID of the associated cart.
@@ -142,6 +143,16 @@ export interface ShippingMethodTaxLineDTO extends TaxLineDTO {
* The ID of the associated shipping method. * The ID of the associated shipping method.
*/ */
shipping_method_id: string shipping_method_id: string
/**
* The total tax relative to the shipping method.
*/
total: BigNumberValue
/**
* The subtotal tax relative to the shipping method.
*/
subtotal: BigNumberValue
} }
/** /**
@@ -157,6 +168,16 @@ export interface LineItemTaxLineDTO extends TaxLineDTO {
* The ID of the associated line item. * The ID of the associated line item.
*/ */
item_id: string item_id: string
/**
* The total tax relative to the item.
*/
total: BigNumberValue
/**
* The subtotal tax relative to the item.
*/
subtotal: BigNumberValue
} }
/** /**
@@ -266,7 +287,7 @@ export interface CartShippingMethodDTO {
/** /**
* The price of the shipping method. * The price of the shipping method.
*/ */
amount: number amount: BigNumberValue
/** /**
* Whether the shipping method price is tax inclusive. * Whether the shipping method price is tax inclusive.
@@ -315,42 +336,42 @@ export interface CartShippingMethodDTO {
/** /**
* The original total of the cart shipping method. * The original total of the cart shipping method.
*/ */
original_total: number original_total: BigNumberValue
/** /**
* The original subtotal of the cart shipping method. * The original subtotal of the cart shipping method.
*/ */
original_subtotal: number original_subtotal: BigNumberValue
/** /**
* The original tax total of the cart shipping method. * The original tax total of the cart shipping method.
*/ */
original_tax_total: number original_tax_total: BigNumberValue
/** /**
* The total of the cart shipping method. * The total of the cart shipping method.
*/ */
total: number total: BigNumberValue
/** /**
* The subtotal of the cart shipping method. * The subtotal of the cart shipping method.
*/ */
subtotal: number subtotal: BigNumberValue
/** /**
* The tax total of the cart shipping method. * The tax total of the cart shipping method.
*/ */
tax_total: number tax_total: BigNumberValue
/** /**
* The discount total of the cart shipping method. * The discount total of the cart shipping method.
*/ */
discount_total: number discount_total: BigNumberValue
/** /**
* The discount tax total of the cart shipping method. * The discount tax total of the cart shipping method.
*/ */
discount_tax_total: number discount_tax_total: BigNumberValue
} }
/** /**
@@ -360,57 +381,57 @@ export interface CartLineItemTotalsDTO {
/** /**
* The original total of the cart line item. * The original total of the cart line item.
*/ */
original_total: number original_total: BigNumberValue
/** /**
* The original subtotal of the cart line item. * The original subtotal of the cart line item.
*/ */
original_subtotal: number original_subtotal: BigNumberValue
/** /**
* The original tax total of the cart line item. * The original tax total of the cart line item.
*/ */
original_tax_total: number original_tax_total: BigNumberValue
/** /**
* The item total of the cart line item. * The item total of the cart line item.
*/ */
item_total: number item_total: BigNumberValue
/** /**
* The item subtotal of the cart line item. * The item subtotal of the cart line item.
*/ */
item_subtotal: number item_subtotal: BigNumberValue
/** /**
* The item tax total of the cart line item. * The item tax total of the cart line item.
*/ */
item_tax_total: number item_tax_total: BigNumberValue
/** /**
* The total of the cart line item. * The total of the cart line item.
*/ */
total: number total: BigNumberValue
/** /**
* The subtotal of the cart line item. * The subtotal of the cart line item.
*/ */
subtotal: number subtotal: BigNumberValue
/** /**
* The tax total of the cart line item. * The tax total of the cart line item.
*/ */
tax_total: number tax_total: BigNumberValue
/** /**
* The discount total of the cart line item. * The discount total of the cart line item.
*/ */
discount_total: number discount_total: BigNumberValue
/** /**
* The discount tax total of the cart line item. * The discount tax total of the cart line item.
*/ */
discount_tax_total: number discount_tax_total: BigNumberValue
} }
/** /**
@@ -440,7 +461,7 @@ export interface CartLineItemDTO extends CartLineItemTotalsDTO {
/** /**
* The line item's quantity in the cart. * The line item's quantity in the cart.
*/ */
quantity: number quantity: BigNumberValue
/** /**
* The ID of the associated product. * The ID of the associated product.
@@ -520,12 +541,12 @@ export interface CartLineItemDTO extends CartLineItemTotalsDTO {
/** /**
* The calculated price of the line item. * The calculated price of the line item.
*/ */
compare_at_unit_price?: number compare_at_unit_price?: BigNumberValue
/** /**
* The unit price of the item. * The unit price of the item.
*/ */
unit_price: number unit_price: BigNumberValue
/** /**
* The associated tax lines. * The associated tax lines.
@@ -654,67 +675,67 @@ export interface CartDTO {
/** /**
* The original item total of the cart. * The original item total of the cart.
*/ */
original_item_total: number original_item_total: BigNumberValue
/** /**
* The original item subtotal of the cart. * The original item subtotal of the cart.
*/ */
original_item_subtotal: number original_item_subtotal: BigNumberValue
/** /**
* The original item tax total of the cart. * The original item tax total of the cart.
*/ */
original_item_tax_total: number original_item_tax_total: BigNumberValue
/** /**
* The item total of the cart. * The item total of the cart.
*/ */
item_total: number item_total: BigNumberValue
/** /**
* The item subtotal of the cart. * The item subtotal of the cart.
*/ */
item_subtotal: number item_subtotal: BigNumberValue
/** /**
* The item tax total of the cart. * The item tax total of the cart.
*/ */
item_tax_total: number item_tax_total: BigNumberValue
/** /**
* The original total of the cart. * The original total of the cart.
*/ */
original_total: number original_total: BigNumberValue
/** /**
* The original subtotal of the cart. * The original subtotal of the cart.
*/ */
original_subtotal: number original_subtotal: BigNumberValue
/** /**
* The original tax total of the cart. * The original tax total of the cart.
*/ */
original_tax_total: number original_tax_total: BigNumberValue
/** /**
* The total of the cart. * The total of the cart.
*/ */
total: number total: BigNumberValue
/** /**
* The subtotal of the cart. * The subtotal of the cart. (Excluding taxes)
*/ */
subtotal: number subtotal: BigNumberValue
/** /**
* The tax total of the cart. * The tax total of the cart.
*/ */
tax_total: number tax_total: BigNumberValue
/** /**
* The discount total of the cart. * The discount total of the cart.
*/ */
discount_total: number discount_total: BigNumberValue
/** /**
* The raw discount total of the cart. * The raw discount total of the cart.
@@ -724,47 +745,47 @@ export interface CartDTO {
/** /**
* The discount tax total of the cart. * The discount tax total of the cart.
*/ */
discount_tax_total: number discount_tax_total: BigNumberValue
/** /**
* The gift card total of the cart. * The gift card total of the cart.
*/ */
gift_card_total: number gift_card_total: BigNumberValue
/** /**
* The gift card tax total of the cart. * The gift card tax total of the cart.
*/ */
gift_card_tax_total: number gift_card_tax_total: BigNumberValue
/** /**
* The shipping total of the cart. * The shipping total of the cart.
*/ */
shipping_total: number shipping_total: BigNumberValue
/** /**
* The shipping subtotal of the cart. * The shipping subtotal of the cart.
*/ */
shipping_subtotal: number shipping_subtotal: BigNumberValue
/** /**
* The shipping tax total of the cart. * The shipping tax total of the cart.
*/ */
shipping_tax_total: number shipping_tax_total: BigNumberValue
/** /**
* The original shipping total of the cart. * The original shipping total of the cart.
*/ */
original_shipping_total: number original_shipping_total: BigNumberValue
/** /**
* The original shipping subtotal of the cart. * The original shipping subtotal of the cart.
*/ */
original_shipping_subtotal: number original_shipping_subtotal: BigNumberValue
/** /**
* The original shipping tax total of the cart. * The original shipping tax total of the cart.
*/ */
original_shipping_tax_total: number original_shipping_tax_total: BigNumberValue
} }
/** /**
+14 -13
View File
@@ -1,3 +1,4 @@
import { BigNumberInput } from "../totals"
import { CartDTO, CartLineItemDTO } from "./common" import { CartDTO, CartLineItemDTO } from "./common"
/** ADDRESS START */ /** ADDRESS START */
@@ -228,7 +229,7 @@ export interface CreateAdjustmentDTO {
/** /**
* The amount to adjust the original amount with. * The amount to adjust the original amount with.
*/ */
amount: number amount: BigNumberInput
/** /**
* The description of the adjustment. * The description of the adjustment.
@@ -264,7 +265,7 @@ export interface UpdateAdjustmentDTO {
/** /**
* The amount to adjust the original amount with. * The amount to adjust the original amount with.
*/ */
amount: number amount: BigNumberInput
/** /**
* The description of the adjustment. * The description of the adjustment.
@@ -325,7 +326,7 @@ export interface UpsertLineItemAdjustmentDTO {
/** /**
* The amount to adjust the original amount with. * The amount to adjust the original amount with.
*/ */
amount?: number amount?: BigNumberInput
/** /**
* The description of the line item adjustment. * The description of the line item adjustment.
@@ -463,7 +464,7 @@ export interface CreateLineItemDTO {
/** /**
* The quantity of the line item in the cart. * The quantity of the line item in the cart.
*/ */
quantity: number quantity: BigNumberInput
/** /**
* The associated product's ID. * The associated product's ID.
@@ -543,12 +544,12 @@ export interface CreateLineItemDTO {
/** /**
* The calculated price of the line item after applying promotions. * The calculated price of the line item after applying promotions.
*/ */
compare_at_unit_price?: number compare_at_unit_price?: BigNumberInput
/** /**
* The unit price of the line item. * The unit price of the line item.
*/ */
unit_price: number | string unit_price: BigNumberInput
/** /**
* The tax lines of the line item. * The tax lines of the line item.
@@ -626,12 +627,12 @@ export interface UpdateLineItemDTO
/** /**
* The quantity of the line item in the cart. * The quantity of the line item in the cart.
*/ */
quantity?: number quantity?: BigNumberInput
/** /**
* The unit price of the line item. * The unit price of the line item.
*/ */
unit_price?: number unit_price?: BigNumberInput
/** /**
* Holds custom data in key-value pairs. * Holds custom data in key-value pairs.
@@ -670,7 +671,7 @@ export interface CreateShippingMethodDTO {
/** /**
* The amount of the shipping method. * The amount of the shipping method.
*/ */
amount: number amount: BigNumberInput
/** /**
* The data of the shipping method. * The data of the shipping method.
@@ -700,7 +701,7 @@ export interface CreateShippingMethodForSingleCartDTO {
/** /**
* The amount of the shipping method. * The amount of the shipping method.
*/ */
amount: number amount: BigNumberInput
/** /**
* The data of the shipping method. * The data of the shipping method.
@@ -735,7 +736,7 @@ export interface UpdateShippingMethodDTO {
/** /**
* The amount of the shipping method. * The amount of the shipping method.
*/ */
amount?: number amount?: BigNumberInput
/** /**
* The data of the shipping method. * The data of the shipping method.
@@ -771,7 +772,7 @@ export interface CreateShippingMethodAdjustmentDTO {
/** /**
* The amount to adjust the original amount with. * The amount to adjust the original amount with.
*/ */
amount: number amount: BigNumberInput
/** /**
* The description of the shipping method adjustment. * The description of the shipping method adjustment.
@@ -807,7 +808,7 @@ export interface UpdateShippingMethodAdjustmentDTO {
/** /**
* The amount to adjust the original amount with. * The amount to adjust the original amount with.
*/ */
amount?: number amount?: BigNumberInput
/** /**
* The description of the shipping method adjustment. * The description of the shipping method adjustment.
+6 -5
View File
@@ -2,11 +2,12 @@ import { CustomerDTO } from "../customer"
import { ShippingOptionDTO } from "../fulfillment" import { ShippingOptionDTO } from "../fulfillment"
import { ProductDTO } from "../product" import { ProductDTO } from "../product"
import { RegionDTO } from "../region" import { RegionDTO } from "../region"
import { BigNumberInput } from "../totals"
import { CartDTO, CartLineItemDTO } from "./common" import { CartDTO, CartLineItemDTO } from "./common"
import { UpdateLineItemDTO } from "./mutations" import { UpdateLineItemDTO } from "./mutations"
export interface CreateCartCreateLineItemDTO { export interface CreateCartCreateLineItemDTO {
quantity: number quantity: BigNumberInput
variant_id: string variant_id: string
title?: string title?: string
@@ -31,8 +32,8 @@ export interface CreateCartCreateLineItemDTO {
is_tax_inclusive?: boolean is_tax_inclusive?: boolean
is_giftcard?: boolean is_giftcard?: boolean
compare_at_unit_price?: number compare_at_unit_price?: BigNumberInput
unit_price?: number | string unit_price?: BigNumberInput
metadata?: Record<string, unknown> metadata?: Record<string, unknown>
} }
@@ -93,7 +94,7 @@ export interface CreatePaymentCollectionForCartWorkflowInputDTO {
cart_id: string cart_id: string
region_id: string region_id: string
currency_code: string currency_code: string
amount: number amount: BigNumberInput
metadata?: Record<string, unknown> metadata?: Record<string, unknown>
} }
@@ -115,5 +116,5 @@ export interface ListShippingOptionsForCartWorkflowInputDTO {
} }
export interface PricedShippingOptionDTO extends ShippingOptionDTO { export interface PricedShippingOptionDTO extends ShippingOptionDTO {
amount: number amount: BigNumberInput
} }
+79 -25
View File
@@ -1,30 +1,30 @@
import { BaseFilterable } from "../dal" import { BaseFilterable } from "../dal"
import { OperatorMap } from "../dal/utils" import { OperatorMap } from "../dal/utils"
import { BigNumberRawValue } from "../totals" import { BigNumberRawValue, BigNumberValue } from "../totals"
export type OrderSummaryDTO = { export type OrderSummaryDTO = {
total: number total: BigNumberValue
subtotal: number subtotal: BigNumberValue
total_tax: number total_tax: BigNumberValue
ordered_total: number ordered_total: BigNumberValue
fulfilled_total: number fulfilled_total: BigNumberValue
returned_total: number returned_total: BigNumberValue
return_request_total: number return_request_total: BigNumberValue
write_off_total: number write_off_total: BigNumberValue
projected_total: number projected_total: BigNumberValue
net_total: number net_total: BigNumberValue
net_subtotal: number net_subtotal: BigNumberValue
net_total_tax: number net_total_tax: BigNumberValue
future_total: number future_total: BigNumberValue
future_subtotal: number future_subtotal: BigNumberValue
future_total_tax: number future_total_tax: BigNumberValue
future_projected_total: number future_projected_total: BigNumberValue
balance: number balance: BigNumberValue
future_balance: number future_balance: BigNumberValue
} }
export interface OrderAdjustmentLineDTO { export interface OrderAdjustmentLineDTO {
@@ -39,7 +39,7 @@ export interface OrderAdjustmentLineDTO {
/** /**
* The amount of the adjustment line * The amount of the adjustment line
*/ */
amount: number amount: BigNumberValue
/** /**
* The ID of the associated order * The ID of the associated order
*/ */
@@ -134,6 +134,16 @@ export interface OrderShippingMethodTaxLineDTO extends OrderTaxLineDTO {
* The ID of the associated shipping method * The ID of the associated shipping method
*/ */
shipping_method_id: string shipping_method_id: string
/**
* The total tax relative to the shipping method.
*/
total: BigNumberValue
/**
* The subtotal tax relative to the shipping method.
*/
subtotal: BigNumberValue
} }
export interface OrderLineItemTaxLineDTO extends OrderTaxLineDTO { export interface OrderLineItemTaxLineDTO extends OrderTaxLineDTO {
@@ -145,6 +155,16 @@ export interface OrderLineItemTaxLineDTO extends OrderTaxLineDTO {
* The ID of the associated line item * The ID of the associated line item
*/ */
item_id: string item_id: string
/**
* The total tax relative to the item.
*/
total: BigNumberValue
/**
* The subtotal tax relative to the item.
*/
subtotal: BigNumberValue
} }
export interface OrderAddressDTO { export interface OrderAddressDTO {
@@ -276,11 +296,45 @@ export interface OrderShippingMethodDTO {
*/ */
updated_at: Date | string updated_at: Date | string
total: number /**
subtotal: number * The original total of the order shipping method.
tax_total: number */
discount_total: number original_total: BigNumberValue
discount_tax_total: number
/**
* The original subtotal of the order shipping method.
*/
original_subtotal: BigNumberValue
/**
* The original tax total of the order shipping method.
*/
original_tax_total: BigNumberValue
/**
* The total of the order shipping method.
*/
total: BigNumberValue
/**
* The subtotal of the order shipping method.
*/
subtotal: BigNumberValue
/**
* The tax total of the order shipping method.
*/
tax_total: BigNumberValue
/**
* The discount total of the order shipping method.
*/
discount_total: BigNumberValue
/**
* The discount tax total of the order shipping method.
*/
discount_tax_total: BigNumberValue
} }
export interface OrderLineItemTotalsDTO { export interface OrderLineItemTotalsDTO {
+6 -4
View File
@@ -40,7 +40,7 @@ export interface CreateOrderDTO {
no_notification?: boolean no_notification?: boolean
items?: CreateOrderLineItemDTO[] items?: CreateOrderLineItemDTO[]
shipping_methods?: Omit<CreateOrderShippingMethodDTO, "order_id">[] shipping_methods?: Omit<CreateOrderShippingMethodDTO, "order_id">[]
transactions?: CreateOrderTransactionDTO[] transactions?: Omit<CreateOrderTransactionDTO, "order_id">[]
metadata?: Record<string, unknown> metadata?: Record<string, unknown>
promo_codes?: string[] promo_codes?: string[]
@@ -244,7 +244,9 @@ export interface CreateOrderChangeDTO {
internal_note?: string internal_note?: string
requested_by?: string requested_by?: string
requested_at?: Date requested_at?: Date
created_by?: string
metadata?: Record<string, unknown> metadata?: Record<string, unknown>
actions?: CreateOrderChangeActionDTO[]
} }
export interface UpdateOrderChangeDTO { export interface UpdateOrderChangeDTO {
@@ -286,9 +288,9 @@ export interface ConfirmOrderChangeDTO {
/** ORDER CHANGE ACTION START */ /** ORDER CHANGE ACTION START */
export interface CreateOrderChangeActionDTO { export interface CreateOrderChangeActionDTO {
order_change_id: string order_change_id?: string
reference: string reference?: string
reference_id: string reference_id?: string
action: string action: string
internal_note?: string internal_note?: string
details?: Record<string, unknown> details?: Record<string, unknown>
+14 -13
View File
@@ -1,5 +1,6 @@
import { BaseFilterable } from "../dal" import { BaseFilterable } from "../dal"
import { OperatorMap } from "../dal/utils" import { OperatorMap } from "../dal/utils"
import { BigNumberValue } from "../totals"
/* ********** PAYMENT COLLECTION ********** */ /* ********** PAYMENT COLLECTION ********** */
/** /**
* @enum * @enum
@@ -87,17 +88,17 @@ export interface PaymentCollectionDTO {
/** /**
* The total amount to be authorized and captured. * The total amount to be authorized and captured.
*/ */
amount: number amount: BigNumberValue
/** /**
* The amount authorized within the associated payment sessions. * The amount authorized within the associated payment sessions.
*/ */
authorized_amount?: number authorized_amount?: BigNumberValue
/** /**
* The amount refunded within the associated payments. * The amount refunded within the associated payments.
*/ */
refunded_amount?: number refunded_amount?: BigNumberValue
/** /**
* When the payment collection was completed. * When the payment collection was completed.
@@ -190,7 +191,7 @@ export interface FilterablePaymentSessionProps
/** /**
* Filter the payment sessions by their amount. * Filter the payment sessions by their amount.
*/ */
amount?: number | OperatorMap<number> amount?: BigNumberValue | OperatorMap<BigNumberValue>
/** /**
* Filter the payment sessions by the ID of their associated payment provider. * Filter the payment sessions by the ID of their associated payment provider.
@@ -240,7 +241,7 @@ export interface FilterableCaptureProps extends BaseFilterable<CaptureDTO> {
/** /**
* Filter the captures by their amounts. * Filter the captures by their amounts.
*/ */
amount?: number | OperatorMap<number> amount?: BigNumberValue | OperatorMap<BigNumberValue>
/** /**
* Filter the captures by the ID of their associated payment. * Filter the captures by the ID of their associated payment.
@@ -280,7 +281,7 @@ export interface FilterableRefundProps extends BaseFilterable<RefundDTO> {
/** /**
* Filter the refunds by their amount. * Filter the refunds by their amount.
*/ */
amount?: number | OperatorMap<number> amount?: BigNumberValue | OperatorMap<BigNumberValue>
/** /**
* Filter the refunds by the ID of their associated payment. * Filter the refunds by the ID of their associated payment.
@@ -312,12 +313,12 @@ export interface PaymentDTO {
/** /**
* The payment's total amount. * The payment's total amount.
*/ */
amount: number amount: BigNumberValue
/** /**
* The authorized amount of the payment. * The authorized amount of the payment.
*/ */
authorized_amount?: number authorized_amount?: BigNumberValue
/** /**
* The ISO 3 character currency code of the payment. * The ISO 3 character currency code of the payment.
@@ -377,12 +378,12 @@ export interface PaymentDTO {
/** /**
* The sum of the associated captures' amounts. * The sum of the associated captures' amounts.
*/ */
captured_amount?: number captured_amount?: BigNumberValue
/** /**
* The sum of the associated refunds' amounts. * The sum of the associated refunds' amounts.
*/ */
refunded_amount?: number refunded_amount?: BigNumberValue
/** /**
* The associated captures. * The associated captures.
@@ -481,7 +482,7 @@ export interface CaptureDTO {
/** /**
* The captured amount. * The captured amount.
*/ */
amount: number amount: BigNumberValue
/** /**
* The creation date of the capture. * The creation date of the capture.
@@ -512,7 +513,7 @@ export interface RefundDTO {
/** /**
* The refunded amount. * The refunded amount.
*/ */
amount: number amount: BigNumberValue
/** /**
* The creation date of the refund. * The creation date of the refund.
@@ -543,7 +544,7 @@ export interface PaymentSessionDTO {
/** /**
* The amount to authorize. * The amount to authorize.
*/ */
amount: number amount: BigNumberValue
/** /**
* The 3 character currency code of the payment session. * The 3 character currency code of the payment session.
+9 -8
View File
@@ -1,3 +1,4 @@
import { BigNumberInput } from "../totals"
import { PaymentProviderContext } from "./provider" import { PaymentProviderContext } from "./provider"
/** /**
@@ -17,7 +18,7 @@ export interface CreatePaymentCollectionDTO {
/** /**
* The amount of the payment collection. * The amount of the payment collection.
*/ */
amount: number amount: BigNumberInput
/** /**
* Holds custom data in key-value pairs. * Holds custom data in key-value pairs.
@@ -58,7 +59,7 @@ export interface UpsertPaymentCollectionDTO {
/** /**
* The amount of the payment collection. * The amount of the payment collection.
*/ */
amount?: number amount?: BigNumberInput
/** /**
* Holds custom data in key-value pairs. * Holds custom data in key-value pairs.
@@ -83,7 +84,7 @@ export interface PaymentCollectionUpdatableFields {
/** /**
* The amount of the payment collection. * The amount of the payment collection.
*/ */
amount?: number amount?: BigNumberInput
/** /**
* Holds custom data in key-value pairs. * Holds custom data in key-value pairs.
@@ -98,7 +99,7 @@ export interface CreatePaymentDTO {
/** /**
* The amount of the payment. * The amount of the payment.
*/ */
amount: number amount: BigNumberInput
/** /**
* The ISO 3 character currency code of the payment. * The ISO 3 character currency code of the payment.
@@ -183,7 +184,7 @@ export interface CreateCaptureDTO {
/** /**
* The amount of the capture. * The amount of the capture.
*/ */
amount?: number amount?: BigNumberInput
/** /**
* The associated payment's ID. * The associated payment's ID.
@@ -204,7 +205,7 @@ export interface CreateRefundDTO {
/** /**
* The amount of the refund. * The amount of the refund.
*/ */
amount?: number amount?: BigNumberInput
/** /**
* The associated payment's ID. * The associated payment's ID.
@@ -235,7 +236,7 @@ export interface CreatePaymentSessionDTO {
/** /**
* The amount to be authorized. * The amount to be authorized.
*/ */
amount: number amount: BigNumberInput
/** /**
* Necessary data for the associated payment provider to process the payment. * Necessary data for the associated payment provider to process the payment.
@@ -270,7 +271,7 @@ export interface UpdatePaymentSessionDTO {
/** /**
* The amount to be authorized. * The amount to be authorized.
*/ */
amount: number amount: BigNumberInput
/** /**
* Necessary context data for the associated payment provider. * Necessary context data for the associated payment provider.
+6 -5
View File
@@ -1,5 +1,6 @@
import { AddressDTO } from "../address" import { AddressDTO } from "../address"
import { CustomerDTO } from "../customer" import { CustomerDTO } from "../customer"
import { BigNumberInput, BigNumberValue } from "../totals"
import { PaymentSessionStatus } from "./common" import { PaymentSessionStatus } from "./common"
import { ProviderWebhookPayload } from "./mutations" import { ProviderWebhookPayload } from "./mutations"
@@ -85,7 +86,7 @@ export type CreatePaymentProviderSession = {
/** /**
* The amount to be authorized. * The amount to be authorized.
*/ */
amount: number amount: BigNumberInput
/** /**
* The ISO 3 character currency code. * The ISO 3 character currency code.
@@ -112,7 +113,7 @@ export type UpdatePaymentProviderSession = {
/** /**
* The payment session's amount. * The payment session's amount.
*/ */
amount: number amount: BigNumberInput
/** /**
* The ISO 3 character code of the payment session. * The ISO 3 character code of the payment session.
@@ -203,7 +204,7 @@ export type WebhookActionData = {
/** /**
* The amount to be captured or authorized (based on the action's type.) * The amount to be captured or authorized (based on the action's type.)
*/ */
amount: number amount: BigNumberValue
} }
/** /**
@@ -310,12 +311,12 @@ export interface IPaymentProvider {
* In this method, you can interact with the third-party provider and perform any actions necessary to refund the payment. * In this method, you can interact with the third-party provider and perform any actions necessary to refund the payment.
* *
* @param {Record<string, unknown>} paymentSessionData - The `data` field of a Payment. * @param {Record<string, unknown>} paymentSessionData - The `data` field of a Payment.
* @param {number} refundAmount - The amount to refund. * @param {BigNumberInput} refundAmount - The amount to refund.
* @returns {Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>} Either an error object or an object that's stored in the `data` field of the payment. * @returns {Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>} Either an error object or an object that's stored in the `data` field of the payment.
*/ */
refundPayment( refundPayment(
paymentSessionData: Record<string, unknown>, paymentSessionData: Record<string, unknown>,
refundAmount: number refundAmount: BigNumberInput
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]> ): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
/** /**
@@ -1,4 +1,5 @@
import { BaseFilterable } from "../../dal" import { BaseFilterable } from "../../dal"
import { BigNumberInput, BigNumberValue } from "../../totals"
/** /**
* @interface * @interface
@@ -17,15 +18,15 @@ export interface MoneyAmountDTO {
/** /**
* The price of this money amount. * The price of this money amount.
*/ */
amount?: number amount?: BigNumberValue
/** /**
* The minimum quantity required to be purchased for this price to be applied. * The minimum quantity required to be purchased for this price to be applied.
*/ */
min_quantity?: number min_quantity?: BigNumberValue
/** /**
* The maximum quantity required to be purchased for this price to be applied. * The maximum quantity required to be purchased for this price to be applied.
*/ */
max_quantity?: number max_quantity?: BigNumberValue
/** /**
* When the money_amount was created. * When the money_amount was created.
*/ */
@@ -57,15 +58,15 @@ export interface CreateMoneyAmountDTO {
/** /**
* The amount of this money amount. * The amount of this money amount.
*/ */
amount: number amount: BigNumberInput
/** /**
* The minimum quantity required to be purchased for this money amount to be applied. * The minimum quantity required to be purchased for this money amount to be applied.
*/ */
min_quantity?: number | null min_quantity?: BigNumberInput | null
/** /**
* The maximum quantity required to be purchased for this money amount to be applied. * The maximum quantity required to be purchased for this money amount to be applied.
*/ */
max_quantity?: number | null max_quantity?: BigNumberInput | null
} }
/** /**
@@ -85,15 +86,15 @@ export interface UpdateMoneyAmountDTO {
/** /**
* The price of this money amount. * The price of this money amount.
*/ */
amount?: number amount?: BigNumberInput
/** /**
* The minimum quantity required to be purchased for this money amount to be applied. * The minimum quantity required to be purchased for this money amount to be applied.
*/ */
min_quantity?: number min_quantity?: BigNumberInput
/** /**
* The maximum quantity required to be purchased for this money amount to be applied. * The maximum quantity required to be purchased for this money amount to be applied.
*/ */
max_quantity?: number max_quantity?: BigNumberInput
} }
/** /**
@@ -1,5 +1,6 @@
import { BaseFilterable } from "../../dal" import { BaseFilterable } from "../../dal"
import { Context } from "../../shared-context" import { Context } from "../../shared-context"
import { BigNumberInput, BigNumberValue } from "../../totals"
import { import {
CreateMoneyAmountDTO, CreateMoneyAmountDTO,
FilterableMoneyAmountProps, FilterableMoneyAmountProps,
@@ -24,10 +25,10 @@ export interface PricingRepositoryService {
*/ */
export interface PricingContext { export interface PricingContext {
/** /**
* an object whose keys are the name of the context attribute. Its value can be a string or a number. For example, you can pass the `currency_code` property with its value being the currency code to calculate the price in. * an object whose keys are the name of the context attribute. Its value can be a string or a BigNumberInput. For example, you can pass the `currency_code` property with its value being the currency code to calculate the price in.
* Another example is passing the `quantity` property to calculate the price for that specified quantity, which finds a price set whose `min_quantity` and `max_quantity` conditions match the specified quantity. * Another example is passing the `quantity` property to calculate the price for that specified quantity, which finds a price set whose `min_quantity` and `max_quantity` conditions match the specified quantity.
*/ */
context?: Record<string, string | number> context?: Record<string, string | BigNumberInput>
} }
/** /**
@@ -124,7 +125,7 @@ export interface CalculatedPriceSet {
/** /**
* The amount of the calculated price, or `null` if there isn't a calculated price. * The amount of the calculated price, or `null` if there isn't a calculated price.
*/ */
calculated_amount: number | null calculated_amount: BigNumberValue | null
/** /**
* Whether the original price is associated with a price list. During the calculation process, if the price list of the calculated price is of type override, * Whether the original price is associated with a price list. During the calculation process, if the price list of the calculated price is of type override,
@@ -134,7 +135,7 @@ export interface CalculatedPriceSet {
/** /**
* The amount of the original price, or `null` if there isn't a calculated price. * The amount of the original price, or `null` if there isn't a calculated price.
*/ */
original_amount: number | null original_amount: BigNumberValue | null
/** /**
* The currency code of the calculated price, or null if there isn't a calculated price. * The currency code of the calculated price, or null if there isn't a calculated price.
@@ -160,11 +161,11 @@ export interface CalculatedPriceSet {
/** /**
* The `min_quantity` field defined on a price. * The `min_quantity` field defined on a price.
*/ */
min_quantity: number | null min_quantity: BigNumberValue | null
/** /**
* The `max_quantity` field defined on a price. * The `max_quantity` field defined on a price.
*/ */
max_quantity: number | null max_quantity: BigNumberValue | null
} }
/** /**
@@ -186,11 +187,11 @@ export interface CalculatedPriceSet {
/** /**
* The `min_quantity` field defined on a price. * The `min_quantity` field defined on a price.
*/ */
min_quantity: number | null min_quantity: BigNumberValue | null
/** /**
* The `max_quantity` field defined on a price. * The `max_quantity` field defined on a price.
*/ */
max_quantity: number | null max_quantity: BigNumberValue | null
} }
} }
+10 -9
View File
@@ -1,4 +1,5 @@
import { BaseFilterable } from "../../dal" import { BaseFilterable } from "../../dal"
import { BigNumberInput, BigNumberValue } from "../../totals"
import { PriceListDTO } from "./price-list" import { PriceListDTO } from "./price-list"
import { PriceRuleDTO } from "./price-rule" import { PriceRuleDTO } from "./price-rule"
import { PriceSetDTO } from "./price-set" import { PriceSetDTO } from "./price-set"
@@ -24,15 +25,15 @@ export interface PriceDTO {
/** /**
* The price of this price. * The price of this price.
*/ */
amount?: number amount?: BigNumberValue
/** /**
* The minimum quantity required to be purchased for this price to be applied. * The minimum quantity required to be purchased for this price to be applied.
*/ */
min_quantity?: number min_quantity?: BigNumberValue
/** /**
* The maximum quantity required to be purchased for this price to be applied. * The maximum quantity required to be purchased for this price to be applied.
*/ */
max_quantity?: number max_quantity?: BigNumberValue
/** /**
* The price set associated with the price. * The price set associated with the price.
* *
@@ -80,15 +81,15 @@ export interface UpdatePriceDTO {
/** /**
* The amount of this price. * The amount of this price.
*/ */
amount?: number amount?: BigNumberInput
/** /**
* The minimum quantity required to be purchased for this price to be applied. * The minimum quantity required to be purchased for this price to be applied.
*/ */
min_quantity?: number min_quantity?: BigNumberInput
/** /**
* The maximum quantity required to be purchased for this price to be applied. * The maximum quantity required to be purchased for this price to be applied.
*/ */
max_quantity?: number max_quantity?: BigNumberInput
} }
export interface CreatePriceDTO { export interface CreatePriceDTO {
@@ -104,15 +105,15 @@ export interface CreatePriceDTO {
/** /**
* The amount of this price. * The amount of this price.
*/ */
amount: number amount: BigNumberInput
/** /**
* The minimum quantity required to be purchased for this price to be applied. * The minimum quantity required to be purchased for this price to be applied.
*/ */
min_quantity?: number | null min_quantity?: BigNumberInput | null
/** /**
* The maximum quantity required to be purchased for this price to be applied. * The maximum quantity required to be purchased for this price to be applied.
*/ */
max_quantity?: number | null max_quantity?: BigNumberInput | null
} }
/** /**
+2
View File
@@ -6,3 +6,5 @@ export type BigNumberRawValue = {
} }
export type BigNumberInput = BigNumberRawValue | number | string | BigNumberJS export type BigNumberInput = BigNumberRawValue | number | string | BigNumberJS
export type BigNumberValue = BigNumberJS | number | string
+26 -1
View File
@@ -1,2 +1,27 @@
export * from "./big-number"; import { BigNumberValue } from "./big-number"
export * from "./big-number"
export type CartLikeWithTotals = {
original_item_subtotal: BigNumberValue
original_item_tax_total: BigNumberValue
item_total: BigNumberValue
item_subtotal: BigNumberValue
item_tax_total: BigNumberValue
original_total: BigNumberValue
original_subtotal: BigNumberValue
original_tax_total: BigNumberValue
total: BigNumberValue
subtotal: BigNumberValue
tax_total: BigNumberValue
discount_total: BigNumberValue
discount_tax_total: BigNumberValue
gift_card_total: BigNumberValue
gift_card_tax_total: BigNumberValue
shipping_total: BigNumberValue
shipping_subtotal: BigNumberValue
shipping_tax_total: BigNumberValue
original_shipping_total: BigNumberValue
original_shipping_subtotal: BigNumberValue
original_shipping_tax_total: BigNumberValue
}
@@ -0,0 +1,637 @@
import { decorateCartTotals } from "../../totals"
describe("Total calculation", function () {
it("should calculate carts with items + taxes", function () {
const cart = {
items: [
{
unit_price: 30,
quantity: 2,
tax_lines: [
{
rate: 10,
},
],
},
{
unit_price: 5,
quantity: 1,
tax_lines: [
{
rate: 50,
},
],
},
],
}
const serialized = JSON.parse(JSON.stringify(decorateCartTotals(cart)))
expect(serialized).toEqual({
items: [
{
unit_price: 30,
quantity: 2,
tax_lines: [
{
rate: 10,
total: 6,
subtotal: 6,
},
],
subtotal: 60,
total: 66,
original_total: 66,
discount_total: 0,
discount_tax_total: 0,
tax_total: 6,
original_tax_total: 6,
},
{
unit_price: 5,
quantity: 1,
tax_lines: [
{
rate: 50,
total: 2.5,
subtotal: 2.5,
},
],
subtotal: 5,
total: 7.5,
original_total: 7.5,
discount_total: 0,
discount_tax_total: 0,
tax_total: 2.5,
original_tax_total: 2.5,
},
],
total: 73.5,
subtotal: 65,
tax_total: 8.5,
discount_total: 0,
discount_tax_total: 0,
item_total: 73.5,
item_subtotal: 65,
item_tax_total: 8.5,
original_total: 73.5,
original_tax_total: 8.5,
original_item_subtotal: 65,
original_item_total: 73.5,
original_item_tax_total: 8.5,
})
})
it("should calculate carts with items + taxes + adjustments", function () {
const cart = {
items: [
{
unit_price: 50,
quantity: 2,
tax_lines: [
{
rate: 10,
},
],
adjustments: [
{
amount: 10,
},
],
},
],
}
const serialized = JSON.parse(JSON.stringify(decorateCartTotals(cart)))
expect(serialized).toEqual({
items: [
{
unit_price: 50,
quantity: 2,
tax_lines: [
{
rate: 10,
total: 9,
subtotal: 10,
},
],
adjustments: [
{
amount: 10,
total: 11,
subtotal: 10,
},
],
subtotal: 100,
total: 99,
original_total: 110,
discount_total: 10,
discount_tax_total: 1,
tax_total: 9,
original_tax_total: 10,
},
],
total: 99,
subtotal: 100,
tax_total: 9,
discount_total: 10,
discount_tax_total: 1,
original_total: 100,
original_tax_total: 10,
item_total: 99,
item_subtotal: 100,
item_tax_total: 9,
original_item_total: 110,
original_item_subtotal: 100,
original_item_tax_total: 10,
})
})
it("should calculate carts with shipping_methods + items + taxes + discounts with is_tax_inclusive", function () {
const cartMixed = {
items: [
{
unit_price: 100,
quantity: 1,
is_tax_inclusive: true,
tax_lines: [
{
rate: 10,
},
],
adjustments: [
{
amount: 10,
},
],
},
{
unit_price: 10,
quantity: 1,
is_tax_inclusive: false,
tax_lines: [
{
rate: 10,
},
],
adjustments: [
{
amount: 3,
},
],
},
],
shipping_methods: [
{
amount: 10,
is_tax_inclusive: true,
tax_lines: [
{
rate: 5,
},
],
adjustments: [
{
amount: 2,
},
],
},
{
amount: 5,
is_tax_inclusive: false,
tax_lines: [
{
rate: 10,
},
],
adjustments: [
{
amount: 2,
},
],
},
],
}
const serializedMixed = JSON.parse(
JSON.stringify(decorateCartTotals(cartMixed))
)
expect(serializedMixed).toEqual({
items: [
{
unit_price: 100,
quantity: 1,
is_tax_inclusive: true,
tax_lines: [
{
rate: 10,
total: 8.181818181818182,
subtotal: 9.090909090909092,
},
],
adjustments: [
{
amount: 10,
subtotal: 9.090909090909092,
total: 10,
},
],
subtotal: 90.9090909090909,
total: 90,
original_total: 100,
discount_total: 10,
discount_tax_total: 1,
tax_total: 8.181818181818182,
original_tax_total: 9.090909090909092,
},
{
unit_price: 10,
quantity: 1,
is_tax_inclusive: false,
tax_lines: [
{
rate: 10,
total: 0.7,
subtotal: 1,
},
],
adjustments: [
{
amount: 3,
subtotal: 3,
total: 3.3,
},
],
subtotal: 10,
total: 7.7,
original_total: 11,
discount_total: 3,
discount_tax_total: 0.3,
tax_total: 0.7,
original_tax_total: 1,
},
],
shipping_methods: [
{
amount: 10,
is_tax_inclusive: true,
tax_lines: [
{
rate: 5,
total: 0.38095238095238093,
subtotal: 0.47619047619047616,
},
],
adjustments: [
{
amount: 2,
subtotal: 1.9047619047619047,
total: 2,
},
],
subtotal: 10.380952380952381,
total: 8,
original_total: 10,
discount_total: 2,
discount_tax_total: 0.1,
tax_total: 0.38095238095238093,
original_tax_total: 0.47619047619047616,
},
{
amount: 5,
is_tax_inclusive: false,
tax_lines: [
{
rate: 10,
total: 0.3,
subtotal: 0.5,
},
],
adjustments: [
{
amount: 2,
subtotal: 2,
total: 2.2,
},
],
subtotal: 5,
total: 3.3,
original_total: 5.5,
discount_total: 2,
discount_tax_total: 0.2,
tax_total: 0.3,
original_tax_total: 0.5,
},
],
total: 104.77186147186147,
subtotal: 100.9090909090909,
tax_total: 9.562770562770563,
discount_total: 17,
discount_tax_total: 1.6,
original_total: 110.47619047619048,
original_tax_total: 11.067099567099566,
item_total: 97.7,
item_subtotal: 100.9090909090909,
item_tax_total: 8.881818181818181,
original_item_total: 111,
original_item_subtotal: 100.9090909090909,
original_item_tax_total: 10.090909090909092,
shipping_total: 11.3,
shipping_subtotal: 15.380952380952381,
shipping_tax_total: 0.680952380952381,
original_shipping_tax_total: 0.9761904761904762,
original_shipping_tax_subtotal: 15.380952380952381,
original_shipping_total: 15.5,
})
})
it("should calculate carts with items + taxes with is_tax_inclusive", function () {
const cartWithTax = {
items: [
{
unit_price: 50,
quantity: 2,
is_tax_inclusive: true,
tax_lines: [
{
rate: 10,
},
],
},
],
}
const cartWithoutTax = {
items: [
{
unit_price: 50,
quantity: 2,
is_tax_inclusive: false,
tax_lines: [
{
rate: 10,
},
],
},
],
}
const cartMixed = {
items: [...cartWithTax.items, ...cartWithoutTax.items],
}
const serializedWith = JSON.parse(
JSON.stringify(decorateCartTotals(cartWithTax))
)
const serializedWithout = JSON.parse(
JSON.stringify(decorateCartTotals(cartWithoutTax))
)
const serializedMixed = JSON.parse(
JSON.stringify(decorateCartTotals(cartMixed))
)
expect(serializedWith).toEqual({
items: [
{
unit_price: 50,
quantity: 2,
is_tax_inclusive: true,
tax_lines: [
{
rate: 10,
total: 9.090909090909092,
subtotal: 9.090909090909092,
},
],
subtotal: 90.9090909090909,
total: 100,
original_total: 100,
discount_total: 0,
discount_tax_total: 0,
tax_total: 9.090909090909092,
original_tax_total: 9.090909090909092,
},
],
total: 100,
subtotal: 90.9090909090909,
tax_total: 9.090909090909092,
discount_total: 0,
discount_tax_total: 0,
original_total: 100,
original_tax_total: 9.090909090909092,
item_total: 100,
item_subtotal: 90.9090909090909,
item_tax_total: 9.090909090909092,
original_item_total: 100,
original_item_subtotal: 90.9090909090909,
original_item_tax_total: 9.090909090909092,
})
expect(serializedWithout).toEqual({
items: [
{
unit_price: 50,
quantity: 2,
is_tax_inclusive: false,
tax_lines: [
{
rate: 10,
total: 10,
subtotal: 10,
},
],
subtotal: 100,
total: 110,
original_total: 110,
discount_total: 0,
discount_tax_total: 0,
tax_total: 10,
original_tax_total: 10,
},
],
total: 110,
subtotal: 100,
tax_total: 10,
discount_total: 0,
discount_tax_total: 0,
original_total: 110,
original_tax_total: 10,
item_total: 110,
item_subtotal: 100,
item_tax_total: 10,
original_item_total: 110,
original_item_subtotal: 100,
original_item_tax_total: 10,
})
expect(serializedMixed).toEqual({
items: [
{
unit_price: 50,
quantity: 2,
is_tax_inclusive: true,
tax_lines: [
{
rate: 10,
total: 9.090909090909092,
subtotal: 9.090909090909092,
},
],
subtotal: 90.9090909090909,
total: 100,
original_total: 100,
discount_total: 0,
discount_tax_total: 0,
tax_total: 9.090909090909092,
original_tax_total: 9.090909090909092,
},
{
unit_price: 50,
quantity: 2,
is_tax_inclusive: false,
tax_lines: [
{
rate: 10,
total: 10,
subtotal: 10,
},
],
subtotal: 100,
total: 110,
original_total: 110,
discount_total: 0,
discount_tax_total: 0,
tax_total: 10,
original_tax_total: 10,
},
],
total: 210,
subtotal: 190.9090909090909,
tax_total: 19.09090909090909,
discount_total: 0,
discount_tax_total: 0,
original_total: 210,
original_tax_total: 19.09090909090909,
item_total: 210,
item_subtotal: 190.9090909090909,
item_tax_total: 19.09090909090909,
original_item_total: 210,
original_item_subtotal: 190.9090909090909,
original_item_tax_total: 19.09090909090909,
})
})
it("should calculate carts with items + taxes + adjustments + shipping methods", function () {
const cart = {
items: [
{
unit_price: 50,
quantity: 2,
tax_lines: [
{
rate: 10,
},
],
adjustments: [
{
amount: 20,
},
],
},
],
shipping_methods: [
{
amount: 25,
tax_lines: [
{
rate: 10,
},
],
adjustments: [
{
amount: 2,
},
],
},
],
}
const serialized = JSON.parse(JSON.stringify(decorateCartTotals(cart)))
expect(serialized).toEqual({
items: [
{
unit_price: 50,
quantity: 2,
tax_lines: [
{
rate: 10,
total: 8,
subtotal: 10,
},
],
adjustments: [
{
amount: 20,
total: 22,
subtotal: 20,
},
],
subtotal: 100,
total: 88,
original_total: 110,
discount_total: 20,
discount_tax_total: 2,
tax_total: 8,
original_tax_total: 10,
},
],
shipping_methods: [
{
amount: 25,
tax_lines: [
{
rate: 10,
total: 2.3,
subtotal: 2.5,
},
],
adjustments: [
{
amount: 2,
total: 2.2,
subtotal: 2,
},
],
subtotal: 25,
total: 25.3,
original_total: 27.5,
discount_total: 2,
discount_tax_total: 0.2,
tax_total: 2.3,
original_tax_total: 2.5,
},
],
total: 113.6,
subtotal: 100,
tax_total: 10.3,
discount_total: 22,
discount_tax_total: 2.2,
original_total: 118,
original_tax_total: 12.5,
item_total: 88,
item_subtotal: 100,
item_tax_total: 8,
original_item_total: 110,
original_item_subtotal: 100,
original_item_tax_total: 10,
shipping_total: 25.3,
shipping_subtotal: 25,
shipping_tax_total: 2.3,
original_shipping_tax_total: 2.5,
original_shipping_tax_subtotal: 25,
original_shipping_total: 27.5,
})
})
})
@@ -0,0 +1,40 @@
import { AdjustmentLineDTO, BigNumberInput } from "@medusajs/types"
import { isDefined } from "../../common"
import { BigNumber } from "../big-number"
import { MathBN } from "../math"
export function calculateAdjustmentTotal({
adjustments,
includesTax,
taxRate,
}: {
adjustments: Pick<AdjustmentLineDTO, "amount">[]
includesTax?: boolean
taxRate?: BigNumberInput
}) {
let total = MathBN.convert(0)
for (const adj of adjustments) {
if (!isDefined(adj.amount)) {
continue
}
total = MathBN.add(total, adj.amount)
if (isDefined(taxRate)) {
const rate = MathBN.div(taxRate, 100)
let taxAmount = MathBN.mult(adj.amount, rate)
if (includesTax) {
taxAmount = MathBN.div(taxAmount, MathBN.add(1, rate))
adj["subtotal"] = new BigNumber(MathBN.sub(adj.amount, taxAmount))
adj["total"] = new BigNumber(adj.amount)
} else {
adj["subtotal"] = new BigNumber(adj.amount)
adj["total"] = new BigNumber(MathBN.add(adj.amount, taxAmount))
}
}
}
return total
}
+1 -1
View File
@@ -119,6 +119,6 @@ export class BigNumber {
} }
valueOf() { valueOf() {
return this.bignumber_ return this.numeric_
} }
} }
+227
View File
@@ -0,0 +1,227 @@
import { BigNumberInput, CartLikeWithTotals } from "@medusajs/types"
import { BigNumber } from "../big-number"
import { GetItemTotalInput, getLineItemsTotals } from "../line-item"
import { MathBN } from "../math"
import {
GetShippingMethodTotalInput,
getShippingMethodsTotals,
} from "../shipping-method"
import { transformPropertiesToBigNumber } from "../transform-properties-to-bignumber"
interface TotalsConfig {
includeTaxes?: boolean
}
export interface DecorateCartLikeInputDTO {
items?: {
id?: string
unit_price: BigNumberInput
quantity: BigNumberInput
adjustments?: { amount: BigNumberInput }[]
tax_lines?: {
rate: BigNumberInput
is_tax_inclusive?: boolean
}[]
}[]
shipping_methods?: {
id?: string
amount: BigNumberInput
adjustments?: { amount: BigNumberInput }[]
tax_lines?: {
rate: BigNumberInput
is_tax_inclusive?: boolean
}[]
}[]
region?: {
automatic_taxes?: boolean
}
}
export function decorateCartTotals(
cartLike: DecorateCartLikeInputDTO,
config: TotalsConfig = {}
): CartLikeWithTotals {
transformPropertiesToBigNumber(cartLike)
const items = (cartLike.items ?? []) as unknown as GetItemTotalInput[]
const shippingMethods = (cartLike.shipping_methods ??
[]) as unknown as GetShippingMethodTotalInput[]
const includeTax = config?.includeTaxes || cartLike.region?.automatic_taxes
const itemsTotals = getLineItemsTotals(items, {
includeTax,
})
const shippingMethodsTotals = getShippingMethodsTotals(shippingMethods, {
includeTax,
})
let subtotal = MathBN.convert(0)
let discountTotal = MathBN.convert(0)
let discountTaxTotal = MathBN.convert(0)
let itemsSubtotal = MathBN.convert(0)
let itemsSubtotalWithoutTaxes = MathBN.convert(0)
let itemsTotal = MathBN.convert(0)
let itemsOriginalTotal = MathBN.convert(0)
let itemsOriginalSubtotal = MathBN.convert(0)
let itemsTaxTotal = MathBN.convert(0)
let itemsOriginalTaxTotal = MathBN.convert(0)
let shippingSubtotal = MathBN.convert(0)
let shippingTotal = MathBN.convert(0)
let shippingOriginalTotal = MathBN.convert(0)
let shippingOriginalSubtotal = MathBN.convert(0)
let shippingTaxTotal = MathBN.convert(0)
let shippingOriginalTaxTotal = MathBN.convert(0)
let shippingOriginalTaxSubtotal = MathBN.convert(0)
const cartItems = items.map((item, index) => {
const itemTotals = Object.assign(item, itemsTotals[item.id ?? index] ?? {})
const itemSubtotal = itemTotals.subtotal
const itemTotal = MathBN.convert(itemTotals.total)
const itemOriginalTotal = MathBN.convert(itemTotals.original_total)
const itemTaxTotal = MathBN.convert(itemTotals.tax_total)
const itemOriginalTaxTotal = MathBN.convert(itemTotals.original_tax_total)
const itemDiscountTotal = MathBN.convert(itemTotals.discount_total)
const itemDiscountTaxTotal = MathBN.convert(itemTotals.discount_tax_total)
subtotal = MathBN.add(subtotal, itemSubtotal)
discountTotal = MathBN.add(discountTotal, itemDiscountTotal)
discountTaxTotal = MathBN.add(discountTaxTotal, itemDiscountTaxTotal)
itemsTotal = MathBN.add(itemsTotal, itemTotal)
itemsOriginalTotal = MathBN.add(itemsOriginalTotal, itemOriginalTotal)
itemsOriginalSubtotal = MathBN.add(itemsOriginalSubtotal, itemSubtotal)
itemsSubtotal = MathBN.add(itemsSubtotal, itemSubtotal)
itemsTaxTotal = MathBN.add(itemsTaxTotal, itemTaxTotal)
itemsOriginalTaxTotal = MathBN.add(
itemsOriginalTaxTotal,
itemOriginalTaxTotal
)
return itemTotals
})
const cartShippingMethods = shippingMethods.map((shippingMethod, index) => {
const methodTotals = Object.assign(
shippingMethod,
shippingMethodsTotals[shippingMethod.id ?? index] ?? {}
)
const methodSubtotal = MathBN.convert(methodTotals.subtotal)
const methodTotal = MathBN.convert(methodTotals.total)
const methodOriginalTotal = MathBN.convert(methodTotals.original_total)
const methodTaxTotal = MathBN.convert(methodTotals.tax_total)
const methodOriginalTaxTotal = MathBN.convert(
methodTotals.original_tax_total
)
const methodDiscountTotal = MathBN.convert(methodTotals.discount_total)
const methodDiscountTaxTotal = MathBN.convert(
methodTotals.discount_tax_total
)
shippingSubtotal = MathBN.add(shippingSubtotal, methodSubtotal)
shippingTotal = MathBN.add(shippingTotal, methodTotal)
shippingOriginalTotal = MathBN.add(
shippingOriginalTotal,
methodOriginalTotal
)
shippingOriginalSubtotal = MathBN.add(
shippingOriginalSubtotal,
methodSubtotal
)
shippingTaxTotal = MathBN.add(shippingTaxTotal, methodTaxTotal)
shippingOriginalTaxTotal = MathBN.add(
shippingOriginalTaxTotal,
methodOriginalTaxTotal
)
shippingOriginalTaxSubtotal = MathBN.add(
shippingOriginalTaxSubtotal,
methodSubtotal
)
discountTotal = MathBN.add(discountTotal, methodDiscountTotal)
discountTaxTotal = MathBN.add(discountTaxTotal, methodDiscountTaxTotal)
return methodTotals
})
const taxTotal = MathBN.add(itemsTaxTotal, shippingTaxTotal)
const originalTaxTotal = MathBN.add(
itemsOriginalTaxTotal,
shippingOriginalTaxTotal
)
// TODO: Gift Card calculations
const originalTempTotal = MathBN.add(
subtotal,
shippingOriginalTotal,
originalTaxTotal
)
const originalTotal = MathBN.sub(originalTempTotal, discountTotal)
// TODO: subtract (cart.gift_card_total + cart.gift_card_tax_total)
const tempTotal = MathBN.add(subtotal, shippingTotal, taxTotal)
const total = MathBN.sub(tempTotal, discountTotal)
const cart = { ...cartLike } as any
cart.total = new BigNumber(total)
cart.subtotal = new BigNumber(subtotal)
cart.tax_total = new BigNumber(taxTotal)
cart.discount_total = new BigNumber(discountTotal)
cart.discount_tax_total = new BigNumber(discountTaxTotal)
// cart.gift_card_total = giftCardTotal.total || 0
// cart.gift_card_tax_total = giftCardTotal.tax_total || 0
cart.original_total = new BigNumber(originalTotal)
cart.original_tax_total = new BigNumber(originalTaxTotal)
// cart.original_gift_card_total =
// cart.original_gift_card_tax_total =
if (cartLike.items) {
cart.items = cartItems
cart.item_total = new BigNumber(itemsTotal)
cart.item_subtotal = new BigNumber(itemsSubtotal)
cart.item_tax_total = new BigNumber(itemsTaxTotal)
cart.original_item_total = new BigNumber(itemsOriginalTotal)
cart.original_item_subtotal = new BigNumber(itemsOriginalSubtotal)
cart.original_item_tax_total = new BigNumber(itemsOriginalTaxTotal)
}
if (cart.shipping_methods) {
cart.shipping_methods = cartShippingMethods
cart.shipping_total = new BigNumber(shippingTotal)
cart.shipping_subtotal = new BigNumber(shippingSubtotal)
cart.shipping_tax_total = new BigNumber(shippingTaxTotal)
cart.original_shipping_tax_total = new BigNumber(shippingOriginalTaxTotal)
cart.original_shipping_tax_subtotal = new BigNumber(
shippingOriginalTaxSubtotal
)
cart.original_shipping_total = new BigNumber(shippingOriginalTotal)
}
return cart
}
+3 -237
View File
@@ -1,240 +1,6 @@
import { export * from "./cart"
BigNumberRawValue, export * from "./line-item"
CartDTO,
CartShippingMethodDTO,
} from "@medusajs/types"
import { BigNumber as BigNumberJs } from "bignumber.js"
import { BigNumber } from "./big-number"
import { toBigNumberJs } from "./to-big-number-js"
export * from "./math" export * from "./math"
export * from "./promotion" export * from "./promotion"
export * from "./to-big-number-js" export * from "./shipping-method"
export * from "./transform-properties-to-bignumber" export * from "./transform-properties-to-bignumber"
type GetLineItemTotalsContext = {
includeTax?: boolean
taxRate?: number | null
}
interface GetShippingMethodTotalInput extends CartShippingMethodDTO {
raw_amount: BigNumberRawValue
}
interface GetItemTotalInput {
id: string
unit_price: BigNumber
quantity: number
is_tax_inclusive?: boolean
tax_total?: BigNumber
original_tax_total?: BigNumber
}
interface GetItemTotalOutput {
quantity: number
unit_price: BigNumber
subtotal: BigNumber
total: BigNumber
original_total: BigNumber
discount_total: BigNumber
tax_total: BigNumber
original_tax_total: BigNumber
}
export function getShippingMethodTotals(
shippingMethods: GetShippingMethodTotalInput[],
context: { includeTax?: boolean }
) {
const { includeTax } = context
const shippingMethodsTotals = {}
for (const shippingMethod of shippingMethods) {
shippingMethodsTotals[shippingMethod.id] = getShippingMethodTotals_(
shippingMethod,
{
includeTax,
}
)
}
return shippingMethodsTotals
}
export function getShippingMethodTotals_(
shippingMethod: GetShippingMethodTotalInput,
context: { includeTax?: boolean }
) {
const { amount, taxTotal, originalTaxTotal } = toBigNumberJs(shippingMethod, [
"amount",
"tax_total",
"original_tax_total",
])
const amountBn = new BigNumber(amount)
const totals = {
amount: amountBn,
total: amountBn,
original_total: amountBn,
subtotal: amountBn,
tax_total: new BigNumber(taxTotal),
original_tax_total: new BigNumber(originalTaxTotal),
}
const isTaxInclusive = context.includeTax ?? shippingMethod.is_tax_inclusive
if (isTaxInclusive) {
const subtotal = amount.minus(taxTotal)
totals.subtotal = new BigNumber(subtotal)
} else {
const originalTotal = amount.plus(originalTaxTotal)
const total = amount.plus(taxTotal)
totals.original_total = new BigNumber(originalTotal)
totals.total = new BigNumber(total)
}
return totals
}
export function getLineItemTotals(
items: GetItemTotalInput[],
context: GetLineItemTotalsContext
): { [itemId: string]: GetItemTotalOutput } {
const itemsTotals: { [itemId: string]: GetItemTotalOutput } = {}
for (const item of items) {
itemsTotals[item.id] = getTotalsForSingleLineItem(item, {
includeTax: context.includeTax,
})
}
return itemsTotals
}
function getTotalsForSingleLineItem(
item: GetItemTotalInput,
context: GetLineItemTotalsContext
) {
const { unitPrice, taxTotal, originalTaxTotal } = toBigNumberJs(item, [
"unit_price",
"tax_total",
"original_tax_total",
])
const subtotal = unitPrice.times(item.quantity)
const discountTotal = BigNumberJs(0)
const total = subtotal.minus(discountTotal)
const totals: GetItemTotalOutput = {
quantity: item.quantity,
unit_price: item.unit_price,
subtotal: new BigNumber(subtotal),
total: new BigNumber(total),
original_total: new BigNumber(subtotal),
discount_total: new BigNumber(discountTotal),
tax_total: new BigNumber(taxTotal),
original_tax_total: new BigNumber(originalTaxTotal),
}
const isTaxInclusive = context.includeTax ?? item.is_tax_inclusive
if (isTaxInclusive) {
const subtotal = unitPrice.times(totals.quantity).minus(originalTaxTotal)
const subtotalBn = new BigNumber(subtotal)
totals.subtotal = subtotalBn
totals.total = subtotalBn
totals.original_total = subtotalBn
} else {
const newTotal = total.plus(taxTotal)
const originalTotal = subtotal.plus(originalTaxTotal)
totals.total = new BigNumber(newTotal)
totals.original_total = new BigNumber(originalTotal)
}
return totals
}
export function decorateCartTotals(
{
shippingMethods = [],
items = [],
}: {
items: GetItemTotalInput[]
shippingMethods: GetShippingMethodTotalInput[]
},
totalsConfig: { includeTaxes?: boolean } = {}
): CartDTO {
let cart: any = {}
const includeTax = totalsConfig?.includeTaxes
const itemsTotals = getLineItemTotals(items, {
includeTax,
})
const shippingMethodsTotals = getShippingMethodTotals(shippingMethods, {
includeTax,
})
const subtotal = BigNumberJs(0)
const discountTotal = BigNumberJs(0)
const itemTaxTotal = BigNumberJs(0)
const shippingTotal = BigNumberJs(0)
const shippingTaxTotal = BigNumberJs(0)
cart.items = items.map((item) => {
const itemTotals = Object.assign(item, itemsTotals[item.id] ?? {})
const subtotal = BigNumberJs(itemTotals.subtotal.raw!.value)
const discountTotal = BigNumberJs(itemTotals.discount_total.raw!.value)
const itemTaxTotal = BigNumberJs(itemTotals.tax_total.raw!.value)
subtotal.plus(subtotal)
discountTotal.plus(discountTotal)
itemTaxTotal.plus(itemTaxTotal)
return itemTotals
})
cart.shipping_methods = shippingMethods.map((shippingMethod) => {
const methodTotals = Object.assign(
shippingMethod,
shippingMethodsTotals[shippingMethod.id] ?? {}
)
const total = BigNumberJs(methodTotals.total.raw!.value)
const methodTaxTotal = BigNumberJs(methodTotals.tax_total.raw!.value)
shippingTotal.plus(total)
shippingTaxTotal.plus(methodTaxTotal)
return methodTotals
})
const taxTotal = itemTaxTotal.plus(shippingTaxTotal)
// TODO: Discount + Gift Card calculations
// TODO: subtract (cart.gift_card_total + cart.discount_total + cart.gift_card_tax_total)
const total = subtotal.plus(shippingTotal).plus(shippingTotal).plus(taxTotal)
cart.total = new BigNumber(total)
cart.subtotal = new BigNumber(subtotal)
cart.discount_total = new BigNumber(discountTotal)
cart.item_tax_total = new BigNumber(itemTaxTotal)
cart.shipping_total = new BigNumber(shippingTotal)
cart.shipping_tax_total = new BigNumber(shippingTaxTotal)
cart.tax_total = new BigNumber(taxTotal)
// cart.discount_total = Math.round(cart.discount_total)
// cart.gift_card_total = giftCardTotal.total || 0
// cart.gift_card_tax_total = giftCardTotal.tax_total || 0
return cart as CartDTO
}
@@ -0,0 +1,126 @@
import { AdjustmentLineDTO, TaxLineDTO } from "@medusajs/types"
import { calculateAdjustmentTotal } from "../adjustment"
import { BigNumber } from "../big-number"
import { MathBN } from "../math"
import { calculateTaxTotal } from "../tax"
interface GetLineItemsTotalsContext {
includeTax?: boolean
}
export interface GetItemTotalInput {
id: string
unit_price: BigNumber
quantity: BigNumber
is_tax_inclusive?: boolean
tax_lines?: Pick<TaxLineDTO, "rate">[]
adjustments?: Pick<AdjustmentLineDTO, "amount">[]
}
export interface GetItemTotalOutput {
quantity: BigNumber
unit_price: BigNumber
subtotal: BigNumber
total: BigNumber
original_total: BigNumber
discount_total: BigNumber
discount_tax_total: BigNumber
tax_total: BigNumber
original_tax_total: BigNumber
}
export function getLineItemsTotals(
items: GetItemTotalInput[],
context: GetLineItemsTotalsContext
) {
const itemsTotals = {}
let index = 0
for (const item of items) {
itemsTotals[item.id ?? index] = getLineItemTotals(item, {
includeTax: context.includeTax || item.is_tax_inclusive,
})
index++
}
return itemsTotals
}
function getLineItemTotals(
item: GetItemTotalInput,
context: GetLineItemsTotalsContext
) {
const subtotal = MathBN.mult(item.unit_price, item.quantity)
const sumTaxRate = MathBN.sum(
...((item.tax_lines ?? []).map((taxLine) => taxLine.rate) ?? [])
)
const discountTotal = calculateAdjustmentTotal({
adjustments: item.adjustments || [],
includesTax: context.includeTax,
taxRate: sumTaxRate,
})
const discountTaxTotal = MathBN.mult(
discountTotal,
MathBN.div(sumTaxRate, 100)
)
const total = MathBN.sub(subtotal, discountTotal)
const totals: GetItemTotalOutput = {
quantity: item.quantity,
unit_price: item.unit_price,
subtotal: new BigNumber(subtotal),
total: new BigNumber(total),
original_total: new BigNumber(subtotal),
discount_total: new BigNumber(discountTotal),
discount_tax_total: new BigNumber(discountTaxTotal),
tax_total: new BigNumber(0),
original_tax_total: new BigNumber(0),
}
const taxableAmountWithDiscount = MathBN.sub(subtotal, discountTotal)
const taxableAmount = subtotal
const taxTotal = calculateTaxTotal({
taxLines: item.tax_lines || [],
includesTax: context.includeTax,
taxableAmount: taxableAmountWithDiscount,
setTotalField: "total",
})
totals.tax_total = new BigNumber(taxTotal)
const originalTaxTotal = calculateTaxTotal({
taxLines: item.tax_lines || [],
includesTax: context.includeTax,
taxableAmount,
setTotalField: "subtotal",
})
totals.original_tax_total = new BigNumber(originalTaxTotal)
const isTaxInclusive = context.includeTax ?? item.is_tax_inclusive
if (isTaxInclusive) {
totals.subtotal = new BigNumber(
MathBN.sub(
MathBN.mult(item.unit_price, totals.quantity),
originalTaxTotal
)
)
} else {
const newTotal = MathBN.add(total, totals.tax_total)
const originalTotal = MathBN.add(subtotal, totals.original_tax_total)
totals.total = new BigNumber(newTotal)
totals.original_total = new BigNumber(originalTotal)
}
return totals
}
+1 -1
View File
@@ -31,7 +31,7 @@ export class MathBN {
} }
static sum(...nums: BNInput[]): BigNumberJS { static sum(...nums: BNInput[]): BigNumberJS {
return MathBN.add(...nums) return MathBN.add(0, ...(nums ?? [0]))
} }
static sub(...nums: BNInput[]): BigNumberJS { static sub(...nums: BNInput[]): BigNumberJS {
+58 -1
View File
@@ -1 +1,58 @@
export * from "./totals" import {
ApplicationMethodAllocation,
ApplicationMethodType,
} from "../../promotion"
function getPromotionValueForPercentage(promotion, lineItemTotal) {
return (promotion.value / 100) * lineItemTotal
}
function getPromotionValueForFixed(promotion, lineItemTotal, lineItemsTotal) {
if (promotion.allocation === ApplicationMethodAllocation.ACROSS) {
return (lineItemTotal / lineItemsTotal) * promotion.value
}
return promotion.value
}
export function getPromotionValue(promotion, lineItemTotal, lineItemsTotal) {
if (promotion.type === ApplicationMethodType.PERCENTAGE) {
return getPromotionValueForPercentage(promotion, lineItemTotal)
}
return getPromotionValueForFixed(promotion, lineItemTotal, lineItemsTotal)
}
export function getApplicableQuantity(lineItem, maxQuantity) {
if (maxQuantity && lineItem.quantity) {
return Math.min(lineItem.quantity, maxQuantity)
}
return lineItem.quantity
}
function getLineItemUnitPrice(lineItem) {
return lineItem.subtotal / lineItem.quantity
}
export function calculateAdjustmentAmountFromPromotion(
lineItem,
promotion,
lineItemsTotal = 0
) {
const quantity = getApplicableQuantity(lineItem, promotion.max_quantity)
const lineItemTotal = getLineItemUnitPrice(lineItem) * quantity
const applicableTotal = lineItemTotal - promotion.applied_value
if (applicableTotal <= 0) {
return applicableTotal
}
const promotionValue = getPromotionValue(
promotion,
applicableTotal,
lineItemsTotal
)
return Math.min(promotionValue, applicableTotal)
}
@@ -1,58 +0,0 @@
import {
ApplicationMethodAllocation,
ApplicationMethodType,
} from "../../promotion"
function getPromotionValueForPercentage(promotion, lineItemTotal) {
return (promotion.value / 100) * lineItemTotal
}
function getPromotionValueForFixed(promotion, lineItemTotal, lineItemsTotal) {
if (promotion.allocation === ApplicationMethodAllocation.ACROSS) {
return (lineItemTotal / lineItemsTotal) * promotion.value
}
return promotion.value
}
export function getPromotionValue(promotion, lineItemTotal, lineItemsTotal) {
if (promotion.type === ApplicationMethodType.PERCENTAGE) {
return getPromotionValueForPercentage(promotion, lineItemTotal)
}
return getPromotionValueForFixed(promotion, lineItemTotal, lineItemsTotal)
}
export function getApplicableQuantity(lineItem, maxQuantity) {
if (maxQuantity && lineItem.quantity) {
return Math.min(lineItem.quantity, maxQuantity)
}
return lineItem.quantity
}
function getLineItemUnitPrice(lineItem) {
return lineItem.subtotal / lineItem.quantity
}
export function calculateAdjustmentAmountFromPromotion(
lineItem,
promotion,
lineItemsTotal = 0
) {
const quantity = getApplicableQuantity(lineItem, promotion.max_quantity)
const lineItemTotal = getLineItemUnitPrice(lineItem) * quantity
const applicableTotal = lineItemTotal - promotion.applied_value
if (applicableTotal <= 0) {
return applicableTotal
}
const promotionValue = getPromotionValue(
promotion,
applicableTotal,
lineItemsTotal
)
return Math.min(promotionValue, applicableTotal)
}
@@ -0,0 +1,132 @@
import { AdjustmentLineDTO, TaxLineDTO } from "@medusajs/types"
import { calculateAdjustmentTotal } from "../adjustment"
import { BigNumber } from "../big-number"
import { MathBN } from "../math"
import { calculateTaxTotal } from "../tax"
interface GetShippingMethodsTotalsContext {
includeTax?: boolean
}
export interface GetShippingMethodTotalInput {
id: string
amount: BigNumber
is_tax_inclusive?: boolean
tax_lines?: TaxLineDTO[]
adjustments?: Pick<AdjustmentLineDTO, "amount">[]
}
export interface GetShippingMethodTotalOutput {
amount: BigNumber
subtotal: BigNumber
total: BigNumber
original_total: BigNumber
discount_total: BigNumber
discount_tax_total: BigNumber
tax_total: BigNumber
original_tax_total: BigNumber
}
export function getShippingMethodsTotals(
shippingMethods: GetShippingMethodTotalInput[],
context: GetShippingMethodsTotalsContext
) {
const { includeTax } = context
const shippingMethodsTotals = {}
let index = 0
for (const shippingMethod of shippingMethods) {
shippingMethodsTotals[shippingMethod.id ?? index] = getShippingMethodTotals(
shippingMethod,
{
includeTax: includeTax || shippingMethod.is_tax_inclusive,
}
)
index++
}
return shippingMethodsTotals
}
export function getShippingMethodTotals(
shippingMethod: GetShippingMethodTotalInput,
context: GetShippingMethodsTotalsContext
) {
const amount = MathBN.convert(shippingMethod.amount)
const subtotal = MathBN.convert(shippingMethod.amount)
const sumTaxRate = MathBN.sum(
...(shippingMethod.tax_lines?.map((taxLine) => taxLine.rate) ?? [])
)
const discountTotal = calculateAdjustmentTotal({
adjustments: shippingMethod.adjustments || [],
includesTax: context.includeTax,
taxRate: sumTaxRate,
})
const discountTaxTotal = MathBN.mult(
discountTotal,
MathBN.div(sumTaxRate, 100)
)
const total = MathBN.sub(amount, discountTotal)
const totals: GetShippingMethodTotalOutput = {
amount: new BigNumber(amount),
subtotal: new BigNumber(subtotal),
total: new BigNumber(total),
original_total: new BigNumber(amount),
discount_total: new BigNumber(discountTotal),
discount_tax_total: new BigNumber(discountTaxTotal),
tax_total: new BigNumber(0),
original_tax_total: new BigNumber(0),
}
const taxLines = shippingMethod.tax_lines || []
const taxableAmountWithDiscount = MathBN.sub(subtotal, discountTotal)
const taxableAmount = subtotal
const taxTotal = calculateTaxTotal({
taxLines,
includesTax: context.includeTax,
taxableAmount: taxableAmountWithDiscount,
setTotalField: "total",
})
totals.tax_total = new BigNumber(taxTotal)
const originalTaxTotal = calculateTaxTotal({
taxLines,
includesTax: context.includeTax,
taxableAmount,
setTotalField: "subtotal",
})
totals.original_tax_total = new BigNumber(originalTaxTotal)
const isTaxInclusive = context.includeTax ?? shippingMethod.is_tax_inclusive
if (isTaxInclusive) {
const subtotal = MathBN.add(shippingMethod.amount, taxTotal)
totals.subtotal = new BigNumber(subtotal)
} else {
const originalTotal = MathBN.add(
shippingMethod.amount,
totals.original_tax_total
)
const total = MathBN.add(totals.total, totals.tax_total)
totals.total = new BigNumber(total)
totals.original_total = new BigNumber(originalTotal)
}
return totals
}
+33
View File
@@ -0,0 +1,33 @@
import { BigNumberInput, TaxLineDTO } from "@medusajs/types"
import { BigNumber } from "../big-number"
import { MathBN } from "../math"
export function calculateTaxTotal({
taxLines,
includesTax,
taxableAmount,
setTotalField,
}: {
taxLines: Pick<TaxLineDTO, "rate">[]
includesTax?: boolean
taxableAmount: BigNumberInput
setTotalField?: string
}) {
let taxTotal = MathBN.convert(0)
for (const taxLine of taxLines) {
const rate = MathBN.div(taxLine.rate, 100)
let taxAmount = MathBN.mult(taxableAmount, rate)
if (includesTax) {
taxAmount = MathBN.div(taxAmount, MathBN.add(1, rate))
}
if (setTotalField) {
;(taxLine as any)[setTotalField] = new BigNumber(taxAmount)
}
taxTotal = MathBN.add(taxTotal, taxAmount)
}
return taxTotal
}
@@ -1,28 +0,0 @@
import { BigNumberInput } from "@medusajs/types"
import { BigNumber as BigNumberJs } from "bignumber.js"
import { isDefined, toCamelCase } from "../common"
import { BigNumber } from "./big-number"
type InputEntity<T, V extends string> = { [key in V]?: InputEntityField }
type InputEntityField = number | string | BigNumber
type Camelize<V extends string> = V extends `${infer A}_${infer B}`
? `${A}${Camelize<Capitalize<B>>}`
: V
type Output<V extends string> = { [key in Camelize<V>]: BigNumberJs }
export function toBigNumberJs<T, V extends string>(
entity: InputEntity<T, V>,
fields: V[]
): Output<V> {
return fields.reduce((acc, field: string) => {
const camelCased = toCamelCase(field)
let val: BigNumberInput = 0
if (isDefined(entity[field])) {
const entityField = entity[field]
val = (entityField?.raw?.value ?? entityField) as number | string
}
acc[camelCased] = new BigNumberJs(val)
return acc
}, {} as Output<V>)
}