Setting Up Full Refunds on 1C-Bitrix
A full refund is the cancellation of an entire payment and the return of the full order amount to the buyer. Technically, this is simpler than a partial refund, but it contains several nuances: the time window for cancellation, the difference between cancelling a pre-authorization and refunding charged funds, and the mandatory refund receipt when a cash register is connected.
Cancellation vs. Refund: What Is the Difference
Cancellation (void/cancel) — only possible before the bank has settled the transaction (usually before the end of the business day). Funds are returned instantly because they were not actually charged. In two-stage payments, this is the cancellation of the pre-authorization.
Refund (refund) — after a settled transaction. The funds are first charged, then a reverse transfer is initiated. The time for the amount to be credited to the buyer is 3–10 business days, depending on the bank.
Implementation 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');
// Full refund = the total payment amount
$fullAmount = $payment->getSum();
// Validate: refund amount must not exceed the payment amount
if ($refundableSum > $fullAmount) {
$result->addError(new \Bitrix\Main\Error(
'Refund amount (' . $refundableSum . ') exceeds payment amount (' . $fullAmount . ')'
));
return $result;
}
try {
// Try cancellation first (faster for the buyer)
$cancelled = $this->tryCancel($externalId);
if (!$cancelled) {
// If cancellation is not possible — full refund via refund API
$this->gateway->createRefund([
'payment_id' => $externalId,
'amount' => [
'value' => number_format($refundableSum, 2, '.', ''),
'currency' => $payment->getField('CURRENCY'),
],
]);
}
// Update payment status in 1C-Bitrix
$payment->setPaid('N');
$payment->setField('PS_STATUS', 'REFUNDED');
$payment->save();
$result->setOperationType(
\Bitrix\Sale\PaySystem\ServiceResult::MONEY_LEAVING
);
} catch (\Exception $e) {
$result->addError(new \Bitrix\Main\Error($e->getMessage()));
}
return $result;
}
private function tryCancel(string $externalId): bool
{
try {
$this->gateway->cancelPayment($externalId);
return true;
} catch (\Exception $e) {
// If cancellation is not possible (transaction already settled) — return false
return false;
}
}
}
Full Refund via YooKassa
// Simple full refund
$payment = $yookassaClient->getPaymentInfo($psInvoiceId);
$fullAmount = $payment->getAmount()->getValue();
$refund = $yookassaClient->createRefund([
'payment_id' => $psInvoiceId,
'amount' => ['value' => $fullAmount, 'currency' => 'RUB'],
'receipt' => $fullRefundReceipt, // required with a fiscal cash register
], uniqid('', true));
Refund Receipt (Fiscal Law Compliance)
For a full refund, the receipt must contain all items from the original payment with the payment_mode: full_refund attribute:
// Get items from the original order
$order = \Bitrix\Sale\Order::load($orderId);
$basket = $order->getBasket();
$refundItems = [];
foreach ($basket->getOrderableItems() as $item) {
$refundItems[] = [
'description' => $item->getField('NAME'),
'quantity' => $item->getQuantity(),
'amount' => ['value' => number_format($item->getPrice(), 2, '.', ''), 'currency' => 'RUB'],
'vat_code' => getVatCode($item),
'payment_subject' => 'commodity',
'payment_mode' => 'full_refund',
];
}
// Add delivery
$delivery = $order->getDeliveryPrice();
if ($delivery > 0) {
$refundItems[] = [
'description' => 'Delivery',
'quantity' => 1,
'amount' => ['value' => number_format($delivery, 2, '.', ''), 'currency' => 'RUB'],
'vat_code' => 1,
'payment_subject' => 'service',
'payment_mode' => 'full_refund',
];
}
Order Status After Refund
After a successful refund, the order status must be updated:
// Change order status to "Cancelled" or "Refunded"
$order->setField('STATUS_ID', 'VD'); // "Refund" status code — configurable
$order->save();
// Return items to stock if required
if ($returnToStock) {
$shipmentCollection = $order->getShipmentCollection();
foreach ($shipmentCollection as $shipment) {
if (!$shipment->isSystem()) {
$shipment->setField('CANCELED', 'Y');
}
}
$order->save();
}
Timeline
| Task | Duration |
|---|---|
refund method in handler + status update |
1 day |
| Refund receipt for fiscal law compliance | 1 day |
| Testing cancellation and refund scenarios | 0.5 day |







