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>
This commit is contained in:
Adrien de Peretti
2023-10-25 12:29:30 +02:00
committed by GitHub
parent 8bb8eb530b
commit e64823d1b9
6 changed files with 84 additions and 29 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
fix(medusa): totals calculation with gift card

View File

@@ -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(() => {

View File

@@ -364,6 +364,7 @@ describe("OrderService", () => {
subtotal: 100,
total: 100,
discount_total: 0,
gift_card_total: 100,
}
orderService.cartService_.retrieveWithTotals = () => {

View File

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

View File

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

View File

@@ -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) => {