Skip to content

Commit ca47e18

Browse files
mnoconadriendupuis
andauthored
[Discounts] Described Discounts API (#2783)
* [Discounts] Described Discounts API * Apply suggestions from code review --------- Co-authored-by: Adrien Dupuis <[email protected]>
1 parent 9526b85 commit ca47e18

File tree

6 files changed

+234
-3
lines changed

6 files changed

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

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#conditions).
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: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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 when parsing REST API responses.
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` | <nobr>`fixed_amount`</nobr> | Deducts the specified amount, for example <nobr>10 EUR</nobr>, from the base price | <nobr>`discount_amount`</nobr> |
44+
| `Ibexa\Discounts\Value\DiscountRule\Percentage` | <nobr>`percentage`</nobr> | Deducts the specified percentage, for example -10%, from the base price | <nobr>`discount_percentage`</nobr> |
45+
46+
Only a single discount can be applied to a given product, and a discount can only have a single rule.
47+
48+
### Conditions
49+
50+
With conditions you can narrow down the scenarios in which the discount applies. The following conditions are available:
51+
52+
| Condition | Applies to | Identifier | Description | Expression values |
53+
|---|---|---|---|---|
54+
| `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` |
55+
| `Ibexa\Discounts\Value\DiscountCondition\IsInCurrency` | Cart, Catalog |`is_in_currency` | Checks if the product has price in the specified currency | `currency_code` |
56+
| `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` |
57+
| `Ibexa\Discounts\Value\DiscountCondition\IsProductInArray` | Cart, Catalog| `is_product_in_array` | Checks if the product belongs to the group of selected products | `product_codes` |
58+
| `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` |
59+
| `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` |
60+
| `Ibexa\Discounts\Value\DiscountCondition\MinimumPurchaseAmount` | Cart | `minimum_purchase_amount` | Checks if purchase amount in the cart exceeds the specified minimum | `minimum_purchase_amount` |
61+
| `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` |
62+
63+
When multiple conditions are specified, all of them must be met.
64+
65+
### Priority
66+
67+
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.
68+
69+
### Start and end date
70+
71+
Discounts can be permanent, or valid only in a specified time frame.
72+
73+
Every discount has a start date, which defaults to the date when the discount was created.
74+
The end date can be set to `null` to make the discount permanent.
75+
76+
### Status
77+
78+
You can disable a discount anytime to stop it from being active, even if the conditions enforced by start and end date are met.
79+
80+
Only disabled discounts can be deleted.
81+
82+
### Discount translations
83+
84+
The discount has four properties that can be translated:
85+
86+
| Property | Usage |
87+
|---|---|
88+
| Name | Internal information for store managers |
89+
| Description | Internal information for store managers |
90+
| Promotion label | Information displayed to customers |
91+
| Promotion description | Information displayed to customers |
92+
93+
Use the [`DiscountTranslationStruct`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Struct-DiscountTranslationStruct.html) to provide translations for discounts.
94+
95+
### Discount codes
96+
97+
To activate a cart discount only after a proper discount code is provided, you need to:
98+
99+
1. Create a discount code using the [`DiscountCodeServiceInterface::createDiscountCode()`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-DiscountsCodes-DiscountCodeServiceInterface.html#method_createDiscountCode) method
100+
1. Attach it to a discount by using the `IsValidDiscountCode` condition
101+
102+
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.
103+
104+
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.
105+
106+
### Example API usage
107+
108+
The example below contains a Command creating a cart discount. The discount:
109+
110+
- has the highest possible [priority](#priority) value
111+
- [rule](#rules) deducts 10 EUR from the base price of the product
112+
- is [permanent](#start-and-end-date)
113+
- [depends](#conditions) on
114+
- being bought from Germany or France
115+
- 2 products
116+
- a `summer10` [discount code](#discount-codes) which can be used unlimited number of times
117+
118+
``` php hl_lines="60-66 68-92"
119+
[[= include_file('code_samples/discounts/src/Command/ManageDiscountsCommand.php') =]]
120+
```
121+
122+
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).
123+
124+
## Search
125+
126+
You can search for Discounts using the [`DiscountServiceInterface::findDiscounts()`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html#method_findDiscounts) method.
127+
To learn more about the available search options, see Discounts' [Search Criteria](discounts_criteria.md) and [Sort Clauses](discounts_sort_clauses.md).
128+
129+
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
@@ -407,6 +407,7 @@ nav:
407407
- Discounts: discounts/discounts.md
408408
- Discounts guide: discounts/discounts_guide.md
409409
- Install Discounts: discounts/install_discounts.md
410+
- Discounts API: discounts/discounts_api.md
410411
- Customer management:
411412
- Customer Portal: customer_management/customer_portal.md
412413
- Customer Portal guide: customer_management/customer_portal_guide.md

0 commit comments

Comments
 (0)