Налаштування часткового повернення замовлення 1С-Бітрікс
Часткове повернення — повернення однієї або кількох позицій із замовлення, а не всього замовлення цілком. Поширений сценарій: покупець замовив 5 товарів, один виявився з дефектом, хоче повернути лише його. Або частина позицій не підійшла. Модуль sale Бітрікс підтримує часткове повернення на рівні API, але інтерфейс в особистому кабінеті та логіка розрахунку суми до повернення потребують налаштування.
Розрахунок суми часткового повернення
Це найнетривіальніша частина. У замовленні може бути знижка на все замовлення, купони, умови доставки, різні ставки ПДВ. При частковому поверненні потрібно перерахувати суму з урахуванням усіх цих факторів.
Підходи:
Підхід 1: Пропорційний — повертаємо пропорцію від загальної суми. Простий, але може давати копійчані розбіжності через округлення.
Підхід 2: За фактичною вартістю позиції — беремо $basketItem->getFinalPrice() × кількість. Це фінальна ціна після всіх знижок на позицію. Рекомендується.
Підхід 3: За вихідною ціною без знижок — рідко використовується, лише якщо умови повернення цього вимагають.
namespace Local\Returns;
class PartialReturnCalculator
{
public function calculateRefundAmount(\Bitrix\Sale\Order $order, array $returnItems): array
{
// $returnItems: [['basket_id' => int, 'quantity' => float], ...]
$refundItems = [];
$totalRefund = 0.0;
$basket = $order->getBasket();
foreach ($returnItems as $item) {
$basketItem = $basket->getItemById($item['basket_id']);
if (!$basketItem) continue;
$qty = min((float)$item['quantity'], $basketItem->getQuantity());
$pricePerUnit = $basketItem->getFinalPrice(); // ціна з урахуванням знижок
$lineTotal = round($pricePerUnit * $qty, 2);
$refundItems[] = [
'basket_id' => $item['basket_id'],
'name' => $basketItem->getField('NAME'),
'quantity' => $qty,
'price' => $pricePerUnit,
'line_total' => $lineTotal,
'vat_rate' => $basketItem->getField('VAT_RATE') ?? 0,
];
$totalRefund += $lineTotal;
}
// Перерахунок доставки при частковому поверненні
$shippingRefund = $this->calculateShippingRefund($order, $returnItems, $totalRefund);
return [
'items' => $refundItems,
'items_total' => $totalRefund,
'shipping_refund'=> $shippingRefund,
'total' => round($totalRefund + $shippingRefund, 2),
];
}
private function calculateShippingRefund(
\Bitrix\Sale\Order $order,
array $returnItems,
float $returnItemsTotal
): float {
// Якщо повертається все замовлення — повертаємо доставку повністю
$totalOrderItems = 0;
$returnBasketIds = array_column($returnItems, 'basket_id');
foreach ($order->getBasket() as $item) {
$totalOrderItems++;
}
if (count($returnBasketIds) === $totalOrderItems) {
$shipment = $order->getShipmentCollection()->getSystemShipment();
return $shipment ? (float)$shipment->getDeliveryPrice() : 0.0;
}
// Інакше — доставка не повертається (залежить від політики магазину)
return 0.0;
}
}
Створення часткового повернення через Sale API
class PartialReturnManager
{
public function create(
int $orderId,
array $returnItems,
string $reason = '',
int $userId = 0
): int {
\Bitrix\Main\Loader::includeModule('sale');
$order = \Bitrix\Sale\Order::load($orderId);
if (!$order) throw new \RuntimeException("Order not found: {$orderId}");
if ($userId && $order->getUserId() !== $userId) {
throw new \RuntimeException("Access denied to order {$orderId}");
}
// Розраховуємо суми
$calculator = new PartialReturnCalculator();
$refundData = $calculator->calculateRefundAmount($order, $returnItems);
// Створюємо об'єкт повернення
$orderReturn = \Bitrix\Sale\OrderReturn::create($order);
$orderReturn->setField('STATUS_ID', 'WAIT');
$orderReturn->setField('TYPE', 'MONEY');
$orderReturn->setField('REASON', $reason ?: 'Часткове повернення');
$orderReturn->setField('REFUND_AMOUNT', $refundData['total']);
$orderReturn->setField('COMMENT', $this->buildComment($refundData));
// Додаємо позиції повернення
foreach ($refundData['items'] as $item) {
$basketItem = $order->getBasket()->getItemById($item['basket_id']);
if (!$basketItem) continue;
$returnItem = $orderReturn->getReturn()->createItem($basketItem);
$returnItem->setField('QUANTITY', $item['quantity']);
$returnItem->setField('REASON', $reason);
}
$result = $orderReturn->save();
if (!$result->isSuccess()) {
throw new \RuntimeException(
'Partial return failed: ' . implode('; ', $result->getErrorMessages())
);
}
// Оновлюємо статус замовлення, якщо потрібно
$this->updateOrderAfterPartialReturn($order, $returnItems);
return $orderReturn->getId();
}
private function updateOrderAfterPartialReturn(
\Bitrix\Sale\Order $order,
array $returnItems
): void {
$returnBasketIds = array_column($returnItems, 'basket_id');
$totalBasketItems = count([...$order->getBasket()]);
// Якщо повертається останній товар — позначаємо замовлення як частково повернуте
if (count($returnBasketIds) < $totalBasketItems) {
// Додаємо кастомний статус «Частково повернуто»
// через поле USER_DESCRIPTION або кастомний статус
}
}
private function buildComment(array $refundData): string
{
$lines = ['Часткове повернення:'];
foreach ($refundData['items'] as $item) {
$lines[] = sprintf(
'- %s × %s = %s',
$item['name'],
$item['quantity'],
number_format($item['line_total'], 2)
);
}
if ($refundData['shipping_refund'] > 0) {
$lines[] = sprintf('- Доставка: %s', number_format($refundData['shipping_refund'], 2));
}
$lines[] = sprintf('Разом до повернення: %s', number_format($refundData['total'], 2));
return implode("\n", $lines);
}
}
Часткове повернення через платіжну систему
Більшість платіжних систем (ЮKassa, Тінькофф) підтримують partial refund через окремий метод API. Приклад для ЮKassa:
class YooKassaPartialRefund
{
public function refund(\Bitrix\Sale\Payment $payment, float $amount, array $items): bool
{
$paymentId = $payment->getField('PS_INVOICE_ID'); // ID платежу в ЮKassa
$receipt = $this->buildReceipt($items); // чек для податкової
$response = $this->yukassaClient->createRefund([
'payment_id' => $paymentId,
'amount' => [
'value' => number_format($amount, 2, '.', ''),
'currency' => 'RUB',
],
'description' => 'Часткове повернення по замовленню #' . $payment->getOrderId(),
'receipt' => $receipt,
]);
return isset($response['id']) && $response['status'] !== 'canceled';
}
private function buildReceipt(array $items): array
{
$receiptItems = [];
foreach ($items as $item) {
$receiptItems[] = [
'description' => $item['name'],
'quantity' => $item['quantity'],
'amount' => [
'value' => number_format($item['price'], 2, '.', ''),
'currency' => 'RUB',
],
'vat_code' => $this->vatRateToCode((float)$item['vat_rate']),
'payment_mode' => 'full_payment',
'payment_subject' => 'commodity',
];
}
return [
'customer' => ['email' => $this->customerEmail],
'items' => $receiptItems,
];
}
}
Ключовий момент: при частковому поверненні потрібно передати коригувальний чек до податкової через оператора фіскальних даних. ЮKassa робить це автоматично, якщо передати receipt у запиті повернення.
Склад робіт
- Калькулятор суми повернення з урахуванням знижок, кількості, ПДВ
-
PartialReturnManager: створення повернення через Sale ORM - Інтерфейс вибору позицій в особистому кабінеті
- Інтеграція з платіжними системами для partial refund
- Формування коригувального чека для податкової
- Логіка повернення суми доставки за певних умов
Терміни: базова механіка часткового повернення — 1–2 тижні. Повна версія з фіскальними чеками та інтеграцією кількох платіжних систем — 3–5 тижнів.







