Setting Up Partial Refunds on 1C-Bitrix
A partial refund is needed when a buyer has declined one item from an order, part of the goods could not be shipped, or a service was rendered incompletely. Unlike a full refund, a partial refund requires precise calculation of the amount and — when an online cash register is connected — the submission of a correction receipt. The standard 1C-Bitrix sale module supports refunds but requires additional configuration for correct interaction with the acquiring service.
Refund Architecture in 1C-Bitrix
A refund in 1C-Bitrix is a \Bitrix\Sale\PaymentCollection::createRefund() object or a direct call to the acquiring service API. Two approaches:
Via the sale module — create a refund through the payment handler. Advantage: automatic order status update and logging. Disadvantage: the handler must support the refund method.
Directly via the acquiring service API — call the refund API independently of 1C-Bitrix, then manually update the status. Advantage: greater control. Disadvantage: state synchronization is required.
Implementing the Refund Method in the Handler
class MyGatewayHandler extends \Bitrix\Sale\PaySystem\ServiceHandler
implements \Bitrix\Sale\PaySystem\IRefund
{
public function refund(
\Bitrix\Sale\Payment $payment,
$refundableSum
): \Bitrix\Sale\PaySystem\ServiceResult {
$result = new \Bitrix\Sale\PaySystem\ServiceResult();
$externalId = $payment->getField('PS_INVOICE_ID');
if (!$externalId) {
$result->addError(new \Bitrix\Main\Error('External payment ID not found'));
return $result;
}
try {
$refundResult = $this->gateway->createRefund([
'payment_id' => $externalId,
'amount' => [
'value' => number_format($refundableSum, 2, '.', ''),
'currency' => $payment->getField('CURRENCY'),
],
'description' => 'Partial refund for order ' . $payment->getOrderId(),
]);
$result->setOperationType(
\Bitrix\Sale\PaySystem\ServiceResult::MONEY_LEAVING
);
$result->setData(['refund_id' => $refundResult['id']]);
} catch (\Exception $e) {
$result->addError(new \Bitrix\Main\Error($e->getMessage()));
}
return $result;
}
}
Partial Refund via YooKassa
// Refund a specific amount from a paid payment
$refund = $yookassaClient->createRefund([
'payment_id' => $psInvoiceId,
'amount' => [
'value' => number_format($refundAmount, 2, '.', ''),
'currency' => 'RUB',
],
'description' => 'Item return: ' . $itemName,
'receipt' => $refundReceipt, // required with a fiscal cash register
], uniqid('', true));
Refund Receipt (Fiscal Law Compliance)
When an online cash register is connected, a partial refund requires a receipt of type refund. The receipt items include only those goods/services being refunded:
$refundReceipt = [
'customer' => ['email' => $buyer->getEmail()],
'items' => [],
];
foreach ($returnedItems as $item) {
$refundReceipt['items'][] = [
'description' => $item['name'],
'quantity' => $item['quantity'],
'amount' => [
'value' => number_format($item['price'], 2, '.', ''),
'currency' => 'RUB',
],
'vat_code' => $item['vat_code'],
'payment_subject' => 'commodity',
'payment_mode' => 'full_refund', // for a refund receipt
];
}
The total of items in the refund receipt must exactly match the amount in the refund request — this is verified by the payment system.
Initiating a Refund from the Admin Panel
In the 1C-Bitrix admin panel, the "Refund" button on the order page (/bitrix/admin/sale_order_detail.php) creates an amount input form. On submission, the method \Bitrix\Sale\PaymentCollection::createRefund() is called:
$order = \Bitrix\Sale\Order::load($orderId);
$paymentCollection = $order->getPaymentCollection();
foreach ($paymentCollection as $payment) {
if ($payment->isPaid() && $payment->getField('PS_INVOICE_ID') === $externalId) {
$paymentService = \Bitrix\Sale\PaySystem\Manager::getObjectById(
$payment->getPaymentSystemId()
);
$result = $paymentService->refund($payment, $refundAmount);
break;
}
}
Timeline
| Task | Duration |
|---|---|
Implementing the refund method in the handler |
1 day |
| Refund receipt for fiscal law compliance | 1 day |
| UI in the admin panel | 0.5 day |







