Skip to content

Commit b58be1d

Browse files
committed
[Discounts] Described Discounts API
1 parent eed2aed commit b58be1d

File tree

6 files changed

+223
-3
lines changed

6 files changed

+223
-3
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Command;
6+
7+
use Ibexa\Contracts\Core\Collection\ArrayMap;
8+
use Ibexa\Contracts\Core\Repository\PermissionResolver;
9+
use Ibexa\Contracts\Core\Repository\UserService;
10+
use Ibexa\Contracts\Discounts\DiscountServiceInterface;
11+
use Ibexa\Contracts\Discounts\Value\DiscountType;
12+
use Ibexa\Contracts\Discounts\Value\Struct\DiscountCreateStruct;
13+
use Ibexa\Contracts\Discounts\Value\Struct\DiscountTranslationStruct;
14+
use Ibexa\Contracts\DiscountsCodes\DiscountCodeServiceInterface;
15+
use Ibexa\Contracts\DiscountsCodes\Value\Struct\DiscountCodeCreateStruct;
16+
use Ibexa\Discounts\Value\DiscountCondition\IsInCurrency;
17+
use Ibexa\Discounts\Value\DiscountCondition\IsInRegions;
18+
use Ibexa\Discounts\Value\DiscountCondition\IsProductInArray;
19+
use Ibexa\Discounts\Value\DiscountRule\FixedAmount;
20+
use Ibexa\DiscountsCodes\Value\DiscountCondition\IsValidDiscountCode;
21+
use Symfony\Component\Console\Command\Command;
22+
use Symfony\Component\Console\Input\InputInterface;
23+
use Symfony\Component\Console\Output\OutputInterface;
24+
25+
final class ManageDiscountsCommand extends Command
26+
{
27+
protected static $defaultName = 'discounts:manage';
28+
29+
private DiscountServiceInterface $discountService;
30+
31+
private DiscountCodeServiceInterface $discountCodeService;
32+
33+
private PermissionResolver $permissionResolver;
34+
35+
private UserService $userService;
36+
37+
public function __construct(UserService $userSerice, PermissionResolver $permissionResolver, DiscountServiceInterface $discountService, DiscountCodeServiceInterface $discountCodeService)
38+
{
39+
$this->userService = $userSerice;
40+
$this->discountService = $discountService;
41+
$this->discountCodeService = $discountCodeService;
42+
$this->permissionResolver = $permissionResolver;
43+
44+
parent::__construct();
45+
}
46+
47+
protected function execute(InputInterface $input, OutputInterface $output): int
48+
{
49+
$this->permissionResolver->setCurrentUserReference($this->userService->loadUserByLogin('admin'));
50+
51+
$now = new \DateTimeImmutable();
52+
53+
$discountCodeCreateStruct = new DiscountCodeCreateStruct(
54+
'summer10',
55+
null, // Unlimited usage
56+
$this->permissionResolver->getCurrentUserReference()->getUserId(),
57+
$now
58+
);
59+
$discountCode = $this->discountCodeService->createDiscountCode($discountCodeCreateStruct);
60+
61+
$discountCreateStruct = new DiscountCreateStruct();
62+
$discountCreateStruct->setIdentifier('discount_identifier')
63+
->setType(DiscountType::CART)
64+
->setPriority(10)
65+
->setEnabled(true)
66+
->setUser($this->userService->loadUserByLogin('admin'))
67+
->setRule(new FixedAmount(10))
68+
->setStartDate($now)
69+
->setConditions([
70+
new IsInRegions(['germany', 'france']),
71+
new IsProductInArray(['product-1', 'product-2']),
72+
new IsInCurrency('EUR'),
73+
new IsValidDiscountCode($discountCode->getCode(), $discountCode->getUsedLimit()),
74+
])
75+
->setTranslations([
76+
new DiscountTranslationStruct('eng-GB', 'Discount name', 'This is a discount description', 'Promotion Label', 'Promotion Description'),
77+
new DiscountTranslationStruct('ger-DE', 'Discount name (German)', 'Description (German)', 'Promotion Label (German)', 'Promotion Description (German)'),
78+
])
79+
->setEndDate(null) // Permanent discount
80+
->setCreatedAt($now)
81+
->setUpdatedAt($now)
82+
->setContext(new ArrayMap(['custom_context' => 'custom_value']));
83+
84+
$this->discountService->createDiscount($discountCreateStruct);
85+
86+
return Command::SUCCESS;
87+
}
88+
}

docs/content_management/data_migration/importing_data.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,8 @@ The provided conditions overwrite any already existing ones.
522522
[[= include_file('code_samples/data_migration/examples/discounts/discount_update.yaml') =]]
523523
```
524524

525+
For a list of available conditions, see [Discounts API](discounts_api.md).
526+
525527
## Criteria
526528

527529
When using `update` or `delete` modes, you can use criteria to identify the objects to operate on.

docs/discounts/discounts.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ You can also extend the feature, for example, by creating custom pricing rules,
1616

1717
[[= cards([
1818
"discounts/discounts_guide",
19-
"discounts/install_discounts"
20-
], columns=4) =]]
19+
"discounts/install_discounts",
20+
"discounts/discounts_api"
21+
], columns=3) =]]

docs/discounts/discounts_api.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
---
2+
description: Discounts LTS Update enables reducing prices on products or product categories based on a detailed logic resolution.
3+
month_change: true
4+
editions:
5+
- lts-update
6+
- commerce
7+
---
8+
9+
# Discounts API
10+
11+
## Manage discounts and discount codes
12+
13+
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.
14+
15+
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.
16+
17+
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.
18+
19+
The core concepts when working with discounts through the APIs are listed below.
20+
21+
### Types
22+
23+
When using the PHP API, the discount type defines where the discount can be applied.
24+
25+
Discounts are applied in two places, listed in the [DiscountType](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountType.html) class:
26+
27+
- **Product catalog** - `catalog` discounts are activated when browsing the product catalog and do not require any action from the customer to be activated
28+
- **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
29+
30+
Regardless of activation place, discounts always apply to products and reduce their base price.
31+
32+
To define when a discount activates and how the price is reduced, use rules and conditions.
33+
They make use of the [Symfony Expression language]([[= symfony_doc=]]//components/expression_language.html).
34+
Use the expression values provided below when using data migrations or the REST API to pass the right values.
35+
36+
### Rules
37+
38+
Discount rules define how the calculate the price reduction.
39+
The following discount rule types are available:
40+
41+
| Rule type | Identifier | Description | Expression value |
42+
|---|---|---|---|
43+
| `Ibexa\Discounts\Value\DiscountRule\FixedAmount` | `fixed_amount` | Deducts the specified amount, for example 10 EUR, from the base price | `discount_amount` |
44+
| `Ibexa\Discounts\Value\DiscountRule\Percentage` | `percentage` | Deducts the specified percentage, for example -10%, from the base price | `discount_percentage` |
45+
46+
Only a single discount can be applied to a given product, and a discount can only have a single rule.
47+
48+
You can create your own rules by creating a class implementing the [DiscountRuleInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountRuleInterface.html) interface.
49+
50+
### Conditions
51+
52+
With conditions you can narrow down the scenarios in which the discount applies. The following conditions are available:
53+
54+
| Condition | Applies to | Identifier | Description | Expression values |
55+
|---|---|---|---|---|
56+
| `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` |
57+
| `Ibexa\Discounts\Value\DiscountCondition\IsInCurrency` | Cart, Catalog |`is_in_currency` | Checks if the product has price in the specified currency | `currency_code` |
58+
| `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` |
59+
| `Ibexa\Discounts\Value\DiscountCondition\IsProductInArray` | Cart, Catalog| `is_product_in_array` | Checks if the product belongs to the group of selected products | `product_codes` |
60+
| `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` |
61+
| `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` |
62+
| `Ibexa\Discounts\Value\DiscountCondition\MinimumPurchaseAmount` | Cart | `minimum_purchase_amount` | Checks if purchase amount in the cart exceeds the specified minimum | `minimum_purchase_amount` |
63+
| `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` |
64+
65+
When multiple conditions are specified, all of them must be met.
66+
67+
You can create your own conditions by creating a class implementing the [DiscountConditionInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountConditionInterface.html) interface.
68+
69+
### Priority
70+
71+
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.
72+
73+
### Start and end date
74+
75+
Discounts can be permanent, or valid only in specified time frame.
76+
77+
Every discount has a start date, which defaults to the date when the discount was created.
78+
The end date can be set to `null` to make the discount permanent.
79+
80+
### Discount translations
81+
82+
The discount has four properties that can be translated:
83+
84+
| Property | Usage |
85+
|---|---|
86+
| Name | Internal information for store managers |
87+
| Description | Internal information for store managers |
88+
| Promotion label | Information displayed to customers |
89+
| Promotion description | Information displayed to customers |
90+
91+
Use the [DiscountTranslationStruct](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-DiscountTranslationStruct.html) to provide translations for discounts.
92+
93+
### Discount codes
94+
95+
To activate a cart discount only after a proper discount code is provided, you need to:
96+
97+
1. Create a discount code using the [DiscountCodeServiceInterface::createDiscountCode()](/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-DiscountCodeServiceInterface.html#method_createDiscountCode) method
98+
1. Attach it to a discount by using the `IsValidDiscountCode` condition
99+
100+
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.
101+
102+
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.
103+
104+
### Example API usage
105+
106+
The example below contains a Command creating a cart discount. The discount:
107+
108+
- has the highest possible priority value
109+
- deducts 10 EUR from the base price of the product
110+
- is permanent
111+
- is valid in Germany and France
112+
- applies to 2 products
113+
- requires a `summer10` discount code to be activated. The code can be used unlimited number of times
114+
115+
``` php hl_lines="53-59 61-84"
116+
[[= include_file('code_samples/discounts/src/Command/ManageDiscountsCommand.php') =]]
117+
```
118+
119+
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.
120+
121+
## Search
122+
123+
You can search for Discounts using the [`DiscountServiceInterface::findDiscounts()](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_findDiscounts) method.
124+
To learn more about the available search options, see Discounts' Search Criteria and Sort Clauses.
125+
126+
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).

docs/discounts/discounts_guide.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
description: Discounts LTS Update enables reducing prices on products or product categories based on a detailed logic resolution.
3-
month_change: false
3+
month_change: true
44
editions:
55
- lts-update
66
- commerce
@@ -55,6 +55,8 @@ Discounts are applied in two places:
5555

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

58+
#### Discounts priority
59+
5860
When two or more discounts can be applied to a single product, the system evaluates the following properties to choose the right one:
5961

6062
- discount activation place (cart discounts rank higher over catalog discounts)

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ nav:
406406
- Discounts: discounts/discounts.md
407407
- Discounts guide: discounts/discounts_guide.md
408408
- Install Discounts: discounts/install_discounts.md
409+
- Discounts API: discounts/discounts_api.md
409410
- Customer management:
410411
- Customer Portal: customer_management/customer_portal.md
411412
- Customer Portal guide: customer_management/customer_portal_guide.md

0 commit comments

Comments
 (0)