diff --git a/code_samples/discounts/src/Command/ManageDiscountsCommand.php b/code_samples/discounts/src/Command/ManageDiscountsCommand.php new file mode 100644 index 0000000000..6243da8ca6 --- /dev/null +++ b/code_samples/discounts/src/Command/ManageDiscountsCommand.php @@ -0,0 +1,96 @@ +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()), + ]) + ->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; + } +} diff --git a/docs/content_management/data_migration/importing_data.md b/docs/content_management/data_migration/importing_data.md index 36c9e67dd1..e1da81407a 100644 --- a/docs/content_management/data_migration/importing_data.md +++ b/docs/content_management/data_migration/importing_data.md @@ -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#conditions). + ## Criteria When using `update` or `delete` modes, you can use criteria to identify the objects to operate on. diff --git a/docs/discounts/discounts.md b/docs/discounts/discounts.md index 78d4070de2..322335a76c 100644 --- a/docs/discounts/discounts.md +++ b/docs/discounts/discounts.md @@ -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) =]] diff --git a/docs/discounts/discounts_api.md b/docs/discounts/discounts_api.md new file mode 100644 index 0000000000..c6ef554b98 --- /dev/null +++ b/docs/discounts/discounts_api.md @@ -0,0 +1,129 @@ +--- +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. + +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: + +- **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 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` | + +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. + +### 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. + +### 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 +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. + +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. + +### Example API usage + +The example below contains a Command creating a cart discount. The discount: + +- has the highest possible [priority](#priority) value +- [rule](#rules) deducts 10 EUR from the base price of the product +- is [permanent](#start-and-end-date) +- [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. +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). diff --git a/docs/discounts/discounts_guide.md b/docs/discounts/discounts_guide.md index 615d02c737..7c0364bb2f 100644 --- a/docs/discounts/discounts_guide.md +++ b/docs/discounts/discounts_guide.md @@ -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 @@ -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) diff --git a/mkdocs.yml b/mkdocs.yml index 20be1066a9..6cabd93a4c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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