В одном из проектов столкнулся с проблемой формирования фискального чека в модуле оплаты best2pay для WooCommerce. Ошибка проявлялась не всегда, а только при использовании промокодов и нескольких товаров в корзине. Формально платеж проходил, но строки в чеке формировались некорректно: вместо скидки появлялась строка с «авансовым платежом».
Разбор показал, что проблема состоит из двух частей: неправильный расчёт цены позиции в самом модуле и отдельная ошибка округления, возникающая после применения купона.
Как best2pay формирует строки чека
В модуле best2pay строки фискального чека собираются на основе данных товара, а не фактической строки корзины. Используется примерно такой подход:
- берётся
get_price()у товара - умножается на количество
- суммируется в итог чека
Это выглядит логично, но в WooCommerce после применения купонов и скидок фактическая цена позиции в корзине уже может отличаться от базовой цены товара. WooCommerce пересчитывает итог строки, а модуль — нет.
В результате возникает расхождение:
- сумма по строкам ≠ итог заказа
- best2pay добавляет отдельную корректирующую строку
- и эта строка в чеке выглядит не как скидка, а как отдельный платёжный тип (фискальная система трактует его как — «аванс»)
Вот фрагмент кода модуля, который добавляет такую «компенсационную» строку:
|
1 2 3 4 |
if($fiscal_diff = abs($fiscal_amount - $order_amount)){ $fiscal_positions[] = [1, $fiscal_diff, (int) $this->tax, 'Скидка', 14]; $shop_cart = []; } |
Формально это должно быть скидкой, но по коду операции и поведению шлюза это обрабатывается иначе. Т.к. документации в публичном доступе нет, то угадать правильный код операции нельзя.
Исправление: брать цену из строки корзины
Правильнее считать цену позиции не из товара, а из фактических данных строки корзины — там уже учтены:
- купоны
- скидки
- динамическое ценообразование
- модификаторы
Я сделал патч, который заменяет получение цены товара на вычисление цены из суммы строки корзины.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
diff --git a/www/wp-content/plugins/best2pay-payment-method-visamastercard/includes/class-wc-gateway-best2pay.php b/www/wp-content/plugins/best2pay-payment-method-visamastercard/includes/class-wc-gateway-best2pay.php index 1477beac..dd14ab72 100644 --- a/www/wp-content/plugins/best2pay-payment-method-visamastercard/includes/class-wc-gateway-best2pay.php +++ b/www/wp-content/plugins/best2pay-payment-method-visamastercard/includes/class-wc-gateway-best2pay.php @@ -800,7 +800,10 @@ class WC_Best2Pay_Gateway extends WC_Payment_Gateway $basket_item_data = $basket_item->get_data(); $fiscal_positions[$b_key]['quantity'] = $basket_item_data['quantity']; - $fiscal_amount += $basket_item_data['quantity'] * ($fiscal_positions[$b_key]['amount'] = $this->client->centifyAmount($basket_item->get_product()->get_price())); + $price = $basket_item_data['total'] / $basket_item_data['quantity']; + $fiscal_positions[$b_key]['amount'] = $this->client->centifyAmount($price); + + $fiscal_amount += $basket_item_data['quantity'] * $fiscal_positions[$b_key]['amount']; $fiscal_positions[$b_key]['tax'] = (int) $this->tax; $fiscal_positions[$b_key]['name'] = str_ireplace([';', '|'], '', $basket_item_data['name']); |
Теперь цена позиции берётся из total строки корзины — то есть уже с учётом купона.
После этого «строка со скидкой» перестаёт появляться — потому что суммы сходятся.
Но тут появляется вторая проблема.
Проблема дробных копеек на единицу товара
Если купон применён к строке с несколькими товарами, WooCommerce спокойно оперирует дробными значениями. Например:
- было: 120 ₽
- стало: 100 ₽ после купона
- количество: 3
Цена за единицу = 33.3333…
WooCommerce это допускает.
Фискальный чек — нет.
best2pay отклоняет такие позиции, потому что цена единицы должна иметь ровно 2 знака после запятой.
Значит, нужно гарантировать, что эффективная цена за единицу после скидки всегда округляется до копеек.
Коррекция скидки через фильтр WooCommerce
Решение — немного увеличить размер скидки так, чтобы итоговая цена за единицу товара стала корректной с точки зрения фискализации.
Это можно сделать через фильтр:
|
1 |
woocommerce_coupon_get_discount_amount |
Я добавил такой обработчик:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
add_filter('woocommerce_coupon_get_discount_amount', 'fix_coupon_discount', 20, 5); function fix_coupon_discount( $n, // рассчитанная скидка $discounting_amount, // сумма строки до скидки $cart_item, $single, $t ){ $line_total = $cart_item['line_subtotal'] - $n; $price_effective = $line_total / $cart_item['quantity']; $price_fixed = floor($price_effective * 100) / 100; $total_delta = round( $line_total - $cart_item['quantity'] * $price_fixed, 2 ); return $n + $total_delta; } |
Что здесь происходит:
- мы берём итог строки после скидки
- считаем цену за единицу
- обрезаем её до копеек вниз
- считаем расхождение
- добавляем это расхождение к скидке
Итог
Проблема оказалась не в best2pay как таковом, а в несовпадении моделей расчёта:
WooCommerce считает строками корзины
модуль — базовыми ценами товара
фискальный чек требует точных цен за единицу
Комбинация патча в модуле и фильтра WooCommerce позволяет:
- убрать лишнюю строку «скидки/аванса»
- синхронизировать суммы
- избежать дробных копеек
- стабильно формировать фискальные чеки
Если модуль обновляется — патч лучше хранить в виде git-patch и накатывать автоматически при деплое.


