PHPackages                             missionx-co/discounts-engine - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. missionx-co/discounts-engine

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

missionx-co/discounts-engine
============================

Flexible and developer-friendly solution for creating, managing, and validating discount rules in PHP application

0.5.1(1y ago)0145MITPHPPHP ^8.3

Since Dec 23Pushed 1y ago1 watchersCompare

[ Source](https://github.com/missionx-co/discounts-engine)[ Packagist](https://packagist.org/packages/missionx-co/discounts-engine)[ Docs](https://github.com/missionx-co/discounts-engine)[ RSS](/packages/missionx-co-discounts-engine/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (8)Dependencies (7)Versions (11)Used By (0)

Discounts Engine
================

[](#discounts-engine)

A simple and developer-friendly tool for creating, managing, and validating discount rules in PHP applications.

Installation
------------

[](#installation)

```
composer require missionx-co/discounts-engine
```

Usage
-----

[](#usage)

This package revolves around three main entities that you'll work with:

### 1. Item

[](#1-item)

Convert your products into Item objects. An Item has the following attributes:

1. `id`: A unique identifier for the item.
2. `name`: The name of the item.
3. `qty`: The quantity of the item.
4. `price`: The unit price of the item.
5. `type`: The type of the item (default is unknown). You can use this to categorize items, e.g., product, shipping, or tax.
6. `discount`: The initial discount applied to the item. This value is updated as discounts are applied by the engine.

### 2. Discount

[](#2-discount)

Define discount configurations to apply to your items.

**Example Items**

```
$items = [
    new Item(id: 1, name: 'Item 1', qty: 1, price: 100, type: 'product'),
    new Item(id: 2, name: 'Item 1', qty: 2, price: 50, type: 'product'),
    new Item(id: 3, name: 'Item 1', qty: 1, price: 5, type: 'shipping'),
];
```

- Total amount: `205`
- Total quantity: `4`

#### 2.1 Basic Discount Configuration

[](#21-basic-discount-configuration)

- `limitToItems`:Use this to specify which items the discount applies to. It accepts a callable to filter items. Example: Apply the discount only to items with type = product. In this case, the purchase amount will be 200 and the quantity will be 3.

    ```
    (new Discount)
    ->limitToItems(
        fn (array $items) => array_filter(
            $items,
            fn (Item $item) => $item->type == 'product'
        )
    )
    ```
- `priority`: When multiple discounts are processed, the engine will apply them in order of priority (highest to lowest).

    ```
    use MissionX\DiscountsEngine\Enums\DiscountPriority;

    (new Discount)
        ->priority(DiscountPriority::High)
    ```
- `minPurchaseAmount`: The discount applies only if the purchase amount is at least the specified minimum.

    ```
    (new Discount)->minPurchaseAmount(50)
    ```
- `minQty`: The discount applies only if the total quantity of applicable items meets or exceeds the minimum.

    ```
    (new Discount)->minQty(4)
    ```
- `combineWithOtherDiscounts`: Allow the discount to be combined with other discounts if the other discount allow combining as well.

    ```
    (new Discount)->combineWithOtherDiscounts()
    ```
- `forceCombineWithOtherDiscounts`: force the discount to be combined with other discounts even if the other discounts DOES NOT allow combining.

    ```
    (new Discount)->forceCombineWithOtherDiscounts()
    ```
- `isAutomatic`:

    ```
    (new Discount)->setIsAutomatic()
    ```

#### 2.2 Available Discount Types

[](#22-available-discount-types)

The `Discount` class is abstract, so you cannot instantiate it directly. This package provides three types of discount objects to handle most use cases.

##### 2.2.1 Amount Off Product

[](#221-amount-off-product)

This discount can apply to all applicable items or just a subset.

Example 1: Apply a %10 discount to all applicable items. The result of this discount is 20.

```
use MissionX\DiscountsEngine\Enums\DiscountType;

// the following configuration will be applied to all applicable items
(new AmountOffProductDiscount)
    ->amount(10)
    ->limitToItems(
        fn (array $items) => array_filter(
            $items,
            fn (Item $item) => $item->type == 'product'
        )
    )
```

Example 2: Apply a $10 fixed discount to a specific item (e.g., item with id = 2) only if the purchase amount exceeds $200.

```
// the following configuration will be applied to item with id=2 only
(new AmountOffProductDiscount)
    ->amount(10, DiscountType::FixedAmount)
    ->limitToItems(
        fn (array $items) => array_filter(
            $items,
            fn (Item $item) => $item->type == 'product'
        )
    )
    ->minPurchaseAmount(200)
    ->selectAffectedItemsUsing(
        fn (array $items) => array_filter(
            $items,
            fn (Item $item) => $item->id == 2
        )
    )
```

The result of the discount is 10

**Difference Between `limitToItems` and `selectAffectedItemsUsing`:**

- `limitToItems` Filters which items are considered when calculating the purchase amount and quantity. For example, you can exclude shipping fees from the basket total.
- `selectAffectedItemsUsing`: Specifies the exact items to which the discount will apply. For instance, if you provide a $10 fixed discount, you might apply it to only one specific item, not all applicable items.

In the example above:

- The purchase amount is calculated based on the items filtered by limitToItems.
- If the purchase amount exceeds $200, the discount is applied only to the items selected by `selectAffectedItemsUsing`.

##### 2.2.2 Amount Off Order

[](#222-amount-off-order)

Apply a discount to the total purchase amount, considering any item limitations if specified.

```
(new AmountOffOrderDiscount)
    ->amount(5)
    ->minPurchaseAmount(200)
    ->limitToItems(
        fn (array $items) => array_filter(
            $items,
            fn (Item $item) => $item->type == 'product'
        )
    )
```

In this example, the limitToItems method restricts the calculation to items of type product. As a result, the total purchase amount is 200, and the discount is 10 (5% of 200).

##### 2.2.3 Buy X Get Amount Off Y

[](#223-buy-x-get-amount-off-y)

This discount type applies a specified discount to certain items (`Y`) if the user purchases a specified items (`X`).

```
(new BuyXGetAmountOffOfYDiscount())
    ->amount(50, DiscountType::Percentage)
    ->limitToItems(
        fn (array $items) => array_filter(
            $items,
            fn (Item $item) => $item->type == 'product'
        )
    )
    ->hasX(
        fn (array $applicableItems) => array_filter(
            $applicableItems,
            fn(Item $item) => $item->id == 2 && $item->qty == 2
        )
    )
    ->getY(fn(array $applicableItems, array $items, Discount $discount) => [new YItem(itemId: 1, qty: 1)])
```

In this example:

1. If the cart contains 2 units of the item with id=2, the user qualifies for the discount.
2. The discount is 50% off on 1 unit of the item with id=1. In case item id=1 has more than 1 item, the savings will be applied only to 1 unit
3. The `limitToItems` ensures that only items of type product are considered for eligibility and discount application.

You can also simulate free shipping with this discount by applying a 100% discount to the shipping item.

```
(new BuyXGetAmountOffOfYDiscount())
    ->amount(100, DiscountType::Percentage)
    ->limitToItems(
        fn (array $items) => array_filter(
            $items,
            fn (Item $item) => $item->type == 'product'
        )
    )
    ->minPurchaseAmount(200)
    ->getY(fn(array $applicableItems, array $items, Discount $discount) => [new YItem(3)])
```

#### 2.3 Custom Discounts

[](#23-custom-discounts)

You can create your own discount just by extending the `Discount` abstract class.

### 3. Discounts Engine

[](#3-discounts-engine)

The final component to work with is the `DiscountsEngine`, which processes and applies discounts to items.

```
$discount = (new AmountOffOrderDiscount())->amount(20);
$discount2 = (new AmountOffOrderDiscount())->amount(10)->forceCombineWithOtherDiscounts();
$discount3 = (new AmountOffOrderDiscount())->amount(30)->combineWithOtherDiscounts();
// won't be applied because min purcahse amount is greater than the total amount
$discount4 = (new AmountOffOrderDiscount())->amount(20)->forceCombineWithOtherDiscounts()->minPurchaseAmount(300);

$group = (new DiscountsEngine)
    ->addDiscount($twentyPercentOff)
    ->addDiscount($twentyPercentOffLimited)
    ->addDiscount($amountOffProduct)
    ->process($this->items());

$group->savings(); // 82 (10% + 30%)
```

#### How the Discount Engine Applies Discounts

[](#how-the-discount-engine-applies-discounts)

1. **Step 1: Grouping Discounts**
    The discount engine processes discounts (that are not forced to combine) one by one and combines them with other discounts:

    - If a discount is **combinable**, it will be combined with:
        - All other combinable discounts.
        - Discounts that are **forced to combine**.
    - If a discount is **not combinable**, it will only be combined with discounts that are **forced to combine**.

    **Example**:
    Discounts in the above example will be grouped into two groups:

    - **Group 1**: Discount 1, Discount 2, and Discount 4.
    - **Group 2**: Discount 2, Discount 3, and Discount 4.
2. **Step 2: Sorting Discounts in Each Group**

    - Discounts in each group are split into two categories:
        1. **Automatic discounts**.
        2. **Non-automatic discounts**.
    - Each category is sorted by **priority** (from high to low).
    - The categories are then combined, with **automatic discounts placed first**.
3. **Step 3: Processing Items Across Groups**
    The discount engine processes items across all groups.
4. **Step 4: Selecting the Best Group**
    The group with the **highest savings** is selected.

###  Health Score

30

—

LowBetter than 64% of packages

Maintenance42

Moderate activity, may be stable

Popularity11

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~4 days

Total

10

Last Release

462d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/aeb75f35979097845cce704062910a6a8c85bc0a7b49b081df7350a4f20023fc?d=identicon)[manssour.mohammed](/maintainers/manssour.mohammed)

---

Top Contributors

[![mohammedmanssour](https://avatars.githubusercontent.com/u/19733629?v=4)](https://github.com/mohammedmanssour "mohammedmanssour (24 commits)")

---

Tags

missionx-codiscounts-engine

###  Code Quality

TestsPHPUnit

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/missionx-co-discounts-engine/health.svg)

```
[![Health](https://phpackages.com/badges/missionx-co-discounts-engine/health.svg)](https://phpackages.com/packages/missionx-co-discounts-engine)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
