# 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." |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.akinon.com/technical-guides/commerce/basket-validators.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
