From e64823d1b9e7556d8bc12c553accefa0b2a8b714 Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Wed, 25 Oct 2023 12:29:30 +0200 Subject: [PATCH] fix(medusa): totals calculation with gift card (#5075) **What** Resolve potential discrepency between what is calculated in the `createFromCart` and the cart/order totals calculation. When the giftCard is taxable then the following calculation is applied ```ts cart.subtotal + cart.shipping_total - cart.discount_total ``` otherwise ```ts cart.subtotal + cart.shipping_total + cart.tax_total - cart.discount_total ``` Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> --- .changeset/fifty-cooks-occur.md | 5 +++ .../src/services/__mocks__/new-totals.js | 19 ++++++++++ .../medusa/src/services/__tests__/order.js | 1 + packages/medusa/src/services/cart.ts | 38 +++++++++++-------- packages/medusa/src/services/new-totals.ts | 22 ++++++++++- packages/medusa/src/services/order.ts | 28 ++++++++------ 6 files changed, 84 insertions(+), 29 deletions(-) create mode 100644 .changeset/fifty-cooks-occur.md diff --git a/.changeset/fifty-cooks-occur.md b/.changeset/fifty-cooks-occur.md new file mode 100644 index 0000000000..c79cacaeba --- /dev/null +++ b/.changeset/fifty-cooks-occur.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +fix(medusa): totals calculation with gift card diff --git a/packages/medusa/src/services/__mocks__/new-totals.js b/packages/medusa/src/services/__mocks__/new-totals.js index bae82eaf1c..bf7af612b8 100644 --- a/packages/medusa/src/services/__mocks__/new-totals.js +++ b/packages/medusa/src/services/__mocks__/new-totals.js @@ -1,3 +1,5 @@ +const NewTotalsService = require("../new-totals") + export const newTotalsServiceMock = { withTransaction: function () { return this @@ -16,6 +18,23 @@ export const newTotalsServiceMock = { getShippingMethodTotals: jest.fn().mockImplementation((order, lineItems) => { return Promise.resolve({}) }), + getGiftCardableAmount: jest + .fn() + .mockImplementation( + ({ + gift_cards_taxable, + subtotal, + shipping_total, + discount_total, + tax_total, + }) => { + return Promise.resolve( + (gift_cards_taxable + ? subtotal + shipping_total - discount_total + : subtotal + shipping_total + tax_total - discount_total) || 0 + ) + } + ), } const mock = jest.fn().mockImplementation(() => { diff --git a/packages/medusa/src/services/__tests__/order.js b/packages/medusa/src/services/__tests__/order.js index 05c5621d6a..d51d3f2fda 100644 --- a/packages/medusa/src/services/__tests__/order.js +++ b/packages/medusa/src/services/__tests__/order.js @@ -364,6 +364,7 @@ describe("OrderService", () => { subtotal: 100, total: 100, discount_total: 0, + gift_card_total: 100, } orderService.cartService_.retrieveWithTotals = () => { diff --git a/packages/medusa/src/services/cart.ts b/packages/medusa/src/services/cart.ts index 83eb8f67cc..1e9596bbaf 100644 --- a/packages/medusa/src/services/cart.ts +++ b/packages/medusa/src/services/cart.ts @@ -1,8 +1,11 @@ +import { FlagRouter, isDefined, MedusaError } from "@medusajs/utils" +import { isEmpty, isEqual } from "lodash" +import { DeepPartial, EntityManager, In, IsNull, Not } from "typeorm" import { Address, Cart, - CustomShippingOption, Customer, + CustomShippingOption, Discount, DiscountRule, DiscountRuleType, @@ -22,13 +25,13 @@ import { CartCreateProps, CartUpdateProps, FilterableCartProps, + isCart, LineItemUpdate, LineItemValidateData, - isCart, } from "../types/cart" import { - CustomShippingOptionService, CustomerService, + CustomShippingOptionService, DiscountService, EventBusService, GiftCardService, @@ -48,21 +51,16 @@ import { TaxProviderService, TotalsService, } from "." -import { DeepPartial, EntityManager, In, IsNull, Not } from "typeorm" import { IPriceSelectionStrategy, TransactionBaseService } from "../interfaces" -import { MedusaError, isDefined } from "medusa-core-utils" +import IsolateProductDomainFeatureFlag from "../loaders/feature-flags/isolate-product-domain" +import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels" import { buildQuery, isString, setMetadata } from "../utils" -import { isEmpty, isEqual } from "lodash" import { AddressRepository } from "../repositories/address" import { CartRepository } from "../repositories/cart" -import { FlagRouter } from "@medusajs/utils" -import { IsNumber } from "class-validator" -import IsolateProductDomainFeatureFlag from "../loaders/feature-flags/isolate-product-domain" import { LineItemRepository } from "../repositories/line-item" import { PaymentSessionInput } from "../types/payment" import { PaymentSessionRepository } from "../repositories/payment-session" -import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels" import { ShippingMethodRepository } from "../repositories/shipping-method" import { validateEmail } from "../utils/is-email" @@ -2753,8 +2751,21 @@ class CartService extends TransactionBaseService { } ) + cart.tax_total = cart.item_tax_total + cart.shipping_tax_total + + cart.raw_discount_total = cart.discount_total + cart.discount_total = Math.round(cart.discount_total) + + const giftCardableAmount = this.newTotalsService_.getGiftCardableAmount({ + gift_cards_taxable: cart.region?.gift_cards_taxable, + subtotal: cart.subtotal, + discount_total: cart.discount_total, + shipping_total: cart.shipping_total, + tax_total: cart.tax_total, + }) + const giftCardTotal = await this.newTotalsService_.getGiftCardTotals( - cart.subtotal - cart.discount_total, + giftCardableAmount, { region: cart.region, giftCards: cart.gift_cards, @@ -2764,11 +2775,6 @@ class CartService extends TransactionBaseService { cart.gift_card_total = giftCardTotal.total || 0 cart.gift_card_tax_total = giftCardTotal.tax_total || 0 - cart.tax_total = cart.item_tax_total + cart.shipping_tax_total - - cart.raw_discount_total = cart.discount_total - cart.discount_total = Math.round(cart.discount_total) - cart.total = cart.subtotal + cart.shipping_total + diff --git a/packages/medusa/src/services/new-totals.ts b/packages/medusa/src/services/new-totals.ts index da5c5a512c..d9baafafde 100644 --- a/packages/medusa/src/services/new-totals.ts +++ b/packages/medusa/src/services/new-totals.ts @@ -1,5 +1,5 @@ import { FlagRouter } from "@medusajs/utils" -import { MedusaError, isDefined } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" import { ITaxCalculationStrategy, @@ -635,6 +635,26 @@ export default class NewTotalsService extends TransactionBaseService { return shippingMethodsTotals } + getGiftCardableAmount({ + gift_cards_taxable, + subtotal, + shipping_total, + discount_total, + tax_total, + }: { + gift_cards_taxable?: boolean + subtotal: number + shipping_total: number + discount_total: number + tax_total: number + }): number { + return ( + (gift_cards_taxable + ? subtotal + shipping_total - discount_total + : subtotal + shipping_total + tax_total - discount_total) || 0 + ) + } + /** * Calculate and return the shipping method totals * @param shippingMethod diff --git a/packages/medusa/src/services/order.ts b/packages/medusa/src/services/order.ts index ed00c9d749..fbd2650692 100644 --- a/packages/medusa/src/services/order.ts +++ b/packages/medusa/src/services/order.ts @@ -725,12 +725,8 @@ class OrderService extends TransactionBaseService { ) } - const giftCardableAmount = - (cart.region?.gift_cards_taxable - ? cart.subtotal! - cart.discount_total! - : cart.total! + cart.gift_card_total!) || 0 // we re add the gift card total to compensate the fact that the decorate total already removed this amount from the total + let giftCardableAmountBalance = cart.gift_card_total ?? 0 - let giftCardableAmountBalance = giftCardableAmount const giftCardService = this.giftCardService_.withTransaction(manager) // Order the gift cards by first ends_at date, then remaining amount. To ensure largest possible amount left, for longest possible time. @@ -739,6 +735,7 @@ class OrderService extends TransactionBaseService { const bEnd = b.ends_at ?? new Date(2100, 1, 1) return aEnd.getTime() - bEnd.getTime() || a.balance - b.balance }) + for (const giftCard of orderedGiftCards) { const newGiftCardBalance = Math.max( 0, @@ -1898,8 +1895,20 @@ class OrderService extends TransactionBaseService { } ) + order.item_tax_total = item_tax_total + order.shipping_tax_total = shipping_tax_total + order.tax_total = item_tax_total + shipping_tax_total + + const giftCardableAmount = this.newTotalsService_.getGiftCardableAmount({ + gift_cards_taxable: order.region?.gift_cards_taxable, + subtotal: order.subtotal, + discount_total: order.discount_total, + shipping_total: order.shipping_total, + tax_total: order.tax_total, + }) + const giftCardTotal = await this.newTotalsService_.getGiftCardTotals( - order.subtotal - order.discount_total, + giftCardableAmount, { region: order.region, giftCards: order.gift_cards, @@ -1909,12 +1918,7 @@ class OrderService extends TransactionBaseService { order.gift_card_total = giftCardTotal.total || 0 order.gift_card_tax_total = giftCardTotal.tax_total || 0 - order.item_tax_total = item_tax_total - order.shipping_tax_total = shipping_tax_total - order.tax_total = - order.item_tax_total + - order.shipping_tax_total - - order.gift_card_tax_total + order.tax_total -= order.gift_card_tax_total for (const swap of order.swaps ?? []) { swap.additional_items = swap.additional_items.map((item) => {