С переходом на новую платформу интернет-магазина Битрикс D7 в службах доставки появились ограничения, которые позволяют ограничить действие доставки местоположениями, стоимостью или весом заказа и т.д.: Пример ограничений доставки в Битрикс D7

Изначально доступны несколько типов ограничений: Стандартные типы ограничений в Битрикс D7

Но вы можете дополнить стандартный набор ограничений своими собственными. Для этого используется событие onSaleDeliveryRestrictionsClassNamesBuildList:

$eventManager = \Bitrix\Main\EventManager::getInstance();
$eventManager->addEventHandler(
    'sale',
    'onSaleDeliveryRestrictionsClassNamesBuildList',
    'myDeliveryRestrictions'
);

function myDeliveryRestrictions()
{
    return new \Bitrix\Main\EventResult(
        \Bitrix\Main\EventResult::SUCCESS,
        array(
            '\MyDeliveryRestriction' => '/path/to/your/class/restriction.php',
        )
    );
}

Минимальный класс ограничения службы доставки

Класс ограничения службы доставки должен быть унаследован от базового класса Bitrix\Sale\Delivery\Restrictions\Base. Но в папке /bitrix/modules/sale/lib/delivery/restrictions/ можно найти классы стандартных ограничений и унаследоваться от них при необходимости.

class MyRestriction extends Bitrix\Sale\Delivery\Restrictions\Base
{
    public static function getClassTitle()
    {
        return 'Моё собственное ограничение';
    }

    public static function getClassDescription()
    {
        return 'Моё собственное ограничение для службы доставки';
    }

    protected static function extractParams(Bitrix\Sale\Shipment $shipment)
    {
        return null;
    }

    public static function getParamsStructure($deliveryId = 0)
    {
        return array();
    }

    public static function check($shipmentParams, array $restrictionParams, $deliveryId = 0)
    {
        return true;
    }
}

Методы getClassTitle и getClassDescription возвращают название и описание ограничения.

Метод extractParams принимает объект отгрузки Bitrix\Sale\Shipment и должен подготовить необходимые данные для проверки ограничения и вернуть их. Далее эти данные передаются в метод check. В самом простом случае можно вернуть null. $shipment - объект отгрузки, для которой идёт подбор подходящих служб доставки. Примеры того, что можно получить из этого объекта:

protected static function extractParams(Bitrix\Sale\Shipment $shipment)
{
    $someShipmentParams = array();

    // Получаем товары в корзине:
    foreach ($shipment->getShipmentItemCollection() as $shipmentItem) {
        /** @var \Bitrix\Sale\BasketItem $basketItem - запись в корзине*/
        $basketItem = $shipmentItem->getBasketItem();
        // ...
    }

    // Получаем информацию о заказе:
    /** @var \Bitrix\Sale\ShipmentCollection $collection - коллекция всех отгрузок в заказе */
    $collection = $shipment->getCollection();
    /** @var \Bitrix\Sale\Order $order - объект заказа*/
    $order = $collection->getOrder();

    // Получаем выбранные оплаты:
    /** @var \Bitrix\Sale\Payment $payment - объект оплаты */
    foreach($order->getPaymentCollection() as $payment) {
        /** @var int $paySystemId - ID способа оплаты*/
        $paySystemId = $payment->getPaymentSystemId();
        // ...
        $someShipmentParams["paySystem"] = $paySystemId;
    }
    //...

    return $someShipmentParams;
}

Как работать с объектами корзины и заказа описано в соответствующих статьях: Объект корзины D7, Объект заказа D7.

Метод getParamsStructure должен возвращать массив параметров ограничения. Если ограничения не требует настройки, то возвращается пустой массив. Для примера допустим у ограничения есть три параметра:

public static function getParamsStructure($deliveryId = 0)
{
    return array(
        "MY_PARAM_CHECKBOX" => array(
            'TYPE' => 'Y/N',
            'VALUE' => 'Y',
            'LABEL' => 'Галочка',
        ),
        "MY_PARAM_ENUM" => array(
            "TYPE" => "ENUM",
            'MULTIPLE' => 'Y',
            "OPTIONS" => array(1 => "Первый вариант", 2 => "Второй вариант"),
            "LABEL" => 'Список',
        ),
        "MY_PARAM_NUMBER" => array(
            'TYPE' => 'NUMBER',
            'DEFAULT' => "0",
            'MIN' => 0,
            'LABEL' => 'Число',
        ),
    );
}

Метод check выполняет основную работу и должен возвращать true, если данная служба доставки доступна при данных параметрах. Первый аргумент - $shipmentParams - параметры отгрузки, те самые, которые вернул метод extractParams. Второй аргумент - $restrictionParams - параметры ограничения. Третий аргумент - $deliveryId - ID службы доставки. Например, extractParams вернул массив с ИД платежной системы, а в MY_PARAM_NUMBER пользователь ограничивает доставку необходимым ИД:

public static function check($shipmentParams, $restrictionParams, $deliveryId = 0)
{
    if (intval($deliveryId) <= 0) {
        return true;
    }

    return $restrictionParams["MY_PARAM_NUMBER"] == $shipmentParams["paySystem"];
}

Дополнительные возможности

В классе ограничения мы можем определить индекс сортировки, по умолчанию - 100. Чем выше индекс, тем позже это ограничение будет проверяться. Поэтому рекомендуется использовать индекс 100 для ограничений с простыми проверками (сравнение параметра с полем из заказа), индекс 200 для ограничений с проверками, содержащими запросы в БД и индекс 300 для ограничений с проверками, включающими тяжелые запросы и вычисления.

public static $easeSort = 100;

Если вы планируете значения параметров в своей таблице (сомнительная возможность, но может пригодиться), то можно переопределить следующие методы:

// Получение значений параметров
public static function prepareParamsValues(array $paramsValues, $deliveryId = 0)
{
    // Достаём откуда-то значения
    $paramsValues = array("MY_PARAM" => $value);

    return $paramsValues;
}
// Сохранение ограничения
public static function save(array $fields, $restrictionId = 0)
{
    $params = $fields["PARAMS"];
    $fields["PARAMS"] = array();

    $result = parent::save($fields, $restrictionId);
    // Сохраняем куда надо
    self::saveToYourPlace($fields["SERVICE_ID"], $params); // Здесь может быть ваш метод или код

    return $result;
}
// Удаление ограничения
public static function delete($restrictionId, $deliveryId = 0)
{
    self::deleteFromYourPlace($fields["SERVICE_ID"]); // Здесь может быть ваш метод или код

    return parent::delete($restrictionId);
}

Эти методы переопределены, например, в ограничениях по местоположению. Как известно, ограничение по местоположению раньше было отдельной опцией и доставки и хранилось в отдельной таблице b_sale_delivery2location. В новых ограничениях с целью совместимости данная таблица еще существует и новое ограничение реализовано переопределением prepareParamsValues, save и delete.

Бонус

Кстати, об ограничении по местоположению. Если у вас много местоположений, и требуется ограничение "Все местоположения, кроме" можете воспользоваться моим вариантом. Это немного изменённое стандартное ограничение по местоположению:

// /bitrix/php_interface/init.php:
$eventManager = \Bitrix\Main\EventManager::getInstance();
$eventManager->addEventHandler(
    'sale',
    'onSaleDeliveryRestrictionsClassNamesBuildList',
    'myDeliveryRestrictions'
);

function myDeliveryRestrictions()
{
    return new \Bitrix\Main\EventResult(
        \Bitrix\Main\EventResult::SUCCESS,
        array(
            '\ExclLocationsDeliveryRestriction' => '/bitrix/php_interface/include/ExclLocationsDeliveryRestriction.php',
        )
    );
}

// /bitrix/php_interface/include/ExclLocationsDeliveryRestriction.php:

use Bitrix\Sale\Delivery\DeliveryLocationTable,
    Bitrix\Sale\Internals\CollectableEntity,
    Bitrix\Sale\Shipment;

class ExclLocationsDeliveryRestriction extends \Bitrix\Sale\Delivery\Restrictions\Base
{
    public static $easeSort = 200;

    public static function getClassTitle()
    {
        return 'По местоположению (Все, кроме)';
    }

    public static function getClassDescription()
    {
        return 'По местоположению (Все, кроме)';
    }

    public static function check($locationCode, array $restrictionParams, $deliveryId = 0)
    {
        if (intval($deliveryId) <= 0) {
            return true;
        }

        if (strlen($locationCode) <= 0) {
            return false;
        }

        try {
            return !DeliveryLocationTable::checkConnectionExists(
                intval($deliveryId),
                $locationCode,
                array(
                    'LOCATION_LINK_TYPE' => 'AUTO'
                )
            );
        }
        catch(\Bitrix\Sale\Location\Tree\NodeNotFoundException $e) {
            return false;
        }
    }

    protected static function extractParams(CollectableEntity $shipment)
    {
        $order = $shipment->getCollection()->getOrder();

        if (!$props = $order->getPropertyCollection()) {
            return '';
        }

        if (!$locationProp = $props->getDeliveryLocation()) {
            return '';
        }

        if (!$locationCode = $locationProp->getValue()) {
            return '';
        }

        return $locationCode;
    }

    protected static function prepareParamsForSaving(array $params = array(), $deliveryId = 0)
    {
        if ($deliveryId > 0) {
            $arLocation = array();

            if (!!\CSaleLocation::isLocationProEnabled()) {
                if (strlen($params["LOCATION"]['L'])) {
                    $LOCATION1 = explode(':', $params["LOCATION"]['L']);
                }

                if (strlen($params["LOCATION"]['G'])) {
                    $LOCATION2 = explode(':', $params["LOCATION"]['G']);
                }
            }

            if (isset($LOCATION1) && is_array($LOCATION1) && count($LOCATION1) > 0) {
                $arLocation["L"] = array();
                $locationCount = count($LOCATION1);

                for ($i = 0; $i < $locationCount; $i++) {
                    if (strlen($LOCATION1[$i])) {
                        $arLocation["L"][] = $LOCATION1[$i];
                    }
                }
            }

            if (isset($LOCATION2) && is_array($LOCATION2) && count($LOCATION2) > 0) {
                $arLocation["G"] = array();
                $locationCount = count($LOCATION2);

                for ($i = 0; $i < $locationCount; $i++) {
                    if (strlen($LOCATION2[$i])) {
                        $arLocation["G"][] = $LOCATION2[$i];
                    }
                }

            }

            DeliveryLocationTable::resetMultipleForOwner($deliveryId, $arLocation);
        }

        return array();
    }

    public static function getParamsStructure($deliveryId = 0)
    {

        $result =  array(
            "LOCATION" => array(
                "TYPE" => "LOCATION_MULTI"
            ),
        );

        if ($deliveryId > 0) {
            $result["LOCATION"]["DELIVERY_ID"] = $deliveryId;
        }

        return $result;
    }

    public static function save(array $fields, $restrictionId = 0)
    {
        $fields["PARAMS"] = self::prepareParamsForSaving($fields["PARAMS"], $fields["SERVICE_ID"]);

        return parent::save($fields, $restrictionId);
    }

    public static function delete($restrictionId, $deliveryId = 0)
    {
        DeliveryLocationTable::resetMultipleForOwner($deliveryId);

        return parent::delete($restrictionId);
    }
}