fix(medusa): gift card values & taxes are calculated correctly (#2777)
* chore: tax_rate is added to giftcards * chore: minor * chore: update gift card tax calculations to look at giftCard.tax_rate * chore: gift card transactions use tax rate from gift card for legacy * fix: gift cart total check for transaction should check the length * chore: use tax exclusive cost + use giftcard tax rate for gctransactions + refactor * chore: fix integration test * chore: address issues brought up in comments * chore: move gift card creation as a part of order service on order placed * chore: add type handling for gift card creation * chore: fix specs * chore: use taxLines to calculate tax of a gift card * chore: specs for line items containing gift cards and without * chore: add integration specs + fix tax rate bug * chore: round totaling + add GC application specs * chore: cleanup trialables * chore: write migration script to backfill gift cards with null tax_rate * chore: update legacy totals service for gift cards * chore: add changeset * chore: address PR review changes * chore: fix tests based on new totals calc * chore: address review comments Co-authored-by: adrien2p <adrien.deperetti@gmail.com> Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
const startServerWithEnvironment =
|
||||
require("../../../../../helpers/start-server-with-environment").default
|
||||
const path = require("path")
|
||||
const { useApi } = require("../../../../../helpers/use-api")
|
||||
const { useDb } = require("../../../../../helpers/use-db")
|
||||
const { GiftCard, TaxRate } = require("@medusajs/medusa")
|
||||
|
||||
const {
|
||||
simpleRegionFactory,
|
||||
simpleProductFactory,
|
||||
simpleCartFactory,
|
||||
simpleCustomerFactory,
|
||||
simpleGiftCardFactory,
|
||||
} = require("../../../../factories")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] Gift Card - Tax calculations", () => {
|
||||
let medusaProcess
|
||||
let dbConnection
|
||||
let customerData
|
||||
|
||||
beforeEach(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", "..", ".."))
|
||||
const [process, connection] = await startServerWithEnvironment({
|
||||
cwd,
|
||||
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
||||
})
|
||||
dbConnection = connection
|
||||
medusaProcess = process
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
|
||||
medusaProcess.kill()
|
||||
})
|
||||
|
||||
describe("POST /store/carts/:id", () => {
|
||||
let product
|
||||
let customer
|
||||
let region
|
||||
|
||||
beforeEach(async () => {
|
||||
region = await simpleRegionFactory(dbConnection, {
|
||||
id: "tax-region",
|
||||
currency_code: "usd",
|
||||
countries: ["us"],
|
||||
tax_rate: 19,
|
||||
name: "region test",
|
||||
includes_tax: true,
|
||||
})
|
||||
|
||||
customer = await simpleCustomerFactory(dbConnection, { password: 'medusatest' })
|
||||
customerData = {
|
||||
email: customer.email,
|
||||
password: "medusatest",
|
||||
first_name: customer.first_name,
|
||||
last_name: customer.last_name,
|
||||
}
|
||||
|
||||
product = await simpleProductFactory(dbConnection, {
|
||||
is_giftcard: true,
|
||||
discountable: false,
|
||||
options: [{ id: "denom", title: "Denomination" }],
|
||||
variants: [{
|
||||
title: "Gift Card",
|
||||
prices: [{
|
||||
amount: 30000,
|
||||
currency: "usd",
|
||||
region_id: region.id,
|
||||
}],
|
||||
options: [{ option_id: "denom", value: "Denomination" }],
|
||||
}]
|
||||
})
|
||||
})
|
||||
|
||||
it("adding a gift card purchase to cart treats it like buying a product", async () => {
|
||||
const api = useApi()
|
||||
const customerRes = await api.post("/store/customers", customerData, {
|
||||
withCredentials: true,
|
||||
})
|
||||
|
||||
const createCartRes = await api.post("/store/carts", {
|
||||
region_id: region.id,
|
||||
items: [{
|
||||
variant_id: product.variants[0].id,
|
||||
quantity: 1,
|
||||
}],
|
||||
})
|
||||
|
||||
expect(createCartRes.status).toEqual(200)
|
||||
|
||||
const cartWithGiftcard = createCartRes.data.cart
|
||||
await api.post(`/store/carts/${cartWithGiftcard.id}`, {
|
||||
customer_id: customerRes.data.customer.id,
|
||||
})
|
||||
|
||||
expect(cartWithGiftcard.items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
is_giftcard: true,
|
||||
unit_price: 30000,
|
||||
quantity: 1,
|
||||
subtotal: 25210,
|
||||
tax_total: 4790,
|
||||
original_tax_total: 4790,
|
||||
original_total: 30000,
|
||||
total: 30000,
|
||||
variant: expect.objectContaining({
|
||||
id: product.variants[0].id,
|
||||
product: expect.objectContaining({
|
||||
is_giftcard: true,
|
||||
})
|
||||
})
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("purchasing a gift card via an order creates a gift card entity", async () => {
|
||||
const api = useApi()
|
||||
const customerRes = await api.post("/store/customers", customerData, {
|
||||
withCredentials: true,
|
||||
})
|
||||
|
||||
const cartFactory = await simpleCartFactory(dbConnection, {
|
||||
customer,
|
||||
region,
|
||||
})
|
||||
|
||||
const response = await api.post(
|
||||
`/store/carts/${cartFactory.id}/line-items`,
|
||||
{
|
||||
variant_id: product.variants[0].id,
|
||||
quantity: 1,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
|
||||
const getCartResponse = await api.get(`/store/carts/${cartFactory.id}`)
|
||||
const cart = getCartResponse.data.cart
|
||||
await api.post(`/store/carts/${cart.id}/payment-sessions`)
|
||||
const createdOrder = await api.post(`/store/carts/${cart.id}/complete-cart`)
|
||||
|
||||
const createdGiftCards = await dbConnection.manager.find(GiftCard, {
|
||||
where: { order_id: createdOrder.data.data.id }
|
||||
})
|
||||
const createdGiftCard = createdGiftCards[0]
|
||||
|
||||
expect(createdOrder.data.type).toEqual("order")
|
||||
expect(createdOrder.status).toEqual(200)
|
||||
expect(createdGiftCards.length).toEqual(1)
|
||||
expect(createdGiftCard.tax_rate).toEqual(19)
|
||||
expect(createdGiftCard.value).toEqual(25210)
|
||||
expect(createdGiftCard.balance).toEqual(25210)
|
||||
})
|
||||
|
||||
it("applying a gift card shows correct total values", async () => {
|
||||
const api = useApi()
|
||||
const giftCard = await simpleGiftCardFactory(dbConnection, {
|
||||
region_id: region.id,
|
||||
value: 25210,
|
||||
balance: 25210,
|
||||
tax_rate: region.tax_rate,
|
||||
})
|
||||
const expensiveProduct = await simpleProductFactory(dbConnection, {
|
||||
variants: [{
|
||||
title: "Product cost higher than gift card balance",
|
||||
prices: [{
|
||||
amount: 50000,
|
||||
currency: "usd",
|
||||
region_id: region.id,
|
||||
}],
|
||||
}]
|
||||
})
|
||||
|
||||
const customerRes = await api.post("/store/customers", customerData, {
|
||||
withCredentials: true,
|
||||
})
|
||||
|
||||
const cartFactory = await simpleCartFactory(dbConnection, {
|
||||
customer,
|
||||
region,
|
||||
line_items: [],
|
||||
})
|
||||
|
||||
const response = await api.post(
|
||||
`/store/carts/${cartFactory.id}/line-items`,
|
||||
{
|
||||
variant_id: expensiveProduct.variants[0].id,
|
||||
quantity: 1,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
|
||||
// Add gift card to cart
|
||||
await api.post(`/store/carts/${cartFactory.id}`, {
|
||||
gift_cards: [{ code: giftCard.code }],
|
||||
})
|
||||
|
||||
const getCartResponse = await api.get(`/store/carts/${cartFactory.id}`)
|
||||
const cart = getCartResponse.data.cart
|
||||
await api.post(`/store/carts/${cart.id}/payment-sessions`)
|
||||
const createdOrder = await api.post(`/store/carts/${cart.id}/complete-cart`)
|
||||
|
||||
expect(createdOrder.data.data).toEqual(
|
||||
expect.objectContaining({
|
||||
subtotal: 42017,
|
||||
discount_total: 0,
|
||||
shipping_total: 0,
|
||||
refunded_total: 0,
|
||||
paid_total: 20000,
|
||||
refundable_amount: 20000,
|
||||
gift_card_total: 25210,
|
||||
gift_card_tax_total: 4790,
|
||||
tax_total: 3193,
|
||||
total: 20000,
|
||||
items: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
includes_tax: true,
|
||||
unit_price: 50000,
|
||||
is_giftcard: false,
|
||||
quantity: 1,
|
||||
subtotal: 42017,
|
||||
discount_total: 0,
|
||||
total: 50000,
|
||||
original_total: 50000,
|
||||
original_tax_total: 7983,
|
||||
tax_total: 7983,
|
||||
refundable: 50000,
|
||||
tax_lines: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
rate: 19
|
||||
})
|
||||
]),
|
||||
})
|
||||
]),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,240 @@
|
||||
const startServerWithEnvironment =
|
||||
require("../../../../../helpers/start-server-with-environment").default
|
||||
const path = require("path")
|
||||
const { useApi } = require("../../../../../helpers/use-api")
|
||||
const { useDb } = require("../../../../../helpers/use-db")
|
||||
const { GiftCard } = require("@medusajs/medusa")
|
||||
|
||||
const {
|
||||
simpleRegionFactory,
|
||||
simpleProductFactory,
|
||||
simpleCartFactory,
|
||||
simpleCustomerFactory,
|
||||
simpleGiftCardFactory,
|
||||
} = require("../../../../factories")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("Gift Card - Tax calculations", () => {
|
||||
let medusaProcess
|
||||
let dbConnection
|
||||
let customerData
|
||||
|
||||
beforeEach(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", "..", ".."))
|
||||
const [process, connection] = await startServerWithEnvironment({
|
||||
cwd,
|
||||
env: {}
|
||||
})
|
||||
dbConnection = connection
|
||||
medusaProcess = process
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
|
||||
medusaProcess.kill()
|
||||
})
|
||||
|
||||
describe("POST /store/carts/:id", () => {
|
||||
let product
|
||||
let customer
|
||||
let region
|
||||
|
||||
beforeEach(async () => {
|
||||
region = await simpleRegionFactory(dbConnection, {
|
||||
id: "tax-region-1",
|
||||
currency_code: "usd",
|
||||
countries: ["us"],
|
||||
tax_rate: 19,
|
||||
name: "region test",
|
||||
})
|
||||
|
||||
customer = await simpleCustomerFactory(dbConnection, { password: 'medusatest' })
|
||||
|
||||
customerData = {
|
||||
email: customer.email,
|
||||
password: "medusatest",
|
||||
first_name: customer.first_name,
|
||||
last_name: customer.last_name,
|
||||
}
|
||||
|
||||
product = await simpleProductFactory(dbConnection, {
|
||||
is_giftcard: true,
|
||||
discountable: false,
|
||||
options: [{ id: "denom", title: "Denomination" }],
|
||||
variants: [{
|
||||
title: "Gift Card",
|
||||
prices: [{ currency: "usd", amount: 30000, region_id: region.id }],
|
||||
options: [{ option_id: "denom", value: "Denomination" }],
|
||||
}]
|
||||
})
|
||||
})
|
||||
|
||||
it("adding a gift card purchase to cart treats it like buying a product", async () => {
|
||||
const api = useApi()
|
||||
const customerResponse = await api.post("/store/customers", customerData, {
|
||||
withCredentials: true,
|
||||
})
|
||||
|
||||
const createCartResponse = await api.post("/store/carts", {
|
||||
region_id: region.id,
|
||||
items: [
|
||||
{
|
||||
variant_id: product.variants[0].id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(createCartResponse.status).toEqual(200)
|
||||
|
||||
const cartWithGiftcard = createCartResponse.data.cart
|
||||
await api.post(`/store/carts/${cartWithGiftcard.id}`, {
|
||||
customer_id: customerResponse.data.customer.id,
|
||||
})
|
||||
|
||||
expect(cartWithGiftcard.items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
is_giftcard: true,
|
||||
unit_price: 30000,
|
||||
quantity: 1,
|
||||
subtotal: 30000,
|
||||
tax_total: 5700,
|
||||
original_tax_total: 5700,
|
||||
original_total: 35700,
|
||||
total: 35700,
|
||||
variant: expect.objectContaining({
|
||||
id: product.variants[0].id,
|
||||
product: expect.objectContaining({
|
||||
is_giftcard: true,
|
||||
})
|
||||
})
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("purchasing a gift card via an order creates a gift card entity", async () => {
|
||||
const api = useApi()
|
||||
const customerResponse = await api.post("/store/customers", customerData, {
|
||||
withCredentials: true,
|
||||
})
|
||||
|
||||
const cartFactory = await simpleCartFactory(dbConnection, { customer, region })
|
||||
|
||||
const response = await api.post(
|
||||
`/store/carts/${cartFactory.id}/line-items`,
|
||||
{
|
||||
variant_id: product.variants[0].id,
|
||||
quantity: 1,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
|
||||
const cartResponse = await api.get(`/store/carts/${cartFactory.id}`)
|
||||
|
||||
const cart = cartResponse.data.cart
|
||||
|
||||
await api.post(`/store/carts/${cart.id}/payment-sessions`)
|
||||
|
||||
const createdOrderResponse = await api.post(`/store/carts/${cart.id}/complete-cart`)
|
||||
const createdGiftCards = await dbConnection.manager.find(GiftCard, {
|
||||
where: { order_id: createdOrderResponse.data.data.id }
|
||||
})
|
||||
const createdGiftCard = createdGiftCards[0]
|
||||
|
||||
expect(createdOrderResponse.data.type).toEqual("order")
|
||||
expect(createdOrderResponse.status).toEqual(200)
|
||||
expect(createdGiftCards.length).toEqual(1)
|
||||
expect(createdGiftCard.tax_rate).toEqual(19)
|
||||
expect(createdGiftCard.value).toEqual(30000)
|
||||
expect(createdGiftCard.balance).toEqual(30000)
|
||||
})
|
||||
|
||||
it("applying a gift card shows correct total values", async () => {
|
||||
const api = useApi()
|
||||
const giftCard = await simpleGiftCardFactory(dbConnection, {
|
||||
region_id: region.id,
|
||||
value: 30000,
|
||||
balance: 30000,
|
||||
tax_rate: region.tax_rate,
|
||||
})
|
||||
const expensiveProduct = await simpleProductFactory(dbConnection, {
|
||||
variants: [{
|
||||
title: "Product cost higher than gift card balance",
|
||||
prices: [{
|
||||
amount: 50000,
|
||||
currency: "usd",
|
||||
region_id: region.id,
|
||||
}],
|
||||
}]
|
||||
})
|
||||
|
||||
const customerRes = await api.post("/store/customers", customerData, {
|
||||
withCredentials: true,
|
||||
})
|
||||
|
||||
const cartFactory = await simpleCartFactory(dbConnection, {
|
||||
customer,
|
||||
region,
|
||||
line_items: [],
|
||||
})
|
||||
|
||||
const response = await api.post(
|
||||
`/store/carts/${cartFactory.id}/line-items`,
|
||||
{
|
||||
variant_id: expensiveProduct.variants[0].id,
|
||||
quantity: 1,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
|
||||
// Add gift card to cart
|
||||
await api.post(`/store/carts/${cartFactory.id}`, {
|
||||
gift_cards: [{ code: giftCard.code }],
|
||||
})
|
||||
|
||||
const getCartResponse = await api.get(`/store/carts/${cartFactory.id}`)
|
||||
const cart = getCartResponse.data.cart
|
||||
await api.post(`/store/carts/${cart.id}/payment-sessions`)
|
||||
const createdOrder = await api.post(`/store/carts/${cart.id}/complete-cart`)
|
||||
|
||||
expect(createdOrder.data.data).toEqual(
|
||||
expect.objectContaining({
|
||||
subtotal: 50000,
|
||||
discount_total: 0,
|
||||
shipping_total: 0,
|
||||
refunded_total: 0,
|
||||
paid_total: 23800,
|
||||
refundable_amount: 23800,
|
||||
gift_card_total: 30000,
|
||||
gift_card_tax_total: 5700,
|
||||
tax_total: 3800,
|
||||
total: 23800,
|
||||
items: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
unit_price: 50000,
|
||||
is_giftcard: false,
|
||||
quantity: 1,
|
||||
subtotal: 50000,
|
||||
discount_total: 0,
|
||||
total: 59500,
|
||||
original_total: 59500,
|
||||
original_tax_total: 9500,
|
||||
tax_total: 9500,
|
||||
refundable: 59500,
|
||||
tax_lines: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
rate: 19
|
||||
})
|
||||
]),
|
||||
})
|
||||
]),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -139,6 +139,7 @@ describe("Order Totals", () => {
|
||||
region_id: region.id,
|
||||
value: 160000,
|
||||
balance: 160000,
|
||||
tax_rate: 25,
|
||||
})
|
||||
|
||||
// Add variant 1 to cart
|
||||
|
||||
@@ -8,6 +8,7 @@ export type GiftCardFactoryData = {
|
||||
region_id: string
|
||||
value: number
|
||||
balance: number
|
||||
tax_rate?: number
|
||||
}
|
||||
|
||||
export const simpleGiftCardFactory = async (
|
||||
@@ -27,6 +28,7 @@ export const simpleGiftCardFactory = async (
|
||||
region_id: data.region_id,
|
||||
value: data.value,
|
||||
balance: data.balance,
|
||||
tax_rate: data.tax_rate,
|
||||
})
|
||||
|
||||
return await manager.save(toSave)
|
||||
|
||||
@@ -23,6 +23,7 @@ export type LineItemFactoryData = {
|
||||
thumbnail?: string
|
||||
should_merge?: boolean
|
||||
allow_discounts?: boolean
|
||||
is_giftcard?: boolean
|
||||
unit_price?: number
|
||||
quantity?: number
|
||||
fulfilled_quantity?: boolean
|
||||
@@ -74,6 +75,7 @@ export const simpleLineItemFactory = async (
|
||||
adjustments: data.adjustments,
|
||||
includes_tax: data.includes_tax,
|
||||
order_edit_id: data.order_edit_id,
|
||||
is_giftcard: data.is_giftcard || false
|
||||
})
|
||||
|
||||
const line = await manager.save(toSave)
|
||||
|
||||
Reference in New Issue
Block a user