Организация собственных скидок в Битрикс

Допустим, мы хотим разработать свою систему скидок, которая не укладывается в рамки Битрикса. Например, добавить региональную скидку, которая зависит от места доставки (для каждого места - своя скидка в процентах).

Штатная система скидок предполагает только скидки на товар и скидки на товарную группу.

В этом случае есть смысл хранить свои скидки в отдельно созданных таблицах, а часть бизнес-логики - переписать, делегируя функции скидок классу MyDiscount:

  • изменить цены и скидки при выводе в каталоге
  • менять скидку при добавлении в корзину своим обработчиком
  • хранить скидку на корзину в отдельной таблице (баг «пересчета» скидок)

Прежнюю систему скидок Битрикс необходимо удалить (иначе в некоторых местах скидки будут считаться дважды):

TRUNCATE b_catalog_discount;
TRUNCATE b_catalog_discount_cond;

Скидки в каталоге товаров

Создаем класс MyDiscount и программируем в нем бизнес-логику, которая выбирает скидки из отдельной таблицы. Новые цены и скидки перед выводом в каталоге можно изменить так:

$arResult["ITEMS"] = MyDiscount::setDiscount($arResult["ITEMS"]);

Добавление скидки в корзину

Обработчик скидок корзины

Для корректной обработки заказа со своей схемой скидок в месте добавления в корзину следует назначить обработчик скидок.

Add2BasketByProductID($iProductID, $iQuantity, array('PRODUCT_PROVIDER_CLASS'=>'CCatalogProductProviderCustom'), $arProductParams);

CCatalogProductProviderCustom - это унаследованный от CCatalogProductProvider класс-обработчик корзины:

class CCatalogProductProviderCustom extends CCatalogProductProvider {
 
  public static function GetProductData($arParams) {
 
    $arResult = CCatalogProductProvider::GetProductData($arParams); // получаем данные из родительского класса
 
    $product = CPrice::GetByID($arResult['PRODUCT_PRICE_ID']); // получаем данные о товаре
    $MyDiscount = new MyDiscount(); // наш класс для обработки скидок
    $arResult = $MyDiscount->setDiscountCartItem($arResult, $product['PRODUCT_ID']); // модифицируем данные
 
    return $arResult; // отдаем модифицированные результаты
  }
 
  public static function OrderProduct($arParams) {
 
    $arResult = CCatalogProductProvider::OrderProduct($arParams);
 
    $product = CPrice::GetByID($arResult['PRODUCT_PRICE_ID']);
    $MyDiscount = new MyDiscount();
    $arResult = $MyDiscount->setDiscountCartItem($arResult, $product['PRODUCT_ID']);
 
    return $arResult;
  }
}

Исправляем баг "пересчета" скидок

Существует проблема «пересчета» скидки: в корзине она рассчитывается на основе цены «до» и «после», а не передается. Поэтому на уровне сотых, иногда - десятых, скидка отличается от «начальной».

Чтобы решить проблему «пересчета» скидок, скидки следует хранить в отдельной таблице:

CREATE TABLE `external_basket_discount` (
  `basket_id` int(11) unsigned NOT NULL,
  `product_id` int(11) unsigned NOT NULL,
  `my_discount` decimal(5,2) NOT NULL,
  PRIMARY KEY (`basket_id`,`product_id`)
) ENGINE=InnoDB;

Добавим в класс MyDiscount три метода:

public function storeDiscount($PRODUCT_ID, $MY_DISCOUNT) { // установить скидку на товар в корзине
  $basket_id = (int) CSaleBasket::GetBasketUserID();
  $product_id = (int) $PRODUCT_ID;
  $my_discount = (float) $MY_DISCOUNT;
 
  $query = "INSERT INTO `external_basket_discount` (`basket_id`, `product_id`, `my_discount`)
    VALUES ($basket_id, $product_id, '$my_discount')
    ON DUPLICATE KEY UPDATE `my_discount` = '$my_discount'
  ";
  $this->db->Query($query);
}
 
public static function extractCartItemDiscount($item) { // получить скидку на товар
  global $DB;
  $basket_id = (int) CSaleBasket::GetBasketUserID();
  $product_id = (int) $item['PRODUCT_ID'];
  $query = "SELECT `my_discount` FROM `external_basket_discount` WHERE `basket_id` = $basket_id AND `product_id` = $product_id";
  $results = $DB->Query($query);
  $results = $results->Fetch();
  $ret = isset($results['my_discount']) ? $results['my_discount'] : '';
  return $ret;
}
 
public static function clearDiscount() { // очистить скидки на товары в корзине
  global $DB;
  $basket_id = (int) CSaleBasket::GetBasketUserID();
  $query = "DELETE FROM `external_basket_discount` WHERE `basket_id` = $basket_id";
  $DB->Query($query);
}

Где вызывать эти методы:

  • storeDiscount - в методе MyDiscount::setDiscountCartItem()
  • extractCartItemDiscount - при выводе корзины
  • clearDiscount - после оформления заказа