Skip to content

Commit 1e16fa6

Browse files
authored
docs: document tax-inclusive promotions concept (medusajs#12849)
1 parent 0bcde27 commit 1e16fa6

File tree

10 files changed

+353
-25
lines changed

10 files changed

+353
-25
lines changed

www/apps/book/public/llms-full.txt

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27808,6 +27808,14 @@ A region’s price preference’s `is_tax_inclusive`'s value takes higher preced
2780827808
- the selected price belongs to the region;
2780927809
- and the region has a price preference
2781027810

27811+
***
27812+
27813+
## Tax-Inclusive Pricing with Promotions
27814+
27815+
When you enable tax-inclusive prices for regions or currencies, this can impact how promotions are applied to the cart. So, it's recommended to enable tax-inclusiveness for promotions as well.
27816+
27817+
Learn more in the [Tax-Inclusive Promotions](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/promotion-taxes/index.html.md) guide.
27818+
2781127819

2781227820
# Calculate Product Variant Price with Taxes
2781327821

@@ -28973,6 +28981,7 @@ export interface AddItemAdjustmentAction {
2897328981
amount: number
2897428982
code: string
2897528983
description?: string
28984+
is_tax_inclusive?: boolean
2897628985
}
2897728986
```
2897828987

@@ -29613,6 +29622,142 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
2961329622
***
2961429623

2961529624

29625+
# Tax-Inclusive Promotions
29626+
29627+
In this guide, you’ll learn how taxes are applied to promotions in a cart.
29628+
29629+
This feature is available from [Medusa v2.8.5](https://github.com/medusajs/medusa/releases/tag/v2.8.5).
29630+
29631+
## What are Tax-Inclusive Promotions?
29632+
29633+
By default, promotions are tax-exclusive, meaning that the discount amount is applied as-is to the cart before taxes are calculated and applied to the cart total.
29634+
29635+
A tax-inclusive promotion is a promotion for which taxes are calculated from the discount amount entered by the merchant.
29636+
29637+
When a promotion is tax-inclusive, the discount amount is reduced by the calculated tax amount based on the [tax region's rate](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-region/index.html.md). The reduced discount amount is then applied to the cart total.
29638+
29639+
Tax-inclusiveness doesn't apply to Buy X Get Y promotions.
29640+
29641+
### When to Use Tax-Inclusive Promotions
29642+
29643+
Tax-inclusive promotions are most useful when using [tax-inclusive prices](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/index.html.md) for items in the cart.
29644+
29645+
In this scenario, Medusa applies taxes consistently across the cart, ensuring that the total price reflects the taxes and promotions correctly.
29646+
29647+
You can see this in action in the [examples below](#tax-inclusiveness-examples).
29648+
29649+
***
29650+
29651+
## What Makes a Promotion Tax-Inclusive?
29652+
29653+
The [Promotion data model](https://docs.medusajs.com/references/promotion/models/Promotion/index.html.md) has an `is_tax_inclusive` property that determines whether the promotion is tax-inclusive.
29654+
29655+
If `is_tax_inclusive` is disabled (which is the default), the promotion's discount amount will be applied as-is to the cart, before taxes are calculated. See an example in the [Tax-Exclusive Promotion Example](#tax-exclusive-promotion-example) section.
29656+
29657+
If `is_tax_inclusive` is enabled, the promotion's discount amount will first be reduced by the calculated tax amount (based on the [tax region's rate](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-region/index.html.md)). The reduced discount amount is then applied to the cart total. See an example in the [Tax-Inclusive Promotion Example](#tax-inclusive-promotion-example) section.
29658+
29659+
***
29660+
29661+
## How to Set a Promotion as Tax-Inclusive
29662+
29663+
You can enable tax-inclusiveness for a promotion when [creating it in the Medusa Admin](https://docs.medusajs.com/user-guide/promotions/create/index.html.md).
29664+
29665+
You can set the `is_tax_inclusive` property when creating a promotion by using either the [Promotion workflows](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/workflows/index.html.md) or the [Promotion Module's service](https://docs.medusajs.com/references/promotion/index.html.md).
29666+
29667+
For most use cases, it's recommended to use [workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md) instead of directly using the module's service, as they implement the necessary rollback mechanisms in case of errors.
29668+
29669+
For example, if you're creating a promotion with the [createPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionsWorkflow/index.html.md) in an API route:
29670+
29671+
```ts highlights={[["17"]]}
29672+
import type {
29673+
MedusaRequest,
29674+
MedusaResponse,
29675+
} from "@medusajs/framework/http"
29676+
import { createPromotionsWorkflow } from "@medusajs/medusa/core-flows"
29677+
29678+
export async function POST(
29679+
req: MedusaRequest,
29680+
res: MedusaResponse
29681+
) {
29682+
const { result } = await createPromotionsWorkflow(req.scope)
29683+
.run({
29684+
input: {
29685+
promotionsData: [{
29686+
code: "10OFF",
29687+
// ...
29688+
is_tax_inclusive: true,
29689+
}],
29690+
}
29691+
})
29692+
29693+
res.send(result)
29694+
}
29695+
```
29696+
29697+
In the above example, you set the `is_tax_inclusive` property to `true` when creating the promotion, making it tax-inclusive.
29698+
29699+
### Updating a Promotion's Tax-Inclusiveness
29700+
29701+
A promotion's tax-inclusiveness cannot be updated after it has been created. If you need to change a promotion's tax-inclusiveness, you must delete the existing promotion and create a new one with the desired `is_tax_inclusive` value.
29702+
29703+
***
29704+
29705+
## Tax-Inclusiveness Examples
29706+
29707+
The following sections provide examples of how tax-inclusive promotions work in different scenarios, including both tax-exclusive and tax-inclusive promotions.
29708+
29709+
These examples will help you understand how tax-inclusive promotions affect the cart total, allowing you to decide when to use them effectively.
29710+
29711+
### Tax-Exclusive Promotion Example
29712+
29713+
Consider the following scenario:
29714+
29715+
- A tax-exclusive promotion gives a `$10` discount on the cart's total.
29716+
- The cart's tax region has a `25%` tax rate.
29717+
- The cart total before applying the promotion is `$100`.
29718+
- [The prices in the cart's tax region are tax-exclusive](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/index.html.md).
29719+
29720+
The result:
29721+
29722+
1. Apply `$10` discount to cart total: `$100` - `$10` = `$90`
29723+
2. Calculate tax on discounted total: `$90` x `25%` = `$22.50`
29724+
3. Final total: `$90` + `$22.50` = `$112.50`
29725+
29726+
### Tax-Inclusive Promotion Example
29727+
29728+
Consider the following scenario:
29729+
29730+
- A tax-inclusive promotion gives a `$10` discount on the cart's total.
29731+
- The cart's tax region has a `25%` tax rate.
29732+
- The cart total before applying the promotion is `$100`.
29733+
- [The prices in the cart's tax region are tax-exclusive](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/index.html.md).
29734+
29735+
The result:
29736+
29737+
1. Calculate actual discount (removing tax): `$10` ÷ `1.25` = `$8`
29738+
2. Apply discount to cart total: `$100` - `$8` = `$92`
29739+
3. Calculate tax on discounted total: `$92` x `25%` = `$23`
29740+
4. Final total: `$92` + `$23` = `$115`
29741+
29742+
### Tax-Inclusive Promotions with Tax-Inclusive Prices
29743+
29744+
Consider the following scenario:
29745+
29746+
- A tax-inclusive promotion gives a `$10` discount on the cart's total.
29747+
- The cart's tax region has a `25%` tax rate.
29748+
- The cart total before applying the promotion is `$100`.
29749+
- [The prices in the cart's tax region are tax-inclusive](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/index.html.md).
29750+
29751+
The result:
29752+
29753+
1. Calculate actual discount (removing tax): `$10` ÷ `1.25` = `$8`
29754+
2. Calculate cart total without tax: `$100` ÷ `1.25` = `$80`
29755+
3. Apply discount to cart total without tax: `$80` - `$8` = `$72`
29756+
4. Add tax back to total: `$72` x `1.25` = `$90`
29757+
29758+
The final total is `$90`, which correctly applies both the tax-inclusive promotion and tax-inclusive pricing.
29759+
29760+
2961629761
# Links between Region Module and Other Modules
2961729762

2961829763
This document showcases the module links defined between the Region Module and other Commerce Modules.

www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/page.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,11 @@ A region’s price preference’s `is_tax_inclusive`'s value takes higher preced
7676
- both the `region_id` and `currency_code` are provided in the calculation context;
7777
- the selected price belongs to the region;
7878
- and the region has a price preference
79+
80+
---
81+
82+
## Tax-Inclusive Pricing with Promotions
83+
84+
When you enable tax-inclusive prices for regions or currencies, this can impact how promotions are applied to the cart. So, it's recommended to enable tax-inclusiveness for promotions as well.
85+
86+
Learn more in the [Tax-Inclusive Promotions](../../promotion/promotion-taxes/page.mdx) guide.

www/apps/resources/app/commerce-modules/promotion/actions/page.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface AddItemAdjustmentAction {
2929
amount: number
3030
code: string
3131
description?: string
32+
is_tax_inclusive?: boolean
3233
}
3334
```
3435

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
---
2+
tags:
3+
- tax
4+
product:
5+
- cart
6+
- tax
7+
---
8+
9+
export const metadata = {
10+
title: `Tax-Inclusive Promotions`,
11+
}
12+
13+
# {metadata.title}
14+
15+
In this guide, you’ll learn how taxes are applied to promotions in a cart.
16+
17+
<Note>
18+
19+
This feature is available from [Medusa v2.8.5](https://github.com/medusajs/medusa/releases/tag/v2.8.5).
20+
21+
</Note>
22+
23+
## What are Tax-Inclusive Promotions?
24+
25+
By default, promotions are tax-exclusive, meaning that the discount amount is applied as-is to the cart before taxes are calculated and applied to the cart total.
26+
27+
A tax-inclusive promotion is a promotion for which taxes are calculated from the discount amount entered by the merchant.
28+
29+
When a promotion is tax-inclusive, the discount amount is reduced by the calculated tax amount based on the [tax region's rate](../../tax/tax-region/page.mdx). The reduced discount amount is then applied to the cart total.
30+
31+
<Note>
32+
33+
Tax-inclusiveness doesn't apply to Buy X Get Y promotions.
34+
35+
</Note>
36+
37+
### When to Use Tax-Inclusive Promotions
38+
39+
Tax-inclusive promotions are most useful when using [tax-inclusive prices](../../pricing/tax-inclusive-pricing/page.mdx) for items in the cart.
40+
41+
In this scenario, Medusa applies taxes consistently across the cart, ensuring that the total price reflects the taxes and promotions correctly.
42+
43+
You can see this in action in the [examples below](#tax-inclusiveness-examples).
44+
45+
---
46+
47+
## What Makes a Promotion Tax-Inclusive?
48+
49+
The [Promotion data model](/references/promotion/models/Promotion) has an `is_tax_inclusive` property that determines whether the promotion is tax-inclusive.
50+
51+
If `is_tax_inclusive` is disabled (which is the default), the promotion's discount amount will be applied as-is to the cart, before taxes are calculated. See an example in the [Tax-Exclusive Promotion Example](#tax-exclusive-promotion-example) section.
52+
53+
If `is_tax_inclusive` is enabled, the promotion's discount amount will first be reduced by the calculated tax amount (based on the [tax region's rate](../../tax/tax-region/page.mdx)). The reduced discount amount is then applied to the cart total. See an example in the [Tax-Inclusive Promotion Example](#tax-inclusive-promotion-example) section.
54+
55+
---
56+
57+
## How to Set a Promotion as Tax-Inclusive
58+
59+
<Note title="Looking for no-code approach?">
60+
61+
You can enable tax-inclusiveness for a promotion when [creating it in the Medusa Admin](!user-guide!/promotions/create).
62+
63+
</Note>
64+
65+
You can set the `is_tax_inclusive` property when creating a promotion by using either the [Promotion workflows](../workflows/page.mdx) or the [Promotion Module's service](/references/promotion).
66+
67+
<Note>
68+
69+
For most use cases, it's recommended to use [workflows](!docs!/learn/fundamentals/workflows) instead of directly using the module's service, as they implement the necessary rollback mechanisms in case of errors.
70+
71+
</Note>
72+
73+
For example, if you're creating a promotion with the [createPromotionsWorkflow](/references/medusa-workflows/createPromotionsWorkflow) in an API route:
74+
75+
```ts highlights={[["17"]]}
76+
import type {
77+
MedusaRequest,
78+
MedusaResponse,
79+
} from "@medusajs/framework/http"
80+
import { createPromotionsWorkflow } from "@medusajs/medusa/core-flows"
81+
82+
export async function POST(
83+
req: MedusaRequest,
84+
res: MedusaResponse
85+
) {
86+
const { result } = await createPromotionsWorkflow(req.scope)
87+
.run({
88+
input: {
89+
promotionsData: [{
90+
code: "10OFF",
91+
// ...
92+
is_tax_inclusive: true,
93+
}],
94+
}
95+
})
96+
97+
res.send(result)
98+
}
99+
```
100+
101+
In the above example, you set the `is_tax_inclusive` property to `true` when creating the promotion, making it tax-inclusive.
102+
103+
### Updating a Promotion's Tax-Inclusiveness
104+
105+
A promotion's tax-inclusiveness cannot be updated after it has been created. If you need to change a promotion's tax-inclusiveness, you must delete the existing promotion and create a new one with the desired `is_tax_inclusive` value.
106+
107+
---
108+
109+
## Tax-Inclusiveness Examples
110+
111+
The following sections provide examples of how tax-inclusive promotions work in different scenarios, including both tax-exclusive and tax-inclusive promotions.
112+
113+
These examples will help you understand how tax-inclusive promotions affect the cart total, allowing you to decide when to use them effectively.
114+
115+
### Tax-Exclusive Promotion Example
116+
117+
Consider the following scenario:
118+
119+
- A tax-exclusive promotion gives a `$10` discount on the cart's total.
120+
- The cart's tax region has a `25%` tax rate.
121+
- The cart total before applying the promotion is `$100`.
122+
- [The prices in the cart's tax region are tax-exclusive](../../pricing/tax-inclusive-pricing/page.mdx).
123+
124+
The result:
125+
126+
1. Apply `$10` discount to cart total: `$100` - `$10` = `$90`
127+
2. Calculate tax on discounted total: `$90` x `25%` = `$22.50`
128+
3. Final total: `$90` + `$22.50` = `$112.50`
129+
130+
### Tax-Inclusive Promotion Example
131+
132+
Consider the following scenario:
133+
134+
- A tax-inclusive promotion gives a `$10` discount on the cart's total.
135+
- The cart's tax region has a `25%` tax rate.
136+
- The cart total before applying the promotion is `$100`.
137+
- [The prices in the cart's tax region are tax-exclusive](../../pricing/tax-inclusive-pricing/page.mdx).
138+
139+
The result:
140+
141+
1. Calculate actual discount (removing tax): `$10` ÷ `1.25` = `$8`
142+
2. Apply discount to cart total: `$100` - `$8` = `$92`
143+
3. Calculate tax on discounted total: `$92` x `25%` = `$23`
144+
4. Final total: `$92` + `$23` = `$115`
145+
146+
### Tax-Inclusive Promotions with Tax-Inclusive Prices
147+
148+
Consider the following scenario:
149+
150+
- A tax-inclusive promotion gives a `$10` discount on the cart's total.
151+
- The cart's tax region has a `25%` tax rate.
152+
- The cart total before applying the promotion is `$100`.
153+
- [The prices in the cart's tax region are tax-inclusive](../../pricing/tax-inclusive-pricing/page.mdx).
154+
155+
The result:
156+
157+
1. Calculate actual discount (removing tax): `$10` ÷ `1.25` = `$8`
158+
2. Calculate cart total without tax: `$100` ÷ `1.25` = `$80`
159+
3. Apply discount to cart total without tax: `$80` - `$8` = `$72`
160+
4. Add tax back to total: `$72` x `1.25` = `$90`
161+
162+
The final total is `$90`, which correctly applies both the tax-inclusive promotion and tax-inclusive pricing.

www/apps/resources/generated/edit-dates.mjs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export const generatedEditDates = {
5858
"app/commerce-modules/pricing/concepts/page.mdx": "2024-10-09T13:37:25.678Z",
5959
"app/commerce-modules/pricing/price-calculation/page.mdx": "2025-05-20T07:51:40.710Z",
6060
"app/commerce-modules/pricing/price-rules/page.mdx": "2025-05-20T07:51:40.710Z",
61-
"app/commerce-modules/pricing/tax-inclusive-pricing/page.mdx": "2024-10-09T13:48:23.261Z",
61+
"app/commerce-modules/pricing/tax-inclusive-pricing/page.mdx": "2025-06-27T15:43:35.193Z",
6262
"app/commerce-modules/pricing/page.mdx": "2025-05-20T07:51:40.710Z",
6363
"app/commerce-modules/product/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
6464
"app/commerce-modules/product/_events/page.mdx": "2024-07-03T19:27:13+03:00",
@@ -67,7 +67,7 @@ export const generatedEditDates = {
6767
"app/commerce-modules/product/page.mdx": "2025-05-20T07:51:40.711Z",
6868
"app/commerce-modules/promotion/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
6969
"app/commerce-modules/promotion/_events/page.mdx": "2024-07-03T19:27:13+03:00",
70-
"app/commerce-modules/promotion/actions/page.mdx": "2024-10-09T14:49:01.645Z",
70+
"app/commerce-modules/promotion/actions/page.mdx": "2025-06-27T15:42:19.142Z",
7171
"app/commerce-modules/promotion/application-method/page.mdx": "2024-06-26T07:55:59+00:00",
7272
"app/commerce-modules/promotion/campaign/page.mdx": "2025-02-26T11:32:24.484Z",
7373
"app/commerce-modules/promotion/concepts/page.mdx": "2025-02-26T11:31:54.391Z",
@@ -6546,8 +6546,6 @@ export const generatedEditDates = {
65466546
"references/utils/utils.Payment/page.mdx": "2025-06-05T19:05:53.489Z",
65476547
"app/integrations/guides/slack/page.mdx": "2025-06-26T12:57:20.880Z",
65486548
"app/integrations/guides/sentry/page.mdx": "2025-06-16T10:11:29.955Z",
6549-
"app/integrations/guides/mailchimp/page.mdx": "2025-06-24T08:08:35.034Z",
6550-
"app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx": "2025-06-26T09:13:19.296Z",
65516549
"app/integrations/guides/mailchimp/page.mdx": "2025-06-26T11:59:15.303Z",
65526550
"app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx": "2025-06-26T11:55:27.175Z",
65536551
"references/types/CommonTypes/interfaces/types.CommonTypes.CookieOptions/page.mdx": "2025-06-25T10:11:37.088Z",
@@ -6557,5 +6555,6 @@ export const generatedEditDates = {
65576555
"references/types/interfaces/types.InitiatePaymentOutput/page.mdx": "2025-06-25T10:11:39.942Z",
65586556
"references/types/interfaces/types.UpdatePaymentOutput/page.mdx": "2025-06-25T10:11:39.945Z",
65596557
"app/how-to-tutorials/tutorials/gift-message/page.mdx": "2025-06-26T09:13:19.296Z",
6560-
"app/how-to-tutorials/tutorials/re-order/page.mdx": "2025-06-26T12:38:24.308Z"
6558+
"app/how-to-tutorials/tutorials/re-order/page.mdx": "2025-06-26T12:38:24.308Z",
6559+
"app/commerce-modules/promotion/promotion-taxes/page.mdx": "2025-06-27T15:44:46.638Z"
65616560
}

0 commit comments

Comments
 (0)