Skip to content

[Discounts] Described Discounts API #2783

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: discounts-rest
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions code_samples/discounts/src/Command/ManageDiscountsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

declare(strict_types=1);

namespace App\Command;

use DateTimeImmutable;
use Ibexa\Contracts\Core\Collection\ArrayMap;
use Ibexa\Contracts\Core\Repository\PermissionResolver;
use Ibexa\Contracts\Core\Repository\UserService;
use Ibexa\Contracts\Discounts\DiscountServiceInterface;
use Ibexa\Contracts\Discounts\Value\DiscountType;
use Ibexa\Contracts\Discounts\Value\Struct\DiscountCreateStruct;
use Ibexa\Contracts\Discounts\Value\Struct\DiscountTranslationStruct;
use Ibexa\Contracts\DiscountsCodes\DiscountCodeServiceInterface;
use Ibexa\Contracts\DiscountsCodes\Value\Struct\DiscountCodeCreateStruct;
use Ibexa\Discounts\Value\DiscountCondition\IsInCurrency;
use Ibexa\Discounts\Value\DiscountCondition\IsInRegions;
use Ibexa\Discounts\Value\DiscountCondition\IsProductInArray;
use Ibexa\Discounts\Value\DiscountRule\FixedAmount;
use Ibexa\DiscountsCodes\Value\DiscountCondition\IsValidDiscountCode;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class ManageDiscountsCommand extends Command
{
protected static $defaultName = 'discounts:manage';

private DiscountServiceInterface $discountService;

private DiscountCodeServiceInterface $discountCodeService;

private PermissionResolver $permissionResolver;

private UserService $userService;

public function __construct(
UserService $userSerice,
PermissionResolver $permissionResolver,
DiscountServiceInterface $discountService,
DiscountCodeServiceInterface $discountCodeService
) {
$this->userService = $userSerice;
$this->discountService = $discountService;
$this->discountCodeService = $discountCodeService;
$this->permissionResolver = $permissionResolver;

parent::__construct();
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->permissionResolver->setCurrentUserReference(
$this->userService->loadUserByLogin('admin')
);

$now = new DateTimeImmutable();

$discountCodeCreateStruct = new DiscountCodeCreateStruct(
'summer10',
null, // Unlimited usage
$this->permissionResolver->getCurrentUserReference()->getUserId(),
$now
);
$discountCode = $this->discountCodeService->createDiscountCode($discountCodeCreateStruct);

$discountCreateStruct = new DiscountCreateStruct();
$discountCreateStruct
->setIdentifier('discount_identifier')
->setType(DiscountType::CART)
->setPriority(10)
->setEnabled(true)
->setUser($this->userService->loadUserByLogin('admin'))
->setRule(new FixedAmount(10))
->setStartDate($now)
->setConditions([
new IsInRegions(['germany', 'france']),
new IsProductInArray(['product-1', 'product-2']),
new IsInCurrency('EUR'),
new IsValidDiscountCode($discountCode->getCode(), $discountCode->getUsedLimit()),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I'm not sure about the $discountCode->getUsedLimit() part - which value should be passed here, the current usage or the max usage?

If I understand correctly, the condition calls the resolver which takes a single argument - so the second argument to IsValidDiscountCode is actually not used?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe @Steveb-p can clarify, but to me it's the max usage.

])
->setTranslations([
new DiscountTranslationStruct('eng-GB', 'Discount name', 'This is a discount description', 'Promotion Label', 'Promotion Description'),
new DiscountTranslationStruct('ger-DE', 'Discount name (German)', 'Description (German)', 'Promotion Label (German)', 'Promotion Description (German)'),
])
->setEndDate(null) // Permanent discount
->setCreatedAt($now)
->setUpdatedAt($now)
->setContext(new ArrayMap(['custom_context' => 'custom_value']));

$this->discountService->createDiscount($discountCreateStruct);

return Command::SUCCESS;
}
}
2 changes: 2 additions & 0 deletions docs/content_management/data_migration/importing_data.md
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,8 @@ The provided conditions overwrite any already existing ones.
[[= include_file('code_samples/data_migration/examples/discounts/discount_update.yaml') =]]
```

For a list of available conditions, see [Discounts API](discounts_api.md).
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For a list of available conditions, see [Discounts API](discounts_api.md).
For a list of available conditions, see [Discounts API](discounts_api.md#conditions).


## Criteria

When using `update` or `delete` modes, you can use criteria to identify the objects to operate on.
Expand Down
5 changes: 3 additions & 2 deletions docs/discounts/discounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ You can also extend the feature, for example, by creating custom pricing rules,

[[= cards([
"discounts/discounts_guide",
"discounts/install_discounts"
], columns=4) =]]
"discounts/install_discounts",
"discounts/discounts_api"
], columns=3) =]]
128 changes: 128 additions & 0 deletions docs/discounts/discounts_api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---

Check warning on line 1 in docs/discounts/discounts_api.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/discounts/discounts_api.md#L1

[Ibexa.ReadingLevel] The grade level is 10.76. Aim for 8th grade or lower by using shorter sentences and words.
Raw output
{"message": "[Ibexa.ReadingLevel] The grade level is 10.76. Aim for 8th grade or lower by using shorter sentences and words.", "location": {"path": "docs/discounts/discounts_api.md", "range": {"start": {"line": 1, "column": 1}}}, "severity": "WARNING"}
description: Discounts LTS Update enables reducing prices on products or product categories based on a detailed logic resolution.
month_change: true
editions:
- lts-update
- commerce
---

# Discounts API

## Manage discounts and discount codes

By integrating with the [Discount feature](discounts_guide.md) you can automate the process of managing discounts, streamlining the whole process and automating business rules.

For example, you can automatically create a discount when a customer places their 3rd order, encouraging them to make another purchase and increase their chances of becoming a local customer.

Check warning on line 15 in docs/discounts/discounts_api.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/discounts/discounts_api.md#L15

[Ibexa.OxfordComma] Use a comma before the last 'and' or 'or' in a list of four or more items.
Raw output
{"message": "[Ibexa.OxfordComma] Use a comma before the last 'and' or 'or' in a list of four or more items.", "location": {"path": "docs/discounts/discounts_api.md", "range": {"start": {"line": 15, "column": 1}}}, "severity": "WARNING"}

You can manage discounts using [data migrations](importing_data.md#discounts), [REST API](/api/rest_api/rest_api_reference/rest_api_reference.html#discounts), or the PHP API by using the [Ibexa\Contracts\Discounts\DiscountServiceInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html) service.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
You can manage discounts using [data migrations](importing_data.md#discounts), [REST API](/api/rest_api/rest_api_reference/rest_api_reference.html#discounts), or the PHP API by using the [Ibexa\Contracts\Discounts\DiscountServiceInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html) service.
You can manage discounts using [data migrations](importing_data.md#discounts), [REST API](/api/rest_api/rest_api_reference/rest_api_reference.html#discounts), or the PHP API by using the [`Ibexa\Contracts\Discounts\DiscountServiceInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html) service.


The core concepts when working with discounts through the APIs are listed below.

### Types

When using the PHP API, the discount type defines where the discount can be applied.

Discounts are applied in two places, listed in the [DiscountType](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountType.html) class:

Check failure on line 25 in docs/discounts/discounts_api.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/discounts/discounts_api.md#L25

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/discounts/discounts_api.md", "range": {"start": {"line": 25, "column": 106}}}, "severity": "ERROR"}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Discounts are applied in two places, listed in the [DiscountType](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountType.html) class:
Discounts are applied in two places, listed in the [`DiscountType`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountType.html) class:


- **Product catalog** - `catalog` discounts are activated when browsing the product catalog and do not require any action from the customer to be activated
- **Cart** - `cart` discounts can activate when entering the [cart](cart.md), if the right conditions are met. They may also require entering a discount code to be activated

Regardless of activation place, discounts always apply to products and reduce their base price.

To define when a discount activates and how the price is reduced, use rules and conditions.
They make use of the [Symfony Expression language]([[= symfony_doc=]]//components/expression_language.html).
Use the expression values provided below when using data migrations or the REST API to pass the right values.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get where and how to use those expression values, especially when looking at REST.

The "Rules" table below has discount_percentage but /api/rest_api/rest_api_reference/rest_api_reference.html#discounts-create-discount use

        "rule": {
            "type": "percentage",
            "amount": 10
        },

The "Conditions" table says for is_in_category that expression value is categories but the REST example uses

        "conditions": [
            {
                "class": "Ibexa\\Discounts\\Value\\DiscountCondition\\IsInCategory",
                "parameters": [["1", "2"]]
            }
        ]

It seems a bit more understandable on migration side.
Identifiers (like is_in_currency) and expression values (like currency_code) can be found on https://ez-systems-developer-documentation--2783.com.readthedocs.build/en/2783/content_management/data_migration/importing_data/#discounts example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, what I had in mind was:
"it might be useful to know the expression values when parsing the REST responses", because they are there.

Zrzut ekranu 2025-06-13 o 11 24 49

But they are not needed when using the REST API to create.

Which one would be clearer to you - drop the REST API mention or mention that they are visible in REST responses?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I continue on this while reviewing #2780 and I see that the expression values

I don't know now how to clarify that yet.
Somehow this confusion is "REST API's fault" which can't be fixed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mnocon Lets be just straight.

Suggested change
Use the expression values provided below when using data migrations or the REST API to pass the right values.
Use the expression values provided below when using data migrations or when parsing REST API responses.


### Rules

Discount rules define how the calculate the price reduction.
The following discount rule types are available:

| Rule type | Identifier | Description | Expression value |
|---|---|---|---|
| `Ibexa\Discounts\Value\DiscountRule\FixedAmount` | `fixed_amount` | Deducts the specified amount, for example 10 EUR, from the base price | `discount_amount` |
| `Ibexa\Discounts\Value\DiscountRule\Percentage` | `percentage` | Deducts the specified percentage, for example -10%, from the base price | `discount_percentage` |
Comment on lines +43 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

I'll add some nobr tag to have the new lines only on Rule type and Description columns

Suggested change
| `Ibexa\Discounts\Value\DiscountRule\FixedAmount` | `fixed_amount` | Deducts the specified amount, for example 10 EUR, from the base price | `discount_amount` |
| `Ibexa\Discounts\Value\DiscountRule\Percentage` | `percentage` | Deducts the specified percentage, for example -10%, from the base price | `discount_percentage` |
| `Ibexa\Discounts\Value\DiscountRule\FixedAmount` | <nobr>`fixed_amount`</nobr> | Deducts the specified amount, for example <nobr>10 EUR</nobr>, from the base price | <nobr>`discount_amount`</nobr> |
| `Ibexa\Discounts\Value\DiscountRule\Percentage` | <nobr>`percentage`</nobr> | Deducts the specified percentage, for example -10%, from the base price | <nobr>`discount_percentage`</nobr> |

Same with the Conditions table.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I was wondering if there's anything I could do to fix this, this is great 🙇


Only a single discount can be applied to a given product, and a discount can only have a single rule.

### Conditions

With conditions you can narrow down the scenarios in which the discount applies. The following conditions are available:

| Condition | Applies to | Identifier | Description | Expression values |
|---|---|---|---|---|
| `Ibexa\Discounts\Value\DiscountCondition\IsInCategory` | Cart, Catalog | `is_in_category` | Checks if the product belongs to specified [product categories]([[= user_doc =]]/pim/work_with_product_categories) | `categories` |
| `Ibexa\Discounts\Value\DiscountCondition\IsInCurrency` | Cart, Catalog |`is_in_currency` | Checks if the product has price in the specified currency | `currency_code` |
| `Ibexa\Discounts\Value\DiscountCondition\IsInRegions` | Cart, Catalog | `is_in_regions` | Checks if the customer is making the purchase in one of the specified regions | `regions` |
| `Ibexa\Discounts\Value\DiscountCondition\IsProductInArray` | Cart, Catalog| `is_product_in_array` | Checks if the product belongs to the group of selected products | `product_codes` |
| `Ibexa\Discounts\Value\DiscountCondition\IsUserInCustomerGroup` | Cart, Catalog| `is_user_in_customer_group` | Check if the customer belongs to specified [customer groups](customer_groups.md) | `customer_groups` |
| `Ibexa\Discounts\Value\DiscountCondition\IsProductInQuantityInCart` | Cart | `is_product_in_quantity_in_cart` | Checks if the required minimum quantity of a given product is present in the cart | `quantity` |
| `Ibexa\Discounts\Value\DiscountCondition\MinimumPurchaseAmount` | Cart | `minimum_purchase_amount` | Checks if purchase amount in the cart exceeds the specified minimum | `minimum_purchase_amount` |
| `Ibexa\DiscountsCodes\Value\DiscountCondition\IsValidDiscountCode` | Cart | `is_valid_discount_code` | Checks if the correct discount code has been provided and how many times it was used by the customer | `discount_code`, `usage_count` |

When multiple conditions are specified, all of them must be met.

### Priority

You can set discount priority as a number between 1 and 10 to indicate which discount should have [higher priority](discounts_guide.md#discounts-priority) when choosing the one to apply.
Comment on lines +65 to +67
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


### Start and end date

Discounts can be permanent, or valid only in a specified time frame.

Every discount has a start date, which defaults to the date when the discount was created.
The end date can be set to `null` to make the discount permanent.

### Status

You can disable a discount anytime to stop it from being active, even if the conditions enforced by start and end date are met.

Only disabled discounts can be deleted.

### Discount translations

The discount has four properties that can be translated:

| Property | Usage |
|---|---|
| Name | Internal information for store managers |
| Description | Internal information for store managers |
| Promotion label | Information displayed to customers |
| Promotion description | Information displayed to customers |

Use the [DiscountTranslationStruct](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-DiscountTranslationStruct.html) to provide translations for discounts.

Check failure on line 93 in docs/discounts/discounts_api.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/discounts/discounts_api.md#L93

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/discounts/discounts_api.md", "range": {"start": {"line": 93, "column": 76}}}, "severity": "ERROR"}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Use the [DiscountTranslationStruct](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-DiscountTranslationStruct.html) to provide translations for discounts.
Use the [`DiscountTranslationStruct`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-DiscountTranslationStruct.html) to provide translations for discounts.


### Discount codes

To activate a cart discount only after a proper discount code is provided, you need to:

1. Create a discount code using the [DiscountCodeServiceInterface::createDiscountCode()](/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-DiscountCodeServiceInterface.html#method_createDiscountCode) method

Check failure on line 99 in docs/discounts/discounts_api.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/discounts/discounts_api.md#L99

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/discounts/discounts_api.md", "range": {"start": {"line": 99, "column": 129}}}, "severity": "ERROR"}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
1. Create a discount code using the [DiscountCodeServiceInterface::createDiscountCode()](/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-DiscountCodeServiceInterface.html#method_createDiscountCode) method
1. Create a discount code using the [`DiscountCodeServiceInterface::createDiscountCode()`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-DiscountCodeServiceInterface.html#method_createDiscountCode) method

1. Attach it to a discount by using the `IsValidDiscountCode` condition

Set the [`usedLimit`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Value-Struct-DiscountCodeCreateStruct.html#method___construct) property to the number of times a single customer can use this code, or to `null` to make the usage unlimited.

Check failure on line 102 in docs/discounts/discounts_api.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/discounts/discounts_api.md#L102

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/discounts/discounts_api.md", "range": {"start": {"line": 102, "column": 62}}}, "severity": "ERROR"}

The [`DiscountCodeServiceInterface::registerUsage()`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-DiscountCodeServiceInterface.html#method_registerUsage) method is used to track the number of times a discount code has been used.

Check failure on line 104 in docs/discounts/discounts_api.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/discounts/discounts_api.md#L104

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/discounts/discounts_api.md", "range": {"start": {"line": 104, "column": 94}}}, "severity": "ERROR"}

### Example API usage

The example below contains a Command creating a cart discount. The discount:

- has the highest possible priority value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As someone might prefer start with an example and I skip or skim read the begining, as someone could end here because of search terms, or whatever the reason to not fully known the concept presented above, I would link back to previous section as much as possible.

Suggested change
- has the highest possible priority value
- has the highest possible [priority](#priority) value

- deducts 10 EUR from the base price of the product
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- deducts 10 EUR from the base price of the product
- [rule](#rules) a deduction of 10 EUR from the base price of the product

- is permanent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- is permanent
- is [permanent](#start-and-end-date)

- is valid in Germany and France
- applies to 2 products
- requires a `summer10` discount code to be activated. The code can be used unlimited number of times
Comment on lines +113 to +115
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- is valid in Germany and France
- applies to 2 products
- requires a `summer10` discount code to be activated. The code can be used unlimited number of times
- [depends](#conditions) on
- being bought from Germany or France
- 2 products
- a `summer10` [discount code](#discount-codes) which can be used unlimited number of times


``` php hl_lines="60-66 68-92"
[[= include_file('code_samples/discounts/src/Command/ManageDiscountsCommand.php') =]]
```

Similarly, use the `deleteDiscount`, `deleteTranslation`, `disableDiscount`, `enableDiscount`, and `updateDiscount` methods from the [DiscountServiceInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html) to manage the discounts. You can always attach additional logic to the Discounts API by listening to the [available events](discounts_events.md).

## Search

You can search for Discounts using the [`DiscountServiceInterface::findDiscounts()](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_findDiscounts) method.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
You can search for Discounts using the [`DiscountServiceInterface::findDiscounts()](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_findDiscounts) method.
You can search for Discounts using the [`DiscountServiceInterface::findDiscounts()`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_findDiscounts) method.

To learn more about the available search options, see Discounts' [Search Criteria](discounts_criteria.md) and [Sort Clauses](discounts_sort_clauses.md).

For discount codes, you can query the database for discount code usage using [`DiscountCodeServiceInterface::findCodeUsages()`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-DiscountCodeServiceInterface.html#method_findCodeUsages) and [`DiscountCodeUsageQuery`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-Value-Query-DiscountCodeUsageQuery.html).
4 changes: 3 additions & 1 deletion docs/discounts/discounts_guide.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Discounts LTS Update enables reducing prices on products or product categories based on a detailed logic resolution.
month_change: false
month_change: true
editions:
- lts-update
- commerce
Expand Down Expand Up @@ -55,6 +55,8 @@ Discounts are applied in two places:

A shopping cart can have multiple active discounts, but a specific product can only have a single discount applied to it at a time.

#### Discounts priority

When two or more discounts can be applied to a single product, the system evaluates the following properties to choose the right one:

- discount activation place (cart discounts rank higher over catalog discounts)
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ nav:
- Discounts: discounts/discounts.md
- Discounts guide: discounts/discounts_guide.md
- Install Discounts: discounts/install_discounts.md
- Discounts API: discounts/discounts_api.md
- Customer management:
- Customer Portal: customer_management/customer_portal.md
- Customer Portal guide: customer_management/customer_portal_guide.md
Expand Down
Loading