# Basket Validators

Basket validators are used to enforce business rules on basket items. They are configured via the `BASKET_VALIDATORS` dynamic setting and are executed automatically whenever items are added to the basket.

These validations help prevent invalid orders and ensure that business constraints are consistently applied before checkout.

## <mark style="color:red;">1. Overview</mark>

**Key Characteristics:**

* Validators run automatically after items are added to the basket
* Multiple validators can be configured and executed together
* Each validator supports localized error messages
* Validation errors are returned in the API response and block checkout

***

## <mark style="color:red;">2. Configuration Structure</mark>

Each entry in the `BASKET_VALIDATORS` setting follows the structure below:

```json
{
    "condition_klass": "omnishop.baskets.validator.ValidatorClassName",
    "kwargs": {
        "param1": "value1",
        "param2": "value2"
    },
    "message": {
        "en-us": "English error message",
        "tr-tr": "Turkish error message"
    }
}
```

<table><thead><tr><th width="171.8984375">Field</th><th width="94.609375">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>condition_klass</code></td><td>string</td><td>Validator identifier (see available validators below)</td></tr><tr><td><code>kwargs</code></td><td>dict</td><td>Parameters specific to the validator</td></tr><tr><td><code>message</code></td><td>dict</td><td>Localized error messages keyed by locale code (e.g., <code>en-us</code>, <code>tr-tr</code>)</td></tr></tbody></table>

{% hint style="warning" %}
If no message is defined for the current language, the validator’s default message is used.
{% endhint %}

***

## <mark style="color:red;">3. Available Validators</mark>

### <mark style="color:red;">3.1. Basket Item Quantity Validator</mark>

Validates the **total quantity** of basket items that share a specific attribute value. An error is triggered when the total quantity falls within a restricted range.

**Parameters:**

<table><thead><tr><th width="169.046875">Parameter</th><th width="118.08203125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>attribute_name</code></td><td>string</td><td>Product attribute key to check</td></tr><tr><td><code>attribute_value</code></td><td>string</td><td>Value to match</td></tr><tr><td><code>upper_limit</code></td><td>int</td><td>Upper bound of the restricted range</td></tr><tr><td><code>lower_limit</code></td><td>int</td><td>Lower bound of the restricted range</td></tr></tbody></table>

**Behavior:**

* Sums quantities of all basket items matching the given attribute
* **Fails** when `lower_limit <= total_quantity < upper_limit`
* **Passes** when `total_quantity < lower_limit` or `total_quantity >= upper_limit`

**Understanding the Logic:**

With `lower_limit=1, upper_limit=3` (meaning: "if the product is purchased, at least 3 units must be bought"):

<table><thead><tr><th width="116.9375">Quantity</th><th>Business Meaning</th><th>Result</th></tr></thead><tbody><tr><td>0</td><td>Not purchased</td><td>✅ PASS</td></tr><tr><td>1</td><td>Buying, but below minimum</td><td>❌ FAIL</td></tr><tr><td>2</td><td>Buying, but below minimum</td><td>❌ FAIL</td></tr><tr><td>3</td><td>Meets minimum</td><td>✅ PASS</td></tr><tr><td>4+</td><td>Above minimum</td><td>✅ PASS</td></tr></tbody></table>

{% hint style="info" %}
This validator enforces a “buy at least X or don’t buy at all” rule, which is common for wholesale or bulk products.
{% endhint %}

***

**Example 1 - B2B Wholesale: Minimum order quantity**

For B2B customers, require at least **10 wholesale units in total**:

```json
{
    "condition_klass": "omnishop.baskets.validator.BasketItemQuantityValidator",
    "kwargs": {
        "attribute_name": "sales_channel",
        "attribute_value": "wholesale",
        "upper_limit": 10,
        "lower_limit": 1
    },
    "message": {
        "en-us": "Wholesale items require minimum 10 units to order",
        "tr-tr": "Toptan ürünler için minimum 10 adet sipariş gereklidir"
    }
}
```

| Cart Contents                                | Total | Result |
| -------------------------------------------- | ----- | ------ |
| No wholesale items                           | 0     | ✅ PASS |
| 3x Product A (wholesale)                     | 3     | ❌ FAIL |
| 5x Product A + 5x Product B (both wholesale) | 10    | ✅ PASS |
| 12x Product A (wholesale)                    | 12    | ✅ PASS |

{% hint style="info" %}
This validator counts the **total quantity** across all products with the matching attribute, not per individual SKU.
{% endhint %}

***

**Example 2 - Promotional Campaign: Block out-of-campaign items**

During a special campaign, block items not included in the promotion:

```json
{
    "condition_klass": "omnishop.baskets.validator.BasketItemQuantityValidator",
    "kwargs": {
        "attribute_name": "campaign_excluded",
        "attribute_value": "true",
        "upper_limit": 999999,
        "lower_limit": 1
    },
    "message": {
        "en-us": "This item is not available during the current campaign",
        "tr-tr": "Bu ürün mevcut kampanya süresince satışa kapalıdır"
    }
}
```

| Quantity | Result |
| -------- | ------ |
| 0 items  | ✅ PASS |
| 1+ items | ❌ FAIL |

***

**Example 3 - Alcohol Regulation: Age-restricted products**

Block alcohol products in regions where online alcohol sales are prohibited:

```json
{
    "condition_klass": "omnishop.baskets.validator.BasketItemQuantityValidator",
    "kwargs": {
        "attribute_name": "product_category",
        "attribute_value": "alcohol",
        "upper_limit": 999999,
        "lower_limit": 1
    },
    "message": {
        "en-us": "Alcohol products cannot be purchased online in your region",
        "tr-tr": "Alkollü ürünler bölgenizde online olarak satın alınamaz"
    }
}
```

***

### <mark style="color:red;">3.2. Basket Item Base Code Quantity Validator</mark>

Similar to `BasketItemQuantityValidator`, but validates quantity limits **per product variant group** (base\_code). **This is ideal for flash sales and limited-edition products.**

**Parameters:**

<table><thead><tr><th width="178.1328125">Parameter</th><th width="121.16796875">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>attribute_name</code></td><td>string</td><td>Product attribute key to check</td></tr><tr><td><code>attribute_value</code></td><td>string</td><td>Value to match</td></tr><tr><td><code>upper_limit</code></td><td>int</td><td>Upper bound per base_code</td></tr><tr><td><code>lower_limit</code></td><td>int</td><td>Lower bound per base_code</td></tr></tbody></table>

**Behavior:**

* Groups basket items by base\_code (e.g., same product in different sizes/colors)
* **Triggers error** when `lower_limit <= quantity < upper_limit` for any base\_code

***

**Example 1 - Flash Sale: Max 2 per product (all sizes/colors combined)**

During a flash sale, limit customers to 2 units **total** per product (base\_code), regardless of which size/color they choose:

```json
{
    "condition_klass": "omnishop.baskets.validator.BasketItemBaseCodeQuantityValidator",
    "kwargs": {
        "attribute_name": "is_flash_sale",
        "attribute_value": "true",
        "upper_limit": 999999,
        "lower_limit": 3
    },
    "message": {
        "en-us": "Flash sale limit: Maximum 2 units allowed for product {}",
        "tr-tr": "Flash satış limiti: {} ürünü için maksimum 2 adet"
    }
}
```

**Scenario:** T-shirt (base\_code: TSHIRT-001) available in S, M, L sizes

<table><thead><tr><th>Cart Contents</th><th width="207.6640625">Total for base_code</th><th>Result</th></tr></thead><tbody><tr><td>1x Small</td><td>1</td><td>✅ PASS</td></tr><tr><td>1x Small + 1x Medium</td><td>2</td><td>✅ PASS</td></tr><tr><td>2x Small + 1x Medium</td><td>3</td><td>❌ FAIL</td></tr><tr><td>3x Small</td><td>3</td><td>❌ FAIL</td></tr></tbody></table>

{% hint style="warning" %}
This validator counts **all variants** (sizes/colors) under the same base\_code together, not per individual SKU.
{% endhint %}

***

**Example 2 - Limited Edition Sneakers: Max 1 per model**

For limited edition sneakers, allow only 1 unit per model (base\_code), regardless of size:

```json
{
    "condition_klass": "omnishop.baskets.validator.BasketItemBaseCodeQuantityValidator",
    "kwargs": {
        "attribute_name": "is_limited_edition",
        "attribute_value": "true",
        "upper_limit": 999999,
        "lower_limit": 2
    },
    "message": {
        "en-us": "Limited edition: Only 1 unit allowed per model ({})",
        "tr-tr": "Sınırlı üretim: Model başına sadece 1 adet izin verilmektedir ({})"
    }
}
```

**Scenario:** Sneaker model (base\_code: SNKR-AIR-001) available in sizes 40, 41, 42

| Cart Contents           | Total for base\_code | Result |
| ----------------------- | -------------------- | ------ |
| 1x Size 42              | 1                    | ✅ PASS |
| 1x Size 42 + 1x Size 43 | 2                    | ❌ FAIL |

{% hint style="info" %}
The error message supports `{}` placeholder which will be replaced with the base\_code.
{% endhint %}

***

### <mark style="color:red;">3.3. Basket Item Stepped Quantity Validator</mark>

Ensures that quantities are **multiples of a step value** and fall within defined minimum and maximum limits. All values are read from product attributes.

This validator is commonly used for grocery and bulk products.

**Parameters:**

<table><thead><tr><th width="245.56640625">Parameter</th><th width="107.453125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>attribute_name</code></td><td>string</td><td>Attribute holding the step value</td></tr><tr><td><code>upper_limit_attribute_name</code></td><td>string</td><td>Attribute holding the maximum quantity</td></tr><tr><td><code>lower_limit_attribute_name</code></td><td>string</td><td>Attribute holding the minimum quantity</td></tr></tbody></table>

**Behavior:**

* Reads step, minimum, and maximum from product attributes
* **Triggers error** if quantity is not a multiple of step or outside min/max range
* Products without the required attributes are skipped

***

**Example 1 - Grocery Store: Eggs sold in packs of 6**

Eggs can only be purchased in multiples of 6 (half dozen), minimum 6, maximum 30:

```json
{
    "condition_klass": "omnishop.baskets.validator.BasketItemSteppedQuantityValidator",
    "kwargs": {
        "attribute_name": "quantity_step",
        "upper_limit_attribute_name": "max_quantity",
        "lower_limit_attribute_name": "min_quantity"
    },
    "message": {
        "en-us": "This product must be purchased in packs of {step} (min: {lower_limit}, max: {upper_limit})",
        "tr-tr": "Bu ürün {step}'li paketler halinde satılmaktadır (min: {lower_limit}, maks: {upper_limit})"
    }
}
```

**Product Attributes for Eggs:**

```json
{
    "quantity_step": "6",
    "min_quantity": "6",
    "max_quantity": "30"
}
```

<table><thead><tr><th width="220.859375">Quantity</th><th>Result</th></tr></thead><tbody><tr><td>3</td><td>❌ FAIL (not multiple of 6)</td></tr><tr><td>6</td><td>✅ PASS</td></tr><tr><td>7</td><td>❌ FAIL (not multiple of 6)</td></tr><tr><td>12</td><td>✅ PASS</td></tr><tr><td>36</td><td>❌ FAIL (exceeds max 30)</td></tr></tbody></table>

***

**Example 2 - Bulk Store: Water bottles sold in cases**

Water bottles sold in cases of 12, minimum 12, maximum 96:

**Product Attributes for Water Bottles:**

```json
{
    "quantity_step": "12",
    "min_quantity": "12",
    "max_quantity": "96"
}
```

***

**Example 3 - Wholesale: Bulk flour by 5kg bags**

Flour sold in 5kg increments, minimum 10kg, maximum 100kg:

**Product Attributes for Bulk Flour:**

```json
{
    "quantity_step": "5",
    "min_quantity": "10",
    "max_quantity": "100"
}
```

***

### <mark style="color:red;">3.4. Attribute Validator</mark>

Validates that product attribute matches an expected values. This validator is useful for enforcing conditional business rules.

**Parameters:**

<table><thead><tr><th width="264.05859375">Parameter</th><th width="97.640625">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>attribute_name</code></td><td>string</td><td>Product attribute to check</td></tr><tr><td><code>expected_value</code></td><td>string</td><td>Expected value for the attribute</td></tr><tr><td><code>disabled_on_sub_basket_items</code></td><td>bool</td><td>If <code>true</code>, skip validation for sub-items (bundle components)</td></tr></tbody></table>

**Behavior:**

* Products without the attribute pass validation(skipped)
* Products with matching attribute value pass
* Products with different attribute value fail

***

**Example 1 - Miscellaneous Products: Block standalone purchase**

Accessories and add-on items (gift wrapping, extended warranty, installation service) that can only be purchased with a main product:

```json
{
    "condition_klass": "omnishop.baskets.validator.AttributeValidator",
    "kwargs": {
        "attribute_name": "cannot_be_sold_alone",
        "expected_value": "false",
        "disabled_on_sub_basket_items": true
    },
    "message": {
        "en-us": "This item can only be purchased together with a main product",
        "tr-tr": "Bu ürün sadece ana ürünle birlikte satın alınabilir"
    }
}
```

| Product Attribute                   | Outcome                             |
| ----------------------------------- | ----------------------------------- |
| No `cannot_be_sold_alone` attribute | ✅ PASS (regular product)            |
| `cannot_be_sold_alone: "false"`     | ✅ PASS                              |
| `cannot_be_sold_alone: "true"`      | ❌ FAIL (addon without main product) |

{% hint style="warning" %}
Set `disabled_on_sub_basket_items: true` to allow these items as bundle components while blocking standalone purchase.
{% endhint %}

***

**Example 2 - Pre-order Products: Block if not accepting pre-orders**

Block pre-order items when pre-order period has ended:

```json
{
    "condition_klass": "omnishop.baskets.validator.AttributeValidator",
    "kwargs": {
        "attribute_name": "is_preorder",
        "expected_value": "false",
        "disabled_on_sub_basket_items": false
    },
    "message": {
        "en-us": "Pre-order period has ended for this product",
        "tr-tr": "Bu ürün için ön sipariş dönemi sona ermiştir"
    }
}
```

***

**Example 3 - Subscription Products: Require active subscription**

Block subscription-only products for non-subscribers:

```json
{
    "condition_klass": "omnishop.baskets.validator.AttributeValidator",
    "kwargs": {
        "attribute_name": "requires_subscription",
        "expected_value": "false",
        "disabled_on_sub_basket_items": false
    },
    "message": {
        "en-us": "This product is only available for subscribers",
        "tr-tr": "Bu ürün sadece abonelere özeldir"
    }
}
```

***

### <mark style="color:red;">3.5. Single Data Source Validator</mark>

Ensures all basket items are from the same seller/data source. **Essential for marketplace platforms with single-seller checkout.**

**Parameters:** None required (empty `kwargs: {}`)

**Behavior:**

* **Triggers error** if basket contains items from different sellers
* Empty baskets pass validation

**Use Case - Marketplace Single-Seller Checkout:**

Many marketplaces require orders to be fulfilled by a single seller for:

* Simplified logistics and shipping
* Consistent delivery experience
* Easier returns and customer service

```json
{
    "condition_klass": "omnishop.baskets.validator.SingleDataSourceValidator",
    "kwargs": {},
    "message": {
        "en-us": "Your cart contains products from different sellers. Please complete separate orders for each seller.",
        "tr-tr": "Sepetiniz farklı satıcılardan ürün içermektedir. Lütfen her satıcı için ayrı sipariş oluşturun."
    }
}
```

**Scenario:**

* Customer adds Product A from Seller X → ✅ PASS
* Customer adds Product B from Seller X → ✅ PASS (same seller)
* Customer adds Product C from Seller Y → ❌ FAIL (different seller)

***

## <mark style="color:red;">4. Default Error Messages</mark>

Each validator has a built-in default error message used when no localized message is configured:

| Validator                             | Default Message                                                                                                      |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `BasketItemQuantityValidator`         | "Product quantity exceeded"                                                                                          |
| `BasketItemBaseCodeQuantityValidator` | "Base code {} quantity exceeded"                                                                                     |
| `BasketItemSteppedQuantityValidator`  | "Quantity must be multiple of {step} and between {lower\_limit} and {upper\_limit}"                                  |
| `AttributeValidator`                  | "{attribute\_name} must be {expected\_value} but it is {attribute\_value}"                                           |
| `SingleDataSourceValidator`           | "Your cart cannot contain products from different sellers. If you wish to add this product, please empty your cart." |
