fix(medusa): Double tax issue on return refund amount (#4899)

Closes #4686 

Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Josip Matić
2023-09-05 10:14:45 +02:00
committed by GitHub
parent 1e3c93a319
commit bb5ea9d5ca
6 changed files with 316 additions and 3 deletions
@@ -147,6 +147,14 @@ export const ShippingOptionServiceMock = {
return Promise.resolve({ _id: methodId })
}),
delete: jest.fn().mockReturnValue(Promise.resolve()),
createShippingMethod: jest.fn().mockImplementation((optionId, data, config) => {
return Promise.resolve({
...config,
id: "test-shipping-method",
shipping_option_id: optionId,
data,
})
}),
}
const mock = jest.fn().mockImplementation(() => {
@@ -1,7 +1,13 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import { FlagRouter } from "@medusajs/utils"
import idMap from "medusa-test-utils/dist/id-map"
import ReturnService from "../return"
import { ProductVariantInventoryServiceMock } from "../__mocks__/product-variant-inventory"
import { ShippingOptionServiceMock } from "../__mocks__/shipping-option"
import TaxInclusivePricingFeatureFlag from "../../loaders/feature-flags/tax-inclusive-pricing"
describe("ReturnService", () => {
describe("receive", () => {
const returnRepository = MockRepository({
@@ -334,4 +340,161 @@ describe("ReturnService", () => {
).rejects.toThrow("Cannot update a canceled return")
})
})
describe("create", () => {
const returnRepository = MockRepository({
findOne: (query) => {
switch (query.where.id) {
case IdMap.getId("test-return"):
return Promise.resolve({
status: "canceled",
})
default:
return Promise.resolve({})
}
},
create: (data) => data,
save: (data) => data,
})
const returnItemRepository = MockRepository({
create: (data) => data,
})
const totalsService = {
getTotal: jest.fn().mockImplementation((cart) => {
return 1000
}),
getRefundTotal: jest.fn().mockImplementation((order, lineItems) => {
return 100
}),
getCalculationContext: jest
.fn()
.mockImplementation((order, lineItems) => {
return Promise.resolve({})
}),
}
const orderService = {
retrieve: jest.fn().mockImplementation(() => {
return Promise.resolve({
items: [
{
id: IdMap.getId("test-line"),
quantity: 10,
returned_quantity: 0,
variant_id: "test-variant",
},
{
id: IdMap.getId("test-line-2"),
quantity: 10,
returned_quantity: 0,
variant_id: "test-variant-2",
},
],
payments: [{ id: "payment_test" }],
})
}),
withTransaction: function () {
return this
},
}
const lineItemService = {
retrieve: jest.fn().mockImplementation((data) => {
return Promise.resolve({ ...data, returned_quantity: 0 })
}),
update: jest.fn(),
withTransaction: function () {
return this
},
}
const returnReasonService = {
withTransaction: function () {
return this
},
list: jest.fn().mockImplementation(() => {
return Promise.resolve([
{
id: IdMap.getId("test-return-reason"),
value: "test-return-reason",
label: "Test Return Reason",
description: null,
parent_return_reason_id: null,
return_reason_children: [],
metadata: {},
},
])
}, {}),
}
const shippingOptionService = ShippingOptionServiceMock
const taxProviderService = {
withTransaction: function () {
return this
},
createShippingTaxLines: jest.fn().mockImplementation((shippingMethod) => {
return Promise.resolve([
{
rate: 25,
},
])
}),
}
const featureFlagRouter = new FlagRouter({
[TaxInclusivePricingFeatureFlag.key]: false,
})
const returnService = new ReturnService({
manager: MockManager,
lineItemService,
orderService,
totalsService,
returnReasonService,
returnRepository,
returnItemRepository,
shippingOptionService,
taxProviderService,
featureFlagRouter,
})
beforeEach(async () => {
jest.clearAllMocks()
})
it("successfully creates a return", async () => {
await returnService.create({
order_id: IdMap.getId("test-order"),
items: [
{
item_id: IdMap.getId("test-line"),
quantity: 10,
},
],
shipping_method: {
option_id: "taxincl-option",
price: 80,
},
})
expect(returnRepository.save).toHaveBeenCalledTimes(2)
expect(returnRepository.save).toHaveBeenCalledWith({
order_id: IdMap.getId("test-order"),
items: [
{
item_id: IdMap.getId("test-line"),
quantity: 10,
metadata: undefined,
note: undefined,
reason_id: undefined,
requested_quantity: 10,
},
],
status: "requested",
refund_amount: 0,
})
})
})
})
+31 -3
View File
@@ -21,7 +21,9 @@ import {
ReturnStatus,
} from "../models"
import { MedusaError, isDefined } from "medusa-core-utils"
import { buildQuery, setMetadata } from "../utils"
import { FlagRouter } from "@medusajs/utils"
import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing"
import { buildQuery, setMetadata, calculatePriceTaxAmount } from "../utils"
import { OrdersReturnItem } from "../types/orders"
import { ReturnItemRepository } from "../repositories/return-item"
@@ -40,6 +42,7 @@ type InjectedDependencies = {
fulfillmentProviderService: FulfillmentProviderService
orderService: OrderService
productVariantInventoryService: ProductVariantInventoryService
featureFlagRouter: FlagRouter
}
type Transformer = (
@@ -60,6 +63,7 @@ class ReturnService extends TransactionBaseService {
protected readonly orderService_: OrderService
// eslint-disable-next-line
protected readonly productVariantInventoryService_: ProductVariantInventoryService
protected readonly featureFlagRouter_: FlagRouter
constructor({
totalsService,
@@ -72,6 +76,7 @@ class ReturnService extends TransactionBaseService {
fulfillmentProviderService,
orderService,
productVariantInventoryService,
featureFlagRouter,
}: InjectedDependencies) {
// eslint-disable-next-line prefer-rest-params
super(arguments[0])
@@ -86,6 +91,7 @@ class ReturnService extends TransactionBaseService {
this.returnReasonService_ = returnReasonService
this.orderService_ = orderService
this.productVariantInventoryService_ = productVariantInventoryService
this.featureFlagRouter_ = featureFlagRouter
}
/**
@@ -486,11 +492,33 @@ class ReturnService extends TransactionBaseService {
.withTransaction(manager)
.createShippingTaxLines(shippingMethod, calculationContext)
const includesTax =
this.featureFlagRouter_.isFeatureEnabled(
TaxInclusivePricingFeatureFlag.key
) && shippingMethod.includes_tax
const taxRate = taxLines.reduce((acc, curr) => {
return acc + curr.rate / 100
}, 0)
const taxAmountIncludedInPrice = !includesTax
? 0
: Math.round(
calculatePriceTaxAmount({
price: shippingMethod.price,
taxRate,
includesTax,
})
)
const shippingPriceWithoutTax =
shippingMethod.price - taxAmountIncludedInPrice
const shippingTotal =
shippingMethod.price +
shippingPriceWithoutTax +
taxLines.reduce(
(acc, tl) =>
acc + Math.round(shippingMethod.price * (tl.rate / 100)),
acc + Math.round(shippingPriceWithoutTax * (tl.rate / 100)),
0
)