-
Notifications
You must be signed in to change notification settings - Fork 86
[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
base: discounts-rest
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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()), | ||
]) | ||
->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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
## Criteria | ||||||
|
||||||
When using `update` or `delete` modes, you can use criteria to identify the objects to operate on. | ||||||
|
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
|
||||||||||||||||
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
|
||||||||||||||||
|
||||||||||||||||
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. | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
|
||||||||||||||||
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
|
||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
|
||||||||||||||||
- **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. | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 "rule": {
"type": "percentage",
"amount": 10
}, The "Conditions" table says for
It seems a bit more understandable on migration side. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, what I had in mind was: 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mnocon Lets be just straight.
Suggested change
|
||||||||||||||||
|
||||||||||||||||
### 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll add some
Suggested change
Same with the Conditions table. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It took me some time to find out where this limit is set…: |
||||||||||||||||
|
||||||||||||||||
### 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
|
||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
|
||||||||||||||||
### 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
|
||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
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
|
||||||||||||||||
|
||||||||||||||||
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
|
||||||||||||||||
|
||||||||||||||||
### Example API usage | ||||||||||||||||
|
||||||||||||||||
The example below contains a Command creating a cart discount. The discount: | ||||||||||||||||
|
||||||||||||||||
- has the highest possible priority value | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||||||||||||
- deducts 10 EUR from the base price of the product | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
- is permanent | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||||||||||||
Comment on lines
+113
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
|
||||||||||||||||
``` 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. | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
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). |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.